blob: 2f9f6df067f5893f43474806b5268ddac76572c5 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
import 'package:kernel/ast.dart';
import '../fasta/kernel/late_lowering.dart';
import '../fasta/source/source_extension_builder.dart' show extensionThisName;
/// Returns `true` if [node] is the field holding the value of a lowered late
/// field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where '_#field' is the field holding that value.
///
/// The default value of this field is `null`.
bool isLateLoweredField(Field node) {
return node.isInternalImplementation &&
node.name != null &&
node.name.text.startsWith(lateFieldPrefix) &&
!node.name.text.endsWith(lateIsSetSuffix);
}
/// Returns the name of the original field for a lowered late field where
/// [node] is the field holding the value of a lowered late field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where '_#field' is the field holding that value and 'field' is the name of
/// the original field.
///
/// This assumes that `isLateLoweredField(node)` is true.
Name extractFieldNameFromLateLoweredField(Field node) {
assert(isLateLoweredField(node));
String prefix = lateFieldPrefix;
if (node.isInstanceMember) {
prefix = '$prefix${node.enclosingClass.name}#';
}
return new Name(node.name.text.substring(prefix.length), node.name.library);
}
/// Returns `true` if [node] is the field holding the marker for whether a
/// lowered late field has been set or not.
///
/// For instance
///
/// late int? field;
///
/// is lowered to (simplified):
///
/// bool _#field#isSet = false;
/// int? _#field = null;
/// int get field => _#field#isSet ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// _#field#isSet = true;
/// }
///
/// where '_#field#isSet' is the field holding the marker.
///
/// The default value of this field is `false`.
bool isLateLoweredIsSetField(Field node) {
return node.isInternalImplementation &&
node.name != null &&
node.name.text.startsWith(lateFieldPrefix) &&
node.name.text.endsWith(lateIsSetSuffix);
}
/// Returns the name of the original field for a lowered late field where [node]
/// is the field holding the marker for whether the lowered late field has been
/// set or not.
///
/// For instance
///
/// late int? field;
///
/// is lowered to (simplified):
///
/// bool _#field#isSet = false;
/// int? _#field = null;
/// int get field => _#field#isSet ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// _#field#isSet = true;
/// }
///
/// where '_#field#isSet' is the field holding the marker and 'field' is the
/// name of the original field.
///
/// This assumes that `isLateLoweredIsSetField(node)` is true.
Name extractFieldNameFromLateLoweredIsSetField(Field node) {
assert(isLateLoweredIsSetField(node));
String prefix = lateFieldPrefix;
if (node.isInstanceMember) {
prefix = '$prefix${node.enclosingClass.name}#';
}
return new Name(
node.name.text.substring(
prefix.length, node.name.text.length - lateIsSetSuffix.length),
node.name.library);
}
/// Returns `true` if [node] is the getter for reading the value of a lowered
/// late field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where 'int get field' is the getter for reading the field.
///
/// Note that the computation of this predicate is _not_ efficient and the
/// result should be cached on the use site if queried repeatedly.
bool isLateLoweredFieldGetter(Procedure node) {
if (node.kind == ProcedureKind.Getter) {
TreeNode parent = node.parent;
if (parent is Class) {
return parent.fields.any((Field field) =>
isLateLoweredField(field) &&
field.name.text.endsWith(node.name.text));
} else if (parent is Library) {
return parent.fields.any((Field field) =>
isLateLoweredField(field) &&
field.name.text.endsWith(node.name.text));
}
}
return false;
}
/// Returns the name of the original field for a lowered late field where [node]
/// is the getter for reading the value of a lowered late field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where 'int get field' is the getter for reading the field and 'field' is the
/// name of the original field.
///
/// This assumes that `isLateLoweredFieldGetter(node)` is true.
Name extractFieldNameFromLateLoweredFieldGetter(Procedure node) {
assert(isLateLoweredFieldGetter(node));
return node.name;
}
/// Returns `true` if [node] is the setter for setting the value of a lowered
/// late field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where 'void set field' is the setter for setting the value of the field.
///
/// Note that the computation of this predicate is _not_ efficient and the
/// result should be cached on the use site if queried repeatedly.
bool isLateLoweredFieldSetter(Procedure node) {
if (node.kind == ProcedureKind.Setter) {
TreeNode parent = node.parent;
if (parent is Class) {
return parent.fields.any((Field field) =>
isLateLoweredField(field) &&
field.name.text.endsWith(node.name.text));
} else if (parent is Library) {
return parent.fields.any((Field field) =>
isLateLoweredField(field) &&
field.name.text.endsWith(node.name.text));
}
}
return false;
}
/// Returns the name of the original field for a lowered late field where [node]
/// is the setter for setting the value of a lowered late field.
///
/// For instance
///
/// late int field;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field != null ? _#field : throw 'Uninitialized';
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where 'void set field' is the setter for setting the value of the field and
/// 'field' is the name of the original field.
///
/// This assumes that `isLateLoweredFieldSetter(node)` is true.
Name extractFieldNameFromLateLoweredFieldSetter(Procedure node) {
assert(isLateLoweredFieldSetter(node));
return node.name;
}
/// Returns the original initializer of a lowered late field where [node] is
/// either the field holding the value, the field holding the marker for whether
/// it has been set or not, getter for reading the value, or the setter for
/// setting the value of the field.
///
/// For instance
///
/// late int field = 42;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field == null ? throw 'Uninitialized' : _#field = 42;
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where this original initializer is `42`, '_#field' is the field holding that
/// value, '_#field#isSet' is the field holding the marker, 'int get field' is
/// the getter for reading the field, and 'void set field' is the setter for
/// setting the value of the field.
///
/// If the original late field had no initializer, `null` is returned.
///
/// If [node] is not part of a late field lowering, `null` is returned.
Expression getLateFieldInitializer(Member node) {
Procedure lateFieldGetter = _getLateFieldTarget(node);
if (lateFieldGetter != null) {
Statement body = lateFieldGetter.function.body;
if (body is Block &&
body.statements.length == 2 &&
body.statements.first is IfStatement) {
IfStatement ifStatement = body.statements.first;
if (ifStatement.then is Block) {
Block block = ifStatement.then;
if (block.statements.isNotEmpty &&
block.statements.first is ExpressionStatement) {
ExpressionStatement firstStatement = block.statements.first;
if (firstStatement.expression is PropertySet) {
// We have
//
// get field {
// if (!_#isSet#field) {
// this._#field = <init>;
// ...
// }
// return _#field;
// }
//
// in case `<init>` is the initializer.
PropertySet propertySet = firstStatement.expression;
assert(propertySet.interfaceTarget == getLateFieldTarget(node));
return propertySet.value;
} else if (firstStatement.expression is InstanceSet) {
// We have
//
// get field {
// if (!_#isSet#field) {
// this._#field = <init>;
// ...
// }
// return _#field;
// }
//
// in case `<init>` is the initializer.
InstanceSet instanceSet = firstStatement.expression;
assert(instanceSet.interfaceTarget == getLateFieldTarget(node));
return instanceSet.value;
} else if (firstStatement.expression is StaticSet) {
// We have
//
// get field {
// if (!_#isSet#field) {
// _#field = <init>;
// ...
// }
// return _#field;
// }
//
// in case `<init>` is the initializer.
StaticSet staticSet = firstStatement.expression;
assert(staticSet.target == getLateFieldTarget(node));
return staticSet.value;
}
} else if (block.statements.isNotEmpty &&
block.statements.first is VariableDeclaration) {
// We have
//
// get field {
// if (!_#isSet#field) {
// var temp = <init>;
// if (_#isSet#field) throw '...'
// _#field = temp;
// _#isSet#field = true
// }
// return _#field;
// }
//
// in case `<init>` is the initializer.
VariableDeclaration variableDeclaration = block.statements.first;
return variableDeclaration.initializer;
}
}
return null;
} else if (body is ReturnStatement) {
Expression expression = body.expression;
if (expression is ConditionalExpression &&
expression.otherwise is Throw) {
// We have
//
// get field => _#field#isSet ? #field : throw ...;
//
// in which case there is no initializer.
return null;
} else if (expression is Let) {
Expression letBody = expression.body;
if (letBody is ConditionalExpression) {
Expression then = letBody.then;
if (then is Throw) {
// We have
//
// get field => let # = _#field in <is-unset> ? throw ... : #;
//
// in which case there is no initializer.
return null;
} else if (then is PropertySet) {
// We have
//
// get field => let # = this._#field in <is-unset>
// ? this._#field = <init> : #;
//
// in which case `<init>` is the initializer.
assert(then.interfaceTarget == getLateFieldTarget(node));
return then.value;
} else if (then is InstanceSet) {
// We have
//
// get field => let # = this._#field in <is-unset>
// ? this._#field = <init> : #;
//
// in which case `<init>` is the initializer.
assert(then.interfaceTarget == getLateFieldTarget(node));
return then.value;
} else if (then is StaticSet) {
// We have
//
// get field => let # = this._#field in <is-unset>
// ? this._#field = <init> : #;
//
// in which case `<init>` is the initializer.
assert(then.target == getLateFieldTarget(node));
return then.value;
} else if (then is Let && then.body is ConditionalExpression) {
// We have
//
// get field => let #1 = _#field in <is-unset>
// ? let #2 = <init> in ...
// : #1;
//
// in which case `<init>` is the initializer.
return then.variable.initializer;
}
}
}
}
throw new UnsupportedError(
'Unrecognized late getter encoding for $lateFieldGetter: ${body}');
}
return null;
}
/// Returns getter for reading the value of a lowered late field where [node] is
/// either the field holding the value, the field holding the marker for whether
/// it has been set or not, getter for reading the value, or the setter for
/// setting the value of the field.
Procedure _getLateFieldTarget(Member node) {
Name lateFieldName;
if (node is Procedure) {
if (isLateLoweredFieldGetter(node)) {
return node;
} else if (isLateLoweredFieldSetter(node)) {
lateFieldName = extractFieldNameFromLateLoweredFieldSetter(node);
}
} else if (node is Field) {
if (isLateLoweredField(node)) {
lateFieldName = extractFieldNameFromLateLoweredField(node);
} else if (isLateLoweredIsSetField(node)) {
lateFieldName = extractFieldNameFromLateLoweredIsSetField(node);
}
}
if (lateFieldName != null) {
TreeNode parent = node.parent;
List<Procedure> procedures;
if (parent is Class) {
procedures = parent.procedures;
} else if (parent is Library) {
procedures = parent.procedures;
}
return procedures.singleWhere((Procedure procedure) =>
isLateLoweredFieldGetter(procedure) &&
extractFieldNameFromLateLoweredFieldGetter(procedure) == lateFieldName);
}
return null;
}
/// Returns the field holding the value for a lowered late field where [node] is
/// either the field holding the value, the field holding the marker for whether
/// it has been set or not, getter for reading the value, or the setter for
/// setting the value of the field.
///
/// For instance
///
/// late int field = 42;
///
/// is lowered to (simplified):
///
/// int? _#field = null;
/// int get field => _#field == null ? throw 'Uninitialized' : _#field = 42;
/// void set field(int value) {
/// _#field = value;
/// }
///
/// where '_#field' is the field holding that value, '_#field#isSet' is the
/// field holding the marker, 'int get field' is the getter for reading the
/// field, and 'void set field' is the setter for setting the value of the
/// field.
///
/// If [node] is not part of a late field lowering, `null` is returned.
Field getLateFieldTarget(Member node) {
Name lateFieldName;
if (node is Procedure) {
if (isLateLoweredFieldGetter(node)) {
lateFieldName = extractFieldNameFromLateLoweredFieldGetter(node);
} else if (isLateLoweredFieldSetter(node)) {
lateFieldName = extractFieldNameFromLateLoweredFieldSetter(node);
}
} else if (node is Field) {
if (isLateLoweredField(node)) {
return node;
} else if (isLateLoweredIsSetField(node)) {
lateFieldName = extractFieldNameFromLateLoweredIsSetField(node);
}
}
if (lateFieldName != null) {
TreeNode parent = node.parent;
List<Field> fields;
if (parent is Class) {
fields = parent.fields;
} else if (parent is Library) {
fields = parent.fields;
}
return fields.singleWhere((Field field) =>
isLateLoweredField(field) &&
extractFieldNameFromLateLoweredField(field) == lateFieldName);
}
return null;
}
/// Returns `true` if [node] is the local variable holding the value of a
/// lowered late variable.
///
/// For instance
///
/// late int local;
///
/// is lowered to (simplified):
///
/// int? #local = null;
/// int #local#get() => #local != null ? #local : throw 'Uninitialized';
/// void #local#set(int value) {
/// #local = value;
/// }
///
/// where '#local' is the local variable holding that value.
///
/// The default value of this variable is `null`.
bool isLateLoweredLocal(VariableDeclaration node) {
assert(node.isLowered ||
node.name == null ||
!isLateLoweredLocalName(node.name));
return node.isLowered && isLateLoweredLocalName(node.name);
}
/// Returns `true` if [name] is the name of a local variable holding the value
/// of a lowered late variable.
bool isLateLoweredLocalName(String name) {
return name != extensionThisName &&
name.startsWith(lateLocalPrefix) &&
!name.endsWith(lateIsSetSuffix) &&
!name.endsWith(lateLocalGetterSuffix) &&
!name.endsWith(lateLocalSetterSuffix);
}
/// Returns the name of the original late local variable from the [name] of the
/// local variable holding the value of the lowered late variable.
///
/// This method assumes that `isLateLoweredLocalName(name)` is `true`.
String extractLocalNameFromLateLoweredLocal(String name) {
return name.substring(lateLocalPrefix.length);
}
/// Returns `true` if [node] is the local variable holding the marker for
/// whether a lowered late local variable has been set or not.
///
/// For instance
///
/// late int? local;
///
/// is lowered to (simplified):
///
/// bool #local#isSet = false;
/// int? #local = null;
/// int #local#get() => _#field#isSet ? #local : throw 'Uninitialized';
/// void #local#set(int value) {
/// #local = value;
/// #local#isSet = true;
/// }
///
/// where '#local#isSet' is the local variable holding the marker.
///
/// The default value of this variable is `false`.
bool isLateLoweredIsSetLocal(VariableDeclaration node) {
assert(node.isLowered ||
node.name == null ||
!isLateLoweredIsSetLocalName(node.name));
return node.isLowered && isLateLoweredIsSetLocalName(node.name);
}
/// Returns `true` if [name] is the name of a local variable holding the marker
/// for whether a lowered late local variable has been set or not.
bool isLateLoweredIsSetLocalName(String name) {
return name.startsWith(lateLocalPrefix) && name.endsWith(lateIsSetSuffix);
}
/// Returns the name of the original late local variable from the [name] of the
/// local variable holding the marker for whether the lowered late local
/// variable has been set or not.
///
/// This method assumes that `isLateLoweredIsSetName(name)` is `true`.
String extractLocalNameFromLateLoweredIsSet(String name) {
return name.substring(
lateLocalPrefix.length, name.length - lateIsSetSuffix.length);
}
/// Returns `true` if [node] is the local variable for the local function for
/// reading the value of a lowered late variable.
///
/// For instance
///
/// late int local;
///
/// is lowered to (simplified):
///
/// int? #local = null;
/// int #local#get() => #local != null ? #local : throw 'Uninitialized';
/// void #local#set(int value) {
/// #local = value;
/// }
///
/// where '#local#get' is the local function for reading the variable.
bool isLateLoweredLocalGetter(VariableDeclaration node) {
assert(node.isLowered ||
node.name == null ||
!isLateLoweredLocalGetterName(node.name));
return node.isLowered && isLateLoweredLocalGetterName(node.name);
}
/// Returns `true` if [name] is the name of the local variable for the local
/// function for reading the value of a lowered late variable.
bool isLateLoweredLocalGetterName(String name) {
return name.startsWith(lateLocalPrefix) &&
name.endsWith(lateLocalGetterSuffix);
}
/// Returns the name of the original late local variable from the [name] of the
/// local variable for the local function for reading the value of the lowered
/// late variable.
///
/// This method assumes that `isLateLoweredGetterName(name)` is `true`.
String extractLocalNameFromLateLoweredGetter(String name) {
return name.substring(
lateLocalPrefix.length, name.length - lateLocalGetterSuffix.length);
}
/// Returns `true` if [node] is the local variable for the local function for
/// setting the value of a lowered late variable.
///
/// For instance
///
/// late int local;
///
/// is lowered to (simplified):
///
/// int? #local = null;
/// int #local#get() => #local != null ? #local : throw 'Uninitialized';
/// void #local#set(int value) {
/// #local = value;
/// }
///
/// where '#local#set' is the local function for setting the value of the
/// variable.
bool isLateLoweredLocalSetter(VariableDeclaration node) {
assert(node.isLowered ||
node.name == null ||
!isLateLoweredLocalSetterName(node.name));
return node.isLowered && isLateLoweredLocalSetterName(node.name);
}
/// Returns `true` if [name] is the name of the local variable for the local
/// function for setting the value of a lowered late variable.
bool isLateLoweredLocalSetterName(String name) {
return name.startsWith(lateLocalPrefix) &&
name.endsWith(lateLocalSetterSuffix);
}
/// Returns the name of the original late local variable from the [name] of the
/// local variable for the local function for setting the value of the lowered
/// late variable.
///
/// This method assumes that `isLateLoweredSetterName(name)` is `true`.
String extractLocalNameFromLateLoweredSetter(String name) {
return name.substring(
lateLocalPrefix.length, name.length - lateLocalSetterSuffix.length);
}
/// Returns `true` if [node] is the synthetic parameter holding the `this` value
/// in the encoding of extension instance members.
///
/// For instance
///
/// extension Extension on int {
/// int method() => this;
/// }
///
/// is encoded as
///
/// int Extension|method(int #this) => #this;
///
/// where '#this' is the synthetic "extension this" parameter.
bool isExtensionThis(VariableDeclaration node) {
assert(
node.isLowered || node.name == null || !isExtensionThisName(node.name));
return node.isLowered && isExtensionThisName(node.name);
}
/// Returns `true` if [name] is the name of the synthetic parameter holding the
/// `this` value in the encoding of extension instance members.
bool isExtensionThisName(String name) {
return name == extensionThisName;
}
/// Returns the name of the original variable from the [name] of the synthetic
/// parameter holding the `this` value in the encoding of extension instance
/// members.
///
/// This method assumes that `isExtensionThisName(name)` is `true`.
String extractLocalNameForExtensionThis(String name) {
return 'this';
}
/// Returns the original name of the variable [node].
///
/// If [node] is a lowered variable then the name before lowering is returned.
/// Otherwise the name of the variable itself is returned.
///
/// Note that the name can be `null` in case of a synthetic variable created
/// for instance for encoding of `?.`.
String extractLocalNameFromVariable(VariableDeclaration node) {
if (node.isLowered) {
String name = _extractLocalName(node.name);
if (name == null) {
throw new UnsupportedError("Unrecognized lowered local $node");
}
return name;
}
return node.name;
}
/// Returns the original name of a variable by the given [name].
///
/// If [name] is the name of a lowered variable then the name before lowering is
/// returned. Otherwise the name of the variable itself is returned.
///
/// This assumed that [name] is non-null.
String extractLocalName(String name) {
return _extractLocalName(name) ?? name;
}
/// Returns the original name of a lowered variable by the given [name].
///
/// If [name] doesn't correspond to a lowered name `null` is returned.
String _extractLocalName(String name) {
if (isExtensionThisName(name)) {
return extractLocalNameForExtensionThis(name);
} else if (isLateLoweredLocalName(name)) {
return extractLocalNameFromLateLoweredLocal(name);
} else if (isLateLoweredLocalGetterName(name)) {
return extractLocalNameFromLateLoweredGetter(name);
} else if (isLateLoweredLocalSetterName(name)) {
return extractLocalNameFromLateLoweredSetter(name);
} else if (isLateLoweredIsSetLocalName(name)) {
return extractLocalNameFromLateLoweredIsSet(name);
}
return null;
}