blob: 5d7f537a3eabbd28eb08f35a6808ead514625b18 [file] [log] [blame]
// Copyright (c) 2016, 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.
library fasta.body_builder;
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
import 'package:_fe_analyzer_shared/src/parser/parser.dart'
show
Assert,
BlockKind,
ConstructorReferenceContext,
FormalParameterKind,
IdentifierContext,
MemberKind,
Parser,
lengthForToken,
lengthOfSpan,
optional;
import 'package:_fe_analyzer_shared/src/parser/quote.dart'
show
Quote,
analyzeQuote,
unescape,
unescapeFirstStringPart,
unescapeLastStringPart,
unescapeString;
import 'package:_fe_analyzer_shared/src/parser/stack_listener.dart'
show FixedNullableList, GrowableList, NullValue, ParserRecovery;
import 'package:_fe_analyzer_shared/src/parser/value_kind.dart';
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show Token;
import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart'
show isBinaryOperator, isMinusOperator, isUserDefinableOperator;
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:front_end/src/fasta/kernel/benchmarker.dart' show Benchmarker;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/clone.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/src/bounds_checks.dart' hide calculateBounds;
import 'package:kernel/transformations/flags.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import '../builder/builder.dart';
import '../builder/class_builder.dart';
import '../builder/constructor_builder.dart';
import '../builder/declaration_builder.dart';
import '../builder/extension_builder.dart';
import '../builder/field_builder.dart';
import '../builder/formal_parameter_builder.dart';
import '../builder/function_type_builder.dart';
import '../builder/invalid_type_declaration_builder.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/modifier_builder.dart';
import '../builder/named_type_builder.dart';
import '../builder/nullability_builder.dart';
import '../builder/prefix_builder.dart';
import '../builder/type_alias_builder.dart';
import '../builder/type_builder.dart';
import '../builder/type_declaration_builder.dart';
import '../builder/type_variable_builder.dart';
import '../builder/variable_builder.dart';
import '../builder/void_type_declaration_builder.dart';
import '../constant_context.dart' show ConstantContext;
import '../dill/dill_library_builder.dart' show DillLibraryBuilder;
import '../fasta_codes.dart' as fasta;
import '../fasta_codes.dart'
show
LocatedMessage,
Message,
Template,
noLength,
templateExperimentNotEnabled;
import '../identifiers.dart'
show Identifier, InitializedIdentifier, QualifiedName, flattenName;
import '../messages.dart' as messages show getLocationFromUri;
import '../modifier.dart'
show Modifier, constMask, covariantMask, finalMask, lateMask, requiredMask;
import '../names.dart' show emptyName, minusName, plusName;
import '../problems.dart'
show internalProblem, unexpected, unhandled, unsupported;
import '../scope.dart';
import '../source/diet_parser.dart';
import '../source/source_class_builder.dart';
import '../source/source_constructor_builder.dart';
import '../source/source_enum_builder.dart';
import '../source/source_factory_builder.dart';
import '../source/source_field_builder.dart';
import '../source/source_function_builder.dart';
import '../source/source_library_builder.dart' show SourceLibraryBuilder;
import '../source/source_procedure_builder.dart';
import '../source/stack_listener_impl.dart'
show StackListenerImpl, offsetForToken;
import '../source/value_kinds.dart';
import '../type_inference/type_inferrer.dart'
show TypeInferrer, InferredFunctionBody, InitializerInferenceResult;
import '../type_inference/type_schema.dart' show UnknownType;
import '../util/helpers.dart' show DelayedActionPerformer;
import 'collections.dart';
import 'constness.dart' show Constness;
import 'constructor_tearoff_lowering.dart';
import 'expression_generator.dart';
import 'expression_generator_helper.dart';
import 'forest.dart' show Forest;
import 'implicit_type_argument.dart' show ImplicitTypeArgument;
import 'internal_ast.dart';
import 'kernel_variable_builder.dart';
import 'load_library_builder.dart';
import 'redirecting_factory_body.dart'
show
RedirectingFactoryBody,
RedirectionTarget,
getRedirectingFactoryBody,
getRedirectionTarget;
import 'type_algorithms.dart' show calculateBounds;
import 'utils.dart';
// TODO(ahe): Remove this and ensure all nodes have a location.
const int noLocation = TreeNode.noOffset;
// TODO(danrubel): Remove this once control flow and spread collection support
// has been enabled by default.
const Object invalidCollectionElement = const Object();
enum JumpTargetKind {
Break,
Continue,
Goto, // Continue label in switch.
}
class BodyBuilder extends StackListenerImpl
implements ExpressionGeneratorHelper, EnsureLoaded, DelayedActionPerformer {
@override
final Forest forest;
// TODO(ahe): Rename [library] to 'part'.
@override
final SourceLibraryBuilder libraryBuilder;
final ModifierBuilder member;
/// The class, mixin or extension declaration in which [member] is declared,
/// if any.
final DeclarationBuilder? declarationBuilder;
/// The source class or mixin declaration in which [member] is declared, if
/// any.
///
/// If [member] is a synthesized member for expression evaluation the
/// enclosing declaration might be a [DillClassBuilder]. This can be accessed
/// through [declarationBuilder].
final SourceClassBuilder? sourceClassBuilder;
final ClassHierarchy hierarchy;
@override
final CoreTypes coreTypes;
final bool isDeclarationInstanceMember;
final Scope enclosingScope;
final bool enableNative;
final bool stringExpectedAfterNative;
/// Whether to ignore an unresolved reference to `main` within the body of
/// `_getMainClosure` when compiling the current library.
///
/// This as a temporary workaround. The standalone VM and flutter have
/// special logic to resolve `main` in `_getMainClosure`, this flag is used to
/// ignore that reference to `main`, but only on libraries where we expect to
/// see it (today that is dart:_builtin and dart:ui).
///
// TODO(ahe,sigmund): remove when the VM gets rid of the special rule, see
// https://github.com/dart-lang/sdk/issues/28989.
final bool ignoreMainInGetMainClosure;
// TODO(ahe): Consider renaming [uri] to 'partUri'.
@override
final Uri uri;
final TypeInferrer typeInferrer;
final Benchmarker? benchmarker;
/// Only used when [member] is a constructor. It tracks if an implicit super
/// initializer is needed.
///
/// An implicit super initializer isn't needed
///
/// 1. if the current class is Object,
/// 2. if there is an explicit super initializer,
/// 3. if there is a redirecting (this) initializer, or
/// 4. if a compile-time error prevented us from generating code for an
/// initializer. This avoids cascading errors.
bool needsImplicitSuperInitializer;
Scope? formalParameterScope;
/// This is set to true when we start parsing an initializer. We use this to
/// find the correct scope for initializers like in this example:
///
/// class C {
/// final x;
/// C(x) : x = x;
/// }
///
/// When parsing this initializer `x = x`, `x` must be resolved in two
/// different scopes. The first `x` must be resolved in the class' scope, the
/// second in the formal parameter scope.
bool inInitializerLeftHandSide = false;
/// This is set to true when we are parsing constructor initializers.
bool inConstructorInitializer = false;
/// Set to `true` when we are parsing a field initializer either directly
/// or within an initializer list.
///
/// For instance in `<init>` in
///
/// var foo = <init>;
/// class Class {
/// var bar = <init>;
/// Class() : <init>;
/// }
///
/// This is used to determine whether instance properties are available.
bool inFieldInitializer = false;
/// `true` if we are directly in a field initializer of a late field.
///
/// For instance in `<init>` in
///
/// late var foo = <init>;
/// class Class {
/// late var bar = <init>;
/// Class() : bar = 42;
/// }
///
bool inLateFieldInitializer = false;
/// `true` if we are directly in the initializer of a late local.
///
/// For instance in `<init>` in
///
/// method() {
/// late var foo = <init>;
/// }
/// class Class {
/// method() {
/// late var bar = <init>;
/// }
/// }
///
bool get inLateLocalInitializer => _localInitializerState.head;
Link<bool> _isOrAsOperatorTypeState = const Link<bool>().prepend(false);
bool get inIsOrAsOperatorType => _isOrAsOperatorTypeState.head;
Link<bool> _localInitializerState = const Link<bool>().prepend(false);
List<Initializer>? _initializers;
bool inCatchClause = false;
bool inCatchBlock = false;
int functionNestingLevel = 0;
// Set when a spread element is encountered in a collection so the collection
// needs to be desugared after type inference.
bool transformCollections = false;
// Set by type inference when a set literal is encountered that needs to be
// transformed because the backend target does not support set literals.
bool transformSetLiterals = false;
Statement? problemInLoopOrSwitch;
Scope? switchScope;
late _BodyBuilderCloner _cloner = new _BodyBuilderCloner(this);
@override
ConstantContext constantContext = ConstantContext.none;
DartType? currentLocalVariableType;
// Using non-null value to initialize this field based on performance advice
// from VM engineers. TODO(ahe): Does this still apply?
int currentLocalVariableModifiers = -1;
/// If non-null, records instance fields which have already been initialized
/// and where that was.
Map<String, int>? initializedFields;
/// List of built redirecting factory invocations. The targets of the
/// invocations are to be resolved in a separate step.
final List<FactoryConstructorInvocation> redirectingFactoryInvocations = [];
/// List of redirecting factory invocations delayed for resolution.
///
/// A resolution of a redirecting factory invocation can be delayed because
/// the inference in the declaration of the redirecting factory isn't done
/// yet.
final List<FactoryConstructorInvocation>
delayedRedirectingFactoryInvocations = [];
/// List of built type aliased generative constructor invocations that
/// require unaliasing.
final List<TypeAliasedConstructorInvocation>
typeAliasedConstructorInvocations = [];
/// List of built type aliased factory constructor invocations that require
/// unaliasing.
final List<TypeAliasedFactoryInvocation> typeAliasedFactoryInvocations = [];
/// List of type aliased factory invocations delayed for resolution.
///
/// A resolution of a type aliased factory invocation can be delayed because
/// the inference in the declaration of the target isn't done yet.
final List<TypeAliasedFactoryInvocation>
delayedTypeAliasedFactoryInvocations = [];
/// Variables with metadata. Their types need to be inferred late, for
/// example, in [finishFunction].
List<VariableDeclaration>? variablesWithMetadata;
/// More than one variable declared in a single statement that has metadata.
/// Their types need to be inferred late, for example, in [finishFunction].
List<List<VariableDeclaration>>? multiVariablesWithMetadata;
/// If the current member is an instance member in an extension declaration,
/// [extensionThis] holds the synthetically add parameter holding the value
/// for `this`.
final VariableDeclaration? extensionThis;
final List<TypeParameter>? extensionTypeParameters;
Scope scope;
JumpTarget? breakTarget;
JumpTarget? continueTarget;
BodyBuilder(
{required this.libraryBuilder,
required this.member,
required this.enclosingScope,
this.formalParameterScope,
required this.hierarchy,
required this.coreTypes,
this.declarationBuilder,
required this.isDeclarationInstanceMember,
this.extensionThis,
this.extensionTypeParameters,
required this.uri,
required this.typeInferrer})
: forest = const Forest(),
sourceClassBuilder = declarationBuilder is SourceClassBuilder
? declarationBuilder
: null,
enableNative = libraryBuilder.loader.target.backendTarget
.enableNative(libraryBuilder.importUri),
stringExpectedAfterNative = libraryBuilder
.loader.target.backendTarget.nativeExtensionExpectsString,
ignoreMainInGetMainClosure =
libraryBuilder.importUri.isScheme('dart') &&
(libraryBuilder.importUri.path == "_builtin" ||
libraryBuilder.importUri.path == "ui"),
needsImplicitSuperInitializer =
declarationBuilder is SourceClassBuilder &&
coreTypes.objectClass != declarationBuilder.cls,
benchmarker = libraryBuilder.loader.target.benchmarker,
this.scope = enclosingScope {
formalParameterScope?.forEach((String name, Builder builder) {
if (builder is VariableBuilder) {
typeInferrer.assignedVariables.declare(builder.variable!);
}
});
}
BodyBuilder.withParents(FieldBuilder field, SourceLibraryBuilder part,
DeclarationBuilder? declarationBuilder, TypeInferrer typeInferrer)
: this(
libraryBuilder: part,
member: field,
enclosingScope: declarationBuilder?.scope ?? field.library.scope,
formalParameterScope: null,
hierarchy: part.loader.hierarchy,
coreTypes: part.loader.coreTypes,
declarationBuilder: declarationBuilder,
isDeclarationInstanceMember: field.isDeclarationInstanceMember,
extensionThis: null,
uri: field.fileUri!,
typeInferrer: typeInferrer);
BodyBuilder.forField(FieldBuilder field, TypeInferrer typeInferrer)
: this.withParents(
field,
field.parent is DeclarationBuilder
? field.parent!.parent as SourceLibraryBuilder
: field.parent as SourceLibraryBuilder,
field.parent is DeclarationBuilder
? field.parent as DeclarationBuilder
: null,
typeInferrer);
BodyBuilder.forOutlineExpression(
SourceLibraryBuilder library,
DeclarationBuilder? declarationBuilder,
ModifierBuilder member,
Scope scope,
Uri fileUri,
{Scope? formalParameterScope})
: this(
libraryBuilder: library,
member: member,
enclosingScope: scope,
formalParameterScope: formalParameterScope,
hierarchy: library.loader.hierarchy,
coreTypes: library.loader.coreTypes,
declarationBuilder: declarationBuilder,
isDeclarationInstanceMember: member.isDeclarationInstanceMember,
extensionThis: null,
uri: fileUri,
typeInferrer: library.loader.typeInferenceEngine
.createLocalTypeInferrer(
fileUri, declarationBuilder?.thisType, library, null));
JumpTarget createBreakTarget(int charOffset) {
return createJumpTarget(JumpTargetKind.Break, charOffset);
}
JumpTarget createContinueTarget(int charOffset) {
return createJumpTarget(JumpTargetKind.Continue, charOffset);
}
JumpTarget createGotoTarget(int charOffset) {
return createJumpTarget(JumpTargetKind.Goto, charOffset);
}
void enterLocalScope(String debugName, [Scope? newScope]) {
push(scope);
scope = newScope ?? scope.createNestedScope(debugName);
assert(checkState(null, [
ValueKinds.Scope,
]));
}
@override
void exitLocalScope() {
assert(checkState(null, [
ValueKinds.Scope,
]));
scope = pop() as Scope;
// ignore: unnecessary_null_comparison
assert(scope != null);
}
void enterBreakTarget(int charOffset, [JumpTarget? target]) {
push(breakTarget ?? NullValue.BreakTarget);
breakTarget = target ?? createBreakTarget(charOffset);
}
void enterContinueTarget(int charOffset, [JumpTarget? target]) {
push(continueTarget ?? NullValue.ContinueTarget);
continueTarget = target ?? createContinueTarget(charOffset);
}
JumpTarget? exitBreakTarget() {
JumpTarget? current = breakTarget;
breakTarget = pop() as JumpTarget?;
return current;
}
JumpTarget? exitContinueTarget() {
JumpTarget? current = continueTarget;
continueTarget = pop() as JumpTarget?;
return current;
}
@override
void beginBlockFunctionBody(Token begin) {
debugEvent("beginBlockFunctionBody");
enterLocalScope("block function body");
}
@override
void beginForStatement(Token token) {
debugEvent("beginForStatement");
enterLoop(token.charOffset);
enterLocalScope("for statement");
}
@override
void beginForControlFlow(Token? awaitToken, Token forToken) {
debugEvent("beginForControlFlow");
enterLocalScope("for in a collection");
}
@override
void beginDoWhileStatementBody(Token token) {
debugEvent("beginDoWhileStatementBody");
enterLocalScope("do-while statement body");
}
@override
void endDoWhileStatementBody(Token token) {
debugEvent("endDoWhileStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginWhileStatementBody(Token token) {
debugEvent("beginWhileStatementBody");
enterLocalScope("while statement body");
}
@override
void endWhileStatementBody(Token token) {
debugEvent("endWhileStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginForStatementBody(Token token) {
debugEvent("beginForStatementBody");
enterLocalScope("for statement body");
}
@override
void endForStatementBody(Token token) {
debugEvent("endForStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginForInBody(Token token) {
debugEvent("beginForInBody");
enterLocalScope("for-in body");
}
@override
void endForInBody(Token token) {
debugEvent("endForInBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginElseStatement(Token token) {
debugEvent("beginElseStatement");
enterLocalScope("else");
}
@override
void endElseStatement(Token token) {
debugEvent("endElseStatement");
Object? body = pop();
exitLocalScope();
push(body);
}
bool get inConstructor {
return functionNestingLevel == 0 && member is ConstructorBuilder;
}
@override
bool get isDeclarationInstanceContext {
return isDeclarationInstanceMember || member is ConstructorBuilder;
}
@override
InstanceTypeVariableAccessState get instanceTypeVariableAccessState {
if (member.isExtensionMember && member.isField && !member.isExternal) {
return InstanceTypeVariableAccessState.Invalid;
} else if (isDeclarationInstanceContext || member is DeclarationBuilder) {
return InstanceTypeVariableAccessState.Allowed;
} else {
return InstanceTypeVariableAccessState.Disallowed;
}
}
@override
TypeEnvironment get typeEnvironment => typeInferrer.typeSchemaEnvironment;
DartType get implicitTypeArgument => const ImplicitTypeArgument();
@override
bool get enableExtensionTypesInLibrary {
return libraryBuilder.enableExtensionTypesInLibrary;
}
@override
bool get enableConstFunctionsInLibrary {
return libraryBuilder.enableConstFunctionsInLibrary;
}
@override
bool get enableConstructorTearOffsInLibrary {
return libraryBuilder.enableConstructorTearOffsInLibrary;
}
@override
bool get enableNamedArgumentsAnywhereInLibrary {
return libraryBuilder.enableNamedArgumentsAnywhereInLibrary;
}
void _enterLocalState({bool inLateLocalInitializer: false}) {
_localInitializerState =
_localInitializerState.prepend(inLateLocalInitializer);
}
void _exitLocalState() {
_localInitializerState = _localInitializerState.tail!;
}
@override
void registerVariableAssignment(VariableDeclaration variable) {
typeInferrer.assignedVariables.write(variable);
}
@override
VariableDeclarationImpl createVariableDeclarationForValue(
Expression expression) {
VariableDeclarationImpl variable =
forest.createVariableDeclarationForValue(expression);
typeInferrer.assignedVariables.declare(variable);
return variable;
}
@override
void push(Object? node) {
if (node is DartType) {
unhandled("DartType", "push", -1, uri);
}
inInitializerLeftHandSide = false;
super.push(node);
}
Expression popForValue() => toValue(pop());
Expression popForEffect() => toEffect(pop());
Expression? popForValueIfNotNull(Object? value) {
return value == null ? null : popForValue();
}
@override
Expression toValue(Object? node) {
if (node is Generator) {
return node.buildSimpleRead();
} else if (node is Expression) {
return node;
} else if (node is SuperInitializer) {
return buildProblem(
fasta.messageSuperAsExpression, node.fileOffset, noLength);
} else if (node is ProblemBuilder) {
return buildProblem(node.message, node.charOffset, noLength);
} else {
return unhandled("${node.runtimeType}", "toValue", -1, uri);
}
}
Expression toEffect(Object? node) {
if (node is Generator) return node.buildForEffect();
return toValue(node);
}
List<Expression> popListForValue(int n) {
List<Expression> list =
new List<Expression>.filled(n, dummyExpression, growable: true);
for (int i = n - 1; i >= 0; i--) {
list[i] = popForValue();
}
return list;
}
List<Expression> popListForEffect(int n) {
List<Expression> list =
new List<Expression>.filled(n, dummyExpression, growable: true);
for (int i = n - 1; i >= 0; i--) {
list[i] = popForEffect();
}
return list;
}
Statement popBlock(int count, Token openBrace, Token? closeBrace) {
return forest.createBlock(
offsetForToken(openBrace),
offsetForToken(closeBrace),
const GrowableList<Statement>()
.popNonNullable(stack, count, dummyStatement) ??
<Statement>[]);
}
Statement? popStatementIfNotNull(Object? value) {
return value == null ? null : popStatement();
}
Statement popStatement() => forest.wrapVariables(pop() as Statement);
Statement? popNullableStatement() {
Statement? statement = pop(NullValue.Block) as Statement?;
if (statement != null) {
statement = forest.wrapVariables(statement);
}
return statement;
}
void enterSwitchScope() {
push(switchScope ?? NullValue.SwitchScope);
switchScope = scope;
}
void exitSwitchScope() {
Scope? outerSwitchScope = pop() as Scope?;
if (switchScope!.unclaimedForwardDeclarations != null) {
switchScope!.unclaimedForwardDeclarations!
.forEach((String name, JumpTarget declaration) {
if (outerSwitchScope == null) {
for (Statement statement in declaration.users) {
statement.parent!.replaceChild(
statement,
wrapInProblemStatement(statement,
fasta.templateLabelNotFound.withArguments(name)));
}
} else {
outerSwitchScope.forwardDeclareLabel(name, declaration);
}
});
}
switchScope = outerSwitchScope;
}
void wrapVariableInitializerInError(
VariableDeclaration variable,
Template<Message Function(String name)> template,
List<LocatedMessage> context) {
String name = variable.name!;
int offset = variable.fileOffset;
Message message = template.withArguments(name);
if (variable.initializer == null) {
variable.initializer =
buildProblem(message, offset, name.length, context: context)
..parent = variable;
} else {
variable.initializer = wrapInLocatedProblem(
variable.initializer!, message.withLocation(uri, offset, name.length),
context: context)
..parent = variable;
}
}
void declareVariable(VariableDeclaration variable, Scope scope) {
String name = variable.name!;
Builder? existing = scope.lookupLocalMember(name, setter: false);
if (existing != null) {
// This reports an error for duplicated declarations in the same scope:
// `{ var x; var x; }`
wrapVariableInitializerInError(
variable, fasta.templateDuplicatedDeclaration, <LocatedMessage>[
fasta.templateDuplicatedDeclarationCause
.withArguments(name)
.withLocation(uri, existing.charOffset, name.length)
]);
return;
}
LocatedMessage? context = scope.declare(
variable.name!, new VariableBuilderImpl(variable, member, uri), uri);
if (context != null) {
// This case is different from the above error. In this case, the problem
// is using `x` before it's declared: `{ var x; { print(x); var x;
// }}`. In this case, we want two errors, the `x` in `print(x)` and the
// second (or innermost declaration) of `x`.
wrapVariableInitializerInError(
variable,
fasta.templateDuplicatedNamePreviouslyUsed,
<LocatedMessage>[context]);
}
}
JumpTarget createJumpTarget(JumpTargetKind kind, int charOffset) {
return new JumpTarget(
kind, functionNestingLevel, member as MemberBuilder, charOffset);
}
void inferAnnotations(TreeNode? parent, List<Expression>? annotations) {
if (annotations != null) {
typeInferrer.inferMetadata(this, parent, annotations);
libraryBuilder.loader.transformListPostInference(annotations,
transformSetLiterals, transformCollections, libraryBuilder.library);
}
}
@override
void beginMetadata(Token token) {
debugEvent("beginMetadata");
super.push(constantContext);
constantContext = ConstantContext.inferred;
assert(checkState(token, [ValueKinds.ConstantContext]));
}
@override
void endMetadata(Token beginToken, Token? periodBeforeName, Token endToken) {
assert(checkState(beginToken, [
/*arguments*/ ValueKinds.ArgumentsOrNull,
/*suffix*/ if (periodBeforeName != null)
unionOfKinds([ValueKinds.Identifier, ValueKinds.ParserRecovery]),
/*type arguments*/ ValueKinds.TypeArgumentsOrNull,
/*type*/ unionOfKinds([
ValueKinds.Generator,
ValueKinds.QualifiedName,
ValueKinds.ProblemBuilder,
ValueKinds.ParserRecovery
])
]));
debugEvent("Metadata");
Arguments? arguments = pop() as Arguments?;
pushQualifiedReference(
beginToken.next!, periodBeforeName, ConstructorReferenceContext.Const);
assert(checkState(beginToken, [
/*constructor name identifier*/ ValueKinds.IdentifierOrNull,
/*constructor name*/ ValueKinds.Name,
/*type arguments*/ ValueKinds.TypeArgumentsOrNull,
/*class*/ unionOfKinds([
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.ParserRecovery
]),
]));
if (arguments != null) {
push(arguments);
_buildConstructorReferenceInvocation(
beginToken.next!, beginToken.offset, Constness.explicitConst,
inMetadata: true, inImplicitCreationContext: false);
push(popForValue());
} else {
pop(); // Name last identifier
String? name = pop() as String?;
pop(); // Type arguments (ignored, already reported by parser).
Object? expression = pop();
if (expression is Identifier) {
Identifier identifier = expression;
expression = new UnresolvedNameGenerator(this, identifier.token,
new Name(identifier.name, libraryBuilder.nameOrigin),
unresolvedReadKind: UnresolvedKind.Unknown);
}
if (name?.isNotEmpty ?? false) {
Token period = periodBeforeName ?? beginToken.next!.next!;
Generator generator = expression as Generator;
expression = generator.buildSelectorAccess(
new PropertySelector(
this, period.next!, new Name(name!, libraryBuilder.nameOrigin)),
period.next!.offset,
false);
}
ConstantContext savedConstantContext = pop() as ConstantContext;
if (expression is! StaticAccessGenerator &&
expression is! VariableUseGenerator &&
// TODO(johnniwinther): Stop using the type of the generator here.
// Ask a property instead.
(expression is! ReadOnlyAccessGenerator ||
expression is TypeUseGenerator ||
expression is ParenthesizedExpressionGenerator)) {
Expression value = toValue(expression);
push(wrapInProblem(value, fasta.messageExpressionNotMetadata,
value.fileOffset, noLength));
} else {
push(toValue(expression));
}
constantContext = savedConstantContext;
}
assert(checkState(beginToken, [ValueKinds.Expression]));
}
@override
void endMetadataStar(int count) {
assert(checkState(null, repeatedKinds(ValueKinds.Expression, count)));
debugEvent("MetadataStar");
if (count == 0) {
push(NullValue.Metadata);
} else {
push(const GrowableList<Expression>()
.popNonNullable(stack, count, dummyExpression) ??
NullValue.Metadata /* Ignore parser recovery */);
}
assert(checkState(null, [ValueKinds.AnnotationListOrNull]));
}
@override
void endTopLevelFields(
Token? externalToken,
Token? staticToken,
Token? covariantToken,
Token? lateToken,
Token? varFinalOrConst,
int count,
Token beginToken,
Token endToken) {
debugEvent("TopLevelFields");
if (!libraryBuilder.isNonNullableByDefault) {
reportNonNullableModifierError(lateToken);
if (externalToken != null) {
handleRecoverableError(
fasta.messageExternalField, externalToken, externalToken);
}
}
push(count);
assert(checkState(beginToken, [ValueKinds.Integer]));
}
@override
void endClassFields(
Token? abstractToken,
Token? augmentToken,
Token? externalToken,
Token? staticToken,
Token? covariantToken,
Token? lateToken,
Token? varFinalOrConst,
int count,
Token beginToken,
Token endToken) {
debugEvent("Fields");
if (!libraryBuilder.isNonNullableByDefault) {
reportNonNullableModifierError(lateToken);
if (abstractToken != null) {
handleRecoverableError(
fasta.messageAbstractClassMember, abstractToken, abstractToken);
}
if (externalToken != null) {
handleRecoverableError(
fasta.messageExternalField, externalToken, externalToken);
}
}
push(count);
assert(checkState(beginToken, [ValueKinds.Integer]));
}
void finishFields() {
debugEvent("finishFields");
assert(checkState(null, [/*field count*/ ValueKinds.Integer]));
int count = pop() as int;
List<SourceFieldBuilder> fields = [];
for (int i = 0; i < count; i++) {
assert(checkState(null, [
ValueKinds.FieldInitializerOrNull,
ValueKinds.Identifier,
]));
Expression? initializer = pop() as Expression?;
Identifier identifier = pop() as Identifier;
String name = identifier.name;
Builder declaration;
if (declarationBuilder != null) {
declaration =
declarationBuilder!.lookupLocalMember(name, required: true)!;
} else {
declaration = libraryBuilder.lookupLocalMember(name, required: true)!;
}
SourceFieldBuilder fieldBuilder;
if (declaration.isField && declaration.next == null) {
fieldBuilder = declaration as SourceFieldBuilder;
} else {
continue;
}
fields.add(fieldBuilder);
if (initializer != null) {
if (fieldBuilder.isDuplicate) {
// Duplicate definition. The field might not be the correct one,
// so we skip inference of the initializer.
// Error reporting and recovery is handled elsewhere.
} else if (fieldBuilder.hasBodyBeenBuilt) {
// The initializer was already compiled (e.g., if it appear in the
// outline, like constant field initializers) so we do not need to
// perform type inference or transformations.
// If the body is already built and it's a type aliased constructor or
// factory invocation, they shouldn't be checked or resolved the
// second time, so they are removed from the corresponding lists.
if (initializer is TypeAliasedConstructorInvocation) {
typeAliasedConstructorInvocations.remove(initializer);
}
if (initializer is TypeAliasedFactoryInvocation) {
typeAliasedFactoryInvocations.remove(initializer);
}
} else {
initializer = typeInferrer
.inferFieldInitializer(this, fieldBuilder.builtType, initializer)
.expression;
if (transformCollections || transformSetLiterals) {
// Wrap the initializer in a temporary parent expression; the
// transformations need a parent relation.
Not wrapper = new Not(initializer);
libraryBuilder.loader.transformPostInference(
wrapper,
transformSetLiterals,
transformCollections,
libraryBuilder.library);
initializer = wrapper.operand;
}
fieldBuilder.buildBody(coreTypes, initializer);
}
} else if (!fieldBuilder.hasBodyBeenBuilt) {
fieldBuilder.buildBody(coreTypes, null);
}
}
assert(checkState(
null, [ValueKinds.TypeOrNull, ValueKinds.AnnotationListOrNull]));
{
// TODO(ahe): The type we compute here may be different from what is
// computed in the outline phase. We should make sure that the outline
// phase computes the same type. See
// pkg/front_end/testcases/regress/issue_32200.dart for an example where
// not calling [buildDartType] leads to a missing compile-time
// error. Also, notice that the type of the problematic field isn't
// `invalid-type`.
TypeBuilder? type = pop() as TypeBuilder?;
if (type != null) {
buildDartType(type, allowPotentiallyConstantType: false);
}
}
pop(); // Annotations.
performBacklogComputations();
assert(stack.length == 0);
}
/// Perform delayed computations that were put on back log during body
/// building.
///
/// Back logged computations include resolution of redirecting factory
/// invocations and checking of typedef types.
void performBacklogComputations(
[List<DelayedActionPerformer>? delayedActionPerformers]) {
_finishVariableMetadata();
_unaliasTypeAliasedConstructorInvocations();
_unaliasTypeAliasedFactoryInvocations(typeAliasedFactoryInvocations);
_resolveRedirectingFactoryTargets(redirectingFactoryInvocations);
libraryBuilder.checkUncheckedTypedefTypes(typeEnvironment);
if (hasDelayedActions) {
assert(
delayedActionPerformers != null,
"Body builder has delayed actions that cannot be performed: "
"$delayedRedirectingFactoryInvocations");
delayedActionPerformers?.add(this);
}
}
void finishRedirectingFactoryBody() {
performBacklogComputations();
}
@override
void endMember() {
debugEvent("Member");
}
@override
void endBlockFunctionBody(int count, Token? openBrace, Token closeBrace) {
debugEvent("BlockFunctionBody");
if (openBrace == null) {
assert(count == 0);
push(NullValue.Block);
} else {
Statement block = popBlock(count, openBrace, closeBrace);
exitLocalScope();
push(block);
}
assert(checkState(closeBrace, [ValueKinds.StatementOrNull]));
}
void prepareInitializers() {
SourceFunctionBuilder member = this.member as SourceFunctionBuilder;
scope = member.computeFormalParameterInitializerScope(scope);
if (member is DeclaredSourceConstructorBuilder) {
member.prepareInitializers();
if (member.formals != null) {
for (FormalParameterBuilder formal in member.formals!) {
if (formal.isInitializingFormal) {
List<Initializer> initializers;
if (member.isExternal) {
initializers = <Initializer>[
buildInvalidInitializer(
buildProblem(
fasta.messageExternalConstructorWithFieldInitializers,
formal.charOffset,
formal.name.length),
formal.charOffset)
];
} else {
initializers = buildFieldInitializer(
formal.name,
formal.charOffset,
formal.charOffset,
new VariableGet(formal.variable!),
formal: formal);
}
for (Initializer initializer in initializers) {
member.addInitializer(initializer, this, inferenceResult: null);
}
}
}
}
}
}
@override
void handleNoInitializers() {
debugEvent("NoInitializers");
if (functionNestingLevel == 0) {
prepareInitializers();
scope = formalParameterScope ?? new Scope.immutable();
}
}
@override
void beginInitializers(Token token) {
debugEvent("beginInitializers");
if (functionNestingLevel == 0) {
prepareInitializers();
}
inConstructorInitializer = true;
}
@override
void endInitializers(int count, Token beginToken, Token endToken) {
debugEvent("Initializers");
if (functionNestingLevel == 0) {
scope = formalParameterScope ?? new Scope.immutable();
}
inConstructorInitializer = false;
}
@override
void beginInitializer(Token token) {
debugEvent("beginInitializer");
inInitializerLeftHandSide = true;
inFieldInitializer = true;
}
@override
void endInitializer(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Initializer,
ValueKinds.Generator,
ValueKinds.Expression,
])
]));
debugEvent("endInitializer");
inFieldInitializer = false;
assert(!inInitializerLeftHandSide);
Object? node = pop();
List<Initializer> initializers;
final ModifierBuilder member = this.member;
if (!(member is ConstructorBuilder && !member.isExternal)) {
// An error has been reported by the parser.
initializers = <Initializer>[];
} else if (node is Initializer) {
initializers = <Initializer>[node];
} else if (node is Generator) {
initializers = node.buildFieldInitializer(initializedFields);
} else if (node is ConstructorInvocation) {
initializers = <Initializer>[
buildSuperInitializer(
false, node.target, node.arguments, token.charOffset)
];
} else {
Expression value = toValue(node);
if (!forest.isThrow(node)) {
value = wrapInProblem(value, fasta.messageExpectedAnInitializer,
value.fileOffset, noLength);
}
initializers = <Initializer>[
// TODO(johnniwinther): This should probably be [value] instead of
// [node].
buildInvalidInitializer(node as Expression, token.charOffset)
];
}
_initializers ??= <Initializer>[];
_initializers!.addAll(initializers);
}
DartType _computeReturnTypeContext(MemberBuilder member) {
if (member is SourceProcedureBuilder) {
final bool isReturnTypeUndeclared = member.returnType == null &&
member.function.returnType is DynamicType;
return isReturnTypeUndeclared && libraryBuilder.isNonNullableByDefault
? const UnknownType()
: member.function.returnType;
} else if (member is SourceFactoryBuilder) {
return member.function.returnType;
} else {
assert(member is ConstructorBuilder);
return const DynamicType();
}
}
void finishFunction(
FormalParameters? formals, AsyncMarker asyncModifier, Statement? body) {
debugEvent("finishFunction");
typeInferrer.assignedVariables.finish();
final SourceFunctionBuilder builder = member as SourceFunctionBuilder;
if (extensionThis != null) {
typeInferrer.flowAnalysis.declare(extensionThis!, true);
}
if (formals?.parameters != null) {
for (int i = 0; i < formals!.parameters!.length; i++) {
FormalParameterBuilder parameter = formals.parameters![i];
typeInferrer.flowAnalysis.declare(parameter.variable!, true);
}
for (int i = 0; i < formals.parameters!.length; i++) {
FormalParameterBuilder parameter = formals.parameters![i];
Expression? initializer = parameter.variable!.initializer;
if (!parameter.isSuperInitializingFormal &&
(parameter.isOptional || initializer != null)) {
if (!parameter.initializerWasInferred) {
parameter.initializerWasInferred = true;
if (parameter.isOptional) {
initializer ??= forest.createNullLiteral(
// TODO(ahe): Should store: originParameter.fileOffset
// https://github.com/dart-lang/sdk/issues/32289
noLocation);
}
VariableDeclaration originParameter = builder.getFormalParameter(i);
initializer = typeInferrer.inferParameterInitializer(
this,
initializer!,
originParameter.type,
parameter.hasDeclaredInitializer);
originParameter.initializer = initializer..parent = originParameter;
libraryBuilder.loader.transformPostInference(
originParameter,
transformSetLiterals,
transformCollections,
libraryBuilder.library);
}
VariableDeclaration? tearOffParameter =
builder.getTearOffParameter(i);
if (tearOffParameter != null) {
Expression tearOffInitializer =
_cloner.cloneInContext(initializer!);
tearOffParameter.initializer = tearOffInitializer
..parent = tearOffParameter;
libraryBuilder.loader.transformPostInference(
tearOffParameter,
transformSetLiterals,
transformCollections,
libraryBuilder.library);
}
}
}
}
if (builder is DeclaredSourceConstructorBuilder) {
finishConstructor(builder, asyncModifier, body);
} else if (builder is SourceProcedureBuilder) {
builder.asyncModifier = asyncModifier;
} else if (builder is SourceFactoryBuilder) {
builder.asyncModifier = asyncModifier;
} else {
unhandled("${builder.runtimeType}", "finishFunction", builder.charOffset,
builder.fileUri);
}
InferredFunctionBody? inferredFunctionBody;
if (body != null) {
inferredFunctionBody = typeInferrer.inferFunctionBody(
this,
builder.charOffset,
_computeReturnTypeContext(builder),
asyncModifier,
body);
body = inferredFunctionBody.body;
builder.function.futureValueType = inferredFunctionBody.futureValueType;
libraryBuilder.loader.transformPostInference(body, transformSetLiterals,
transformCollections, libraryBuilder.library);
}
if (builder.returnType != null) {
checkAsyncReturnType(asyncModifier, builder.function.returnType,
builder.charOffset, builder.name.length);
}
if (builder.kind == ProcedureKind.Setter) {
if (formals?.parameters == null ||
formals!.parameters!.length != 1 ||
formals.parameters!.single.isOptional) {
int charOffset = formals?.charOffset ??
body?.fileOffset ??
builder.member.fileOffset;
if (body == null) {
body = new EmptyStatement()..fileOffset = charOffset;
}
if (builder.formals != null) {
// Illegal parameters were removed by the function builder.
// Add them as local variable to put them in scope of the body.
List<Statement> statements = <Statement>[];
for (FormalParameterBuilder parameter in builder.formals!) {
statements.add(parameter.variable!);
}
statements.add(body);
body = forest.createBlock(charOffset, noLocation, statements);
}
body = forest.createBlock(charOffset, noLocation, <Statement>[
forest.createExpressionStatement(
noLocation,
// This error is added after type inference is done, so we
// don't need to wrap errors in SyntheticExpressionJudgment.
buildProblem(fasta.messageSetterWithWrongNumberOfFormals,
charOffset, noLength)),
body,
]);
}
}
// No-such-method forwarders get their bodies injected during outline
// building, so we should skip them here.
bool isNoSuchMethodForwarder = (builder.function.parent is Procedure &&
(builder.function.parent as Procedure).isNoSuchMethodForwarder);
if (body != null) {
if (!builder.isExternal && !isNoSuchMethodForwarder) {
builder.body = body;
} else {
builder.body = new Block(<Statement>[
new ExpressionStatement(buildProblem(
fasta.messageExternalMethodWithBody, body.fileOffset, noLength))
..fileOffset = body.fileOffset,
body,
])
..fileOffset = body.fileOffset;
}
}
performBacklogComputations();
}
void checkAsyncReturnType(AsyncMarker asyncModifier, DartType returnType,
int charOffset, int length) {
// For async, async*, and sync* functions with declared return types, we
// need to determine whether those types are valid.
// We use the same trick in each case below. For example to decide whether
// Future<T> <: [returnType] for every T, we rely on Future<Bot> and
// transitivity of the subtyping relation because Future<Bot> <: Future<T>
// for every T.
// We use [problem == null] to signal success.
Message? problem;
switch (asyncModifier) {
case AsyncMarker.Async:
DartType futureBottomType = libraryBuilder.loader.futureOfBottom;
if (!typeEnvironment.isSubtypeOf(
futureBottomType, returnType, SubtypeCheckMode.withNullabilities)) {
problem = fasta.messageIllegalAsyncReturnType;
}
break;
case AsyncMarker.AsyncStar:
DartType streamBottomType = libraryBuilder.loader.streamOfBottom;
if (returnType is VoidType) {
problem = fasta.messageIllegalAsyncGeneratorVoidReturnType;
} else if (!typeEnvironment.isSubtypeOf(
streamBottomType, returnType, SubtypeCheckMode.withNullabilities)) {
problem = fasta.messageIllegalAsyncGeneratorReturnType;
}
break;
case AsyncMarker.SyncStar:
DartType iterableBottomType = libraryBuilder.loader.iterableOfBottom;
if (returnType is VoidType) {
problem = fasta.messageIllegalSyncGeneratorVoidReturnType;
} else if (!typeEnvironment.isSubtypeOf(iterableBottomType, returnType,
SubtypeCheckMode.withNullabilities)) {
problem = fasta.messageIllegalSyncGeneratorReturnType;
}
break;
case AsyncMarker.Sync:
break; // skip
case AsyncMarker.SyncYielding:
unexpected("async, async*, sync, or sync*", "$asyncModifier",
member.charOffset, uri);
}
if (problem != null) {
// TODO(hillerstrom): once types get annotated with location
// information, we can improve the quality of the error message by
// using the offset of [returnType] (and the length of its name).
addProblem(problem, charOffset, length);
}
}
/// Ensure that the containing library of the [member] has been loaded.
///
/// This is for instance important for lazy dill library builders where this
/// method has to be called to ensure that
/// a) The library has been fully loaded (and for instance any internal
/// transformation needed has been performed); and
/// b) The library is correctly marked as being used to allow for proper
/// 'dependency pruning'.
@override
void ensureLoaded(Member? member) {
if (member == null) return;
Library ensureLibraryLoaded = member.enclosingLibrary;
LibraryBuilder? builder = libraryBuilder.loader
.lookupLibraryBuilder(ensureLibraryLoaded.importUri) ??
libraryBuilder.loader.target.dillTarget.loader
.lookupLibraryBuilder(ensureLibraryLoaded.importUri);
if (builder is DillLibraryBuilder) {
builder.ensureLoaded();
}
}
/// Check if the containing library of the [member] has been loaded.
///
/// This is designed for use with asserts.
/// See [ensureLoaded] for a description of what 'loaded' means and the ideas
/// behind that.
@override
bool isLoaded(Member? member) {
if (member == null) return true;
Library ensureLibraryLoaded = member.enclosingLibrary;
LibraryBuilder? builder = libraryBuilder.loader
.lookupLibraryBuilder(ensureLibraryLoaded.importUri) ??
libraryBuilder.loader.target.dillTarget.loader
.lookupLibraryBuilder(ensureLibraryLoaded.importUri);
if (builder is DillLibraryBuilder) {
return builder.isBuiltAndMarked;
}
return true;
}
/// Return an [Expression] resolving the argument invocation.
///
/// The arguments specify the [StaticInvocation] whose `.target` is
/// [target], `.arguments` is [arguments], `.fileOffset` is [fileOffset],
/// and `.isConst` is [isConst].
/// Returns null if the invocation can't be resolved.
Expression? _resolveRedirectingFactoryTarget(
Procedure target, Arguments arguments, int fileOffset, bool isConst) {
Procedure initialTarget = target;
Expression replacementNode;
RedirectionTarget redirectionTarget =
getRedirectionTarget(initialTarget, this);
Member resolvedTarget = redirectionTarget.target;
if (redirectionTarget.typeArguments.any((type) => type is UnknownType)) {
return null;
}
RedirectingFactoryBody? redirectingFactoryBody =
getRedirectingFactoryBody(resolvedTarget);
if (redirectingFactoryBody != null) {
// If the redirection target is itself a redirecting factory, it means
// that it is unresolved.
assert(redirectingFactoryBody.isError);
String errorMessage = redirectingFactoryBody.errorMessage!;
replacementNode = new InvalidExpression(errorMessage)
..fileOffset = fileOffset;
} else {
Substitution substitution = Substitution.fromPairs(
initialTarget.function.typeParameters, arguments.types);
for (int i = 0; i < redirectionTarget.typeArguments.length; i++) {
DartType typeArgument =
substitution.substituteType(redirectionTarget.typeArguments[i]);
if (i < arguments.types.length) {
arguments.types[i] = typeArgument;
} else {
arguments.types.add(typeArgument);
}
}
arguments.types.length = redirectionTarget.typeArguments.length;
replacementNode = buildStaticInvocation(
resolvedTarget,
forest.createArguments(noLocation, arguments.positional,
types: arguments.types,
named: arguments.named,
hasExplicitTypeArguments: hasExplicitTypeArguments(arguments)),
constness: isConst ? Constness.explicitConst : Constness.explicitNew,
charOffset: fileOffset);
}
return replacementNode;
}
void _resolveRedirectingFactoryTargets(
List<FactoryConstructorInvocation> redirectingFactoryInvocations) {
List<FactoryConstructorInvocation> invocations =
redirectingFactoryInvocations.toList();
redirectingFactoryInvocations.clear();
for (FactoryConstructorInvocation invocation in invocations) {
// If the invocation was invalid, it or its parent has already been
// desugared into an exception throwing expression. There is nothing to
// resolve anymore. Note that in the case where the invocation's parent
// was invalid, type inference won't reach the invocation node and won't
// set its inferredType field. If type inference is disabled, reach to
// the outermost parent to check if the node is a dead code.
if (invocation.parent == null) continue;
// ignore: unnecessary_null_comparison
if (typeInferrer != null) {
if (!invocation.hasBeenInferred) {
continue;
}
} else {
TreeNode? parent = invocation.parent;
while (parent is! Component && parent != null) {
parent = parent.parent;
}
if (parent == null) continue;
}
Expression? replacement = _resolveRedirectingFactoryTarget(
invocation.target,
invocation.arguments,
invocation.fileOffset,
invocation.isConst);
if (replacement == null) {
delayedRedirectingFactoryInvocations.add(invocation);
} else {
invocation.replaceWith(replacement);
}
}
}
void _unaliasTypeAliasedConstructorInvocations() {
for (TypeAliasedConstructorInvocation invocation
in typeAliasedConstructorInvocations) {
if (!invocation.hasBeenInferred) {
assert(
isOrphaned(invocation), "Node $invocation has not been inferred.");
continue;
}
bool inferred = !hasExplicitTypeArguments(invocation.arguments);
DartType aliasedType = new TypedefType(
invocation.typeAliasBuilder.typedef,
Nullability.nonNullable,
invocation.arguments.types);
libraryBuilder.checkBoundsInType(
aliasedType, typeEnvironment, uri, invocation.fileOffset,
allowSuperBounded: false, inferred: inferred);
DartType unaliasedType = aliasedType.unalias;
List<DartType>? invocationTypeArguments = null;
if (unaliasedType is InterfaceType) {
invocationTypeArguments = unaliasedType.typeArguments;
}
Arguments invocationArguments = forest.createArguments(
noLocation, invocation.arguments.positional,
types: invocationTypeArguments, named: invocation.arguments.named);
invocation.replaceWith(new ConstructorInvocation(
invocation.target, invocationArguments,
isConst: invocation.isConst));
}
typeAliasedConstructorInvocations.clear();
}
void _unaliasTypeAliasedFactoryInvocations(
List<TypeAliasedFactoryInvocation> typeAliasedFactoryInvocations) {
List<TypeAliasedFactoryInvocation> invocations =
typeAliasedFactoryInvocations.toList();
typeAliasedFactoryInvocations.clear();
for (TypeAliasedFactoryInvocation invocation in invocations) {
if (!invocation.hasBeenInferred) {
assert(
isOrphaned(invocation), "Node $invocation has not been inferred.");
continue;
}
bool inferred = !hasExplicitTypeArguments(invocation.arguments);
DartType aliasedType = new TypedefType(
invocation.typeAliasBuilder.typedef,
Nullability.nonNullable,
invocation.arguments.types);
libraryBuilder.checkBoundsInType(
aliasedType, typeEnvironment, uri, invocation.fileOffset,
allowSuperBounded: false, inferred: inferred);
DartType unaliasedType = aliasedType.unalias;
List<DartType>? invocationTypeArguments = null;
if (unaliasedType is InterfaceType) {
invocationTypeArguments = unaliasedType.typeArguments;
}
Arguments invocationArguments = forest.createArguments(
noLocation, invocation.arguments.positional,
types: invocationTypeArguments,
named: invocation.arguments.named,
hasExplicitTypeArguments:
hasExplicitTypeArguments(invocation.arguments));
Expression? replacement = _resolveRedirectingFactoryTarget(
invocation.target,
invocationArguments,
invocation.fileOffset,
invocation.isConst);
if (replacement == null) {
delayedTypeAliasedFactoryInvocations.add(invocation);
} else {
invocation.replaceWith(replacement);
}
}
typeAliasedFactoryInvocations.clear();
}
/// Perform actions that were delayed
///
/// An action can be delayed, for instance, because it depends on some
/// calculations in another library. For example, a resolution of a
/// redirecting factory invocation depends on the type inference in the
/// redirecting factory.
@override
void performDelayedActions() {
if (delayedRedirectingFactoryInvocations.isNotEmpty) {
_resolveRedirectingFactoryTargets(delayedRedirectingFactoryInvocations);
if (delayedRedirectingFactoryInvocations.isNotEmpty) {
for (StaticInvocation invocation
in delayedRedirectingFactoryInvocations) {
internalProblem(
fasta.templateInternalProblemUnhandled.withArguments(
invocation.target.name.text, 'performDelayedActions'),
invocation.fileOffset,
uri);
}
}
}
if (delayedTypeAliasedFactoryInvocations.isNotEmpty) {
_unaliasTypeAliasedFactoryInvocations(
delayedTypeAliasedFactoryInvocations);
if (delayedTypeAliasedFactoryInvocations.isNotEmpty) {
for (StaticInvocation invocation
in delayedTypeAliasedFactoryInvocations) {
internalProblem(
fasta.templateInternalProblemUnhandled.withArguments(
invocation.target.name.text, 'performDelayedActions'),
invocation.fileOffset,
uri);
}
}
}
}
@override
bool get hasDelayedActions {
return delayedRedirectingFactoryInvocations.isNotEmpty ||
delayedTypeAliasedFactoryInvocations.isNotEmpty;
}
void _finishVariableMetadata() {
List<VariableDeclaration>? variablesWithMetadata =
this.variablesWithMetadata;
this.variablesWithMetadata = null;
List<List<VariableDeclaration>>? multiVariablesWithMetadata =
this.multiVariablesWithMetadata;
this.multiVariablesWithMetadata = null;
if (variablesWithMetadata != null) {
for (int i = 0; i < variablesWithMetadata.length; i++) {
inferAnnotations(
variablesWithMetadata[i], variablesWithMetadata[i].annotations);
}
}
if (multiVariablesWithMetadata != null) {
for (int i = 0; i < multiVariablesWithMetadata.length; i++) {
List<VariableDeclaration> variables = multiVariablesWithMetadata[i];
List<Expression> annotations = variables.first.annotations;
inferAnnotations(variables.first, annotations);
for (int i = 1; i < variables.length; i++) {
VariableDeclaration variable = variables[i];
for (int i = 0; i < annotations.length; i++) {
variable.addAnnotation(_cloner.cloneInContext(annotations[i]));
}
}
}
}
}
@override
List<Expression> finishMetadata(Annotatable? parent) {
assert(checkState(null, [ValueKinds.AnnotationList]));
List<Expression> expressions = pop() as List<Expression>;
inferAnnotations(parent, expressions);
// The invocation of [resolveRedirectingFactoryTargets] below may change the
// root nodes of the annotation expressions. We need to have a parent of
// the annotation nodes before the resolution is performed, to collect and
// return them later. If [parent] is not provided, [temporaryParent] is
// used.
ListLiteral? temporaryParent;
if (parent != null) {
for (Expression expression in expressions) {
parent.addAnnotation(expression);
}
} else {
temporaryParent = new ListLiteral(expressions);
}
performBacklogComputations();
return temporaryParent != null ? temporaryParent.expressions : expressions;
}
@override
Expression parseSingleExpression(
Parser parser, Token token, FunctionNode parameters) {
assert(redirectingFactoryInvocations.isEmpty);
int fileOffset = offsetForToken(token);
List<TypeVariableBuilder>? typeParameterBuilders;
for (TypeParameter typeParameter in parameters.typeParameters) {
typeParameterBuilders ??= <TypeVariableBuilder>[];
typeParameterBuilders.add(
new TypeVariableBuilder.fromKernel(typeParameter, libraryBuilder));
}
enterFunctionTypeScope(typeParameterBuilders);
List<FormalParameterBuilder>? formals =
parameters.positionalParameters.length == 0
? null
: new List<FormalParameterBuilder>.generate(
parameters.positionalParameters.length, (int i) {
VariableDeclaration formal = parameters.positionalParameters[i];
return new FormalParameterBuilder(null, 0, null, formal.name!,
libraryBuilder, formal.fileOffset,
fileUri: uri)
..variable = formal;
}, growable: false);
enterLocalScope(
'formalParameters',
new FormalParameters(formals, fileOffset, noLength, uri)
.computeFormalParameterScope(scope, member, this));
Token endToken =
parser.parseExpression(parser.syntheticPreviousToken(token));
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
])
]));
Expression expression = popForValue();
Token eof = endToken.next!;
if (!eof.isEof) {
expression = wrapInLocatedProblem(
expression,
fasta.messageExpectedOneExpression
.withLocation(uri, eof.charOffset, eof.length));
}
ReturnStatementImpl fakeReturn = new ReturnStatementImpl(true, expression);
if (formals != null) {
for (int i = 0; i < formals.length; i++) {
typeInferrer.flowAnalysis.declare(formals[i].variable!, true);
}
}
InferredFunctionBody inferredFunctionBody = typeInferrer.inferFunctionBody(
this, fileOffset, const DynamicType(), AsyncMarker.Sync, fakeReturn);
assert(
fakeReturn == inferredFunctionBody.body,
"Previously implicit assumption about inferFunctionBody "
"not returning anything different.");
performBacklogComputations();
libraryBuilder.loader.transformPostInference(fakeReturn,
transformSetLiterals, transformCollections, libraryBuilder.library);
return fakeReturn.expression!;
}
List<Initializer>? parseInitializers(Token token,
{bool doFinishConstructor = true}) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
if (!token.isEof) {
token = parser.parseInitializers(token);
checkEmpty(token.charOffset);
} else {
handleNoInitializers();
}
if (doFinishConstructor) {
finishConstructor(
member as DeclaredSourceConstructorBuilder, AsyncMarker.Sync, null);
}
return _initializers;
}
Expression parseFieldInitializer(Token token) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
Token endToken =
parser.parseExpression(parser.syntheticPreviousToken(token));
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
])
]));
Expression expression = popForValue();
checkEmpty(endToken.charOffset);
return expression;
}
Expression parseAnnotation(Token token) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
Token endToken = parser.parseMetadata(parser.syntheticPreviousToken(token));
assert(checkState(token, [ValueKinds.Expression]));
Expression annotation = pop() as Expression;
checkEmpty(endToken.charOffset);
return annotation;
}
ArgumentsImpl parseArguments(Token token) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe);
token = parser.parseArgumentsRest(token);
ArgumentsImpl arguments = pop() as ArgumentsImpl;
checkEmpty(token.charOffset);
return arguments;
}
void finishConstructor(DeclaredSourceConstructorBuilder builder,
AsyncMarker asyncModifier, Statement? body) {
/// Quotes below are from [Dart Programming Language Specification, 4th
/// Edition](
/// https://ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf).
assert(builder == member);
Constructor constructor = builder.actualConstructor;
List<FormalParameterBuilder>? formals = builder.formals;
if (formals != null) {
for (int i = 0; i < formals.length; i++) {
FormalParameterBuilder parameter = formals[i];
typeInferrer.flowAnalysis.declare(parameter.variable!, true);
}
}
List<Expression>? positionalSuperParametersAsArguments;
List<NamedExpression>? namedSuperParametersAsArguments;
if (formals != null) {
for (FormalParameterBuilder formal in formals) {
if (formal.isSuperInitializingFormal) {
if (formal.isNamed) {
(namedSuperParametersAsArguments ??= <NamedExpression>[]).add(
new NamedExpression(
formal.name,
new VariableGetImpl(formal.variable!,
forNullGuardedAccess: false)
..fileOffset = formal.charOffset)
..fileOffset = formal.charOffset);
} else {
(positionalSuperParametersAsArguments ??= <Expression>[]).add(
new VariableGetImpl(formal.variable!,
forNullGuardedAccess: false)
..fileOffset = formal.charOffset);
}
}
}
}
List<Initializer>? initializers = _initializers;
if (initializers != null && initializers.isNotEmpty) {
if (initializers.last is SuperInitializer) {
SuperInitializer superInitializer =
initializers.last as SuperInitializer;
if (builder.classBuilder.isEnum) {
initializers[initializers.length - 1] = buildInvalidInitializer(
buildProblem(fasta.messageEnumConstructorSuperInitializer,
superInitializer.fileOffset, noLength))
..parent = constructor;
} else if (libraryBuilder.enableSuperParametersInLibrary) {
Arguments arguments = superInitializer.arguments;
if (positionalSuperParametersAsArguments != null) {
if (arguments.positional.isNotEmpty) {
addProblem(fasta.messagePositionalSuperParametersAndArguments,
arguments.fileOffset, noLength,
context: <LocatedMessage>[
fasta.messageSuperInitializerParameter.withLocation(
uri,
(positionalSuperParametersAsArguments.first
as VariableGet)
.variable
.fileOffset,
noLength)
]);
} else {
arguments.positional.addAll(positionalSuperParametersAsArguments);
setParents(positionalSuperParametersAsArguments, arguments);
}
}
if (namedSuperParametersAsArguments != null) {
// TODO(cstefantsova): Report name conflicts.
arguments.named.addAll(namedSuperParametersAsArguments);
setParents(namedSuperParametersAsArguments, arguments);
}
}
} else if (initializers.last is RedirectingInitializer) {
RedirectingInitializer redirectingInitializer =
initializers.last as RedirectingInitializer;
if (sourceClassBuilder is SourceEnumBuilder &&
libraryBuilder.enableEnhancedEnumsInLibrary) {
ArgumentsImpl arguments =
redirectingInitializer.arguments as ArgumentsImpl;
List<Expression> enumSyntheticArguments = [
new VariableGetImpl(constructor.function.positionalParameters[0],
forNullGuardedAccess: false)
..parent = redirectingInitializer.arguments,
new VariableGetImpl(constructor.function.positionalParameters[1],
forNullGuardedAccess: false)
..parent = redirectingInitializer.arguments
];
arguments.positional.insertAll(0, enumSyntheticArguments);
arguments.argumentsOriginalOrder
?.insertAll(0, enumSyntheticArguments);
}
}
List<InitializerInferenceResult> inferenceResults =
new List<InitializerInferenceResult>.generate(
initializers.length,
(index) =>
typeInferrer.inferInitializer(this, initializers[index]),
growable: false);
if (!builder.isExternal) {
for (int i = 0; i < initializers.length; i++) {
builder.addInitializer(initializers[i], this,
inferenceResult: inferenceResults[i]);
}
}
}
if (asyncModifier != AsyncMarker.Sync) {
constructor.initializers.add(buildInvalidInitializer(buildProblem(
fasta.messageConstructorNotSync, body!.fileOffset, noLength)));
}
if (needsImplicitSuperInitializer) {
/// >If no superinitializer is provided, an implicit superinitializer
/// >of the form super() is added at the end of k’s initializer list,
/// >unless the enclosing class is class Object.
Constructor? superTarget = lookupConstructor(emptyName, isSuper: true);
Initializer initializer;
Arguments arguments;
List<Expression>? positionalArguments;
List<NamedExpression>? namedArguments;
if (libraryBuilder.enableSuperParametersInLibrary) {
positionalArguments = positionalSuperParametersAsArguments;
namedArguments = namedSuperParametersAsArguments;
}
if (sourceClassBuilder is SourceEnumBuilder) {
assert(constructor.function.positionalParameters.length >= 2 &&
constructor.function.positionalParameters[0].name == "index" &&
constructor.function.positionalParameters[1].name == "name");
(positionalArguments ??= <Expression>[]).insertAll(0, [
new VariableGet(constructor.function.positionalParameters[0]),
new VariableGet(constructor.function.positionalParameters[1])
]);
}
if (positionalArguments != null || namedArguments != null) {
arguments = forest.createArguments(
noLocation, positionalArguments ?? <Expression>[],
named: namedArguments);
} else {
arguments = forest.createArgumentsEmpty(noLocation);
}
if (superTarget == null ||
checkArgumentsForFunction(superTarget.function, arguments,
builder.charOffset, const <TypeParameter>[]) !=
null) {
String superclass =
sourceClassBuilder!.supertypeBuilder!.fullNameForErrors;
int length = constructor.name.text.length;
if (length == 0) {
length = (constructor.parent as Class).name.length;
}
initializer = buildInvalidInitializer(
buildProblem(
fasta.templateSuperclassHasNoDefaultConstructor
.withArguments(superclass),
builder.charOffset,
length),
builder.charOffset);
} else {
initializer = buildSuperInitializer(
true, superTarget, arguments, builder.charOffset);
}
constructor.initializers.add(initializer);
}
setParents(constructor.initializers, constructor);
libraryBuilder.loader.transformListPostInference(constructor.initializers,
transformSetLiterals, transformCollections, libraryBuilder.library);
if (body == null) {
/// >If a generative constructor c is not a redirecting constructor
/// >and no body is provided, then c implicitly has an empty body {}.
/// We use an empty statement instead.
constructor.function.body = new EmptyStatement()
..parent = constructor.function;
}
}
@override
void handleExpressionStatement(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
debugEvent("ExpressionStatement");
push(forest.createExpressionStatement(
offsetForToken(token), popForEffect()));
}
@override
void endArguments(int count, Token beginToken, Token endToken) {
debugEvent("Arguments");
List<Object?>? arguments = count == 0
? <Object>[]
: const FixedNullableList<Object>().pop(stack, count);
if (arguments == null) {
push(new ParserRecovery(beginToken.charOffset));
return;
}
List<Object?>? argumentsOriginalOrder;
if (libraryBuilder.enableNamedArgumentsAnywhereInLibrary) {
argumentsOriginalOrder = new List<Object?>.of(arguments);
}
int firstNamedArgumentIndex = arguments.length;
int positionalCount = 0;
bool hasNamedBeforePositional = false;
for (int i = 0; i < arguments.length; i++) {
Object? node = arguments[i];
if (node is NamedExpression) {
firstNamedArgumentIndex =
i < firstNamedArgumentIndex ? i : firstNamedArgumentIndex;
} else {
positionalCount++;
Expression argument = toValue(node);
arguments[i] = argument;
argumentsOriginalOrder?[i] = argument;
if (i > firstNamedArgumentIndex) {
hasNamedBeforePositional = true;
if (!libraryBuilder.enableNamedArgumentsAnywhereInLibrary) {
arguments[i] = new NamedExpression(
"#$i",
buildProblem(fasta.messageExpectedNamedArgument,
argument.fileOffset, noLength))
..fileOffset = beginToken.charOffset;
}
}
}
}
if (!hasNamedBeforePositional) {
argumentsOriginalOrder = null;
}
if (firstNamedArgumentIndex < arguments.length) {
List<Expression> positional;
List<NamedExpression> named;
if (libraryBuilder.enableNamedArgumentsAnywhereInLibrary) {
positional = new List<Expression>.filled(
positionalCount, dummyExpression,
growable: true);
named = new List<NamedExpression>.filled(
arguments.length - positionalCount, dummyNamedExpression,
growable: true);
int positionalIndex = 0;
int namedIndex = 0;
for (int i = 0; i < arguments.length; i++) {
if (arguments[i] is NamedExpression) {
named[namedIndex++] = arguments[i] as NamedExpression;
} else {
positional[positionalIndex++] = arguments[i] as Expression;
}
}
assert(
positionalIndex == positional.length && namedIndex == named.length);
} else {
// arguments have non-null Expression entries after the initial loop.
positional = new List<Expression>.from(
arguments.getRange(0, firstNamedArgumentIndex));
named = new List<NamedExpression>.from(
arguments.getRange(firstNamedArgumentIndex, arguments.length));
}
push(forest.createArguments(beginToken.offset, positional,
named: named, argumentsOriginalOrder: argumentsOriginalOrder));
} else {
// TODO(kmillikin): Find a way to avoid allocating a second list in the
// case where there were no named arguments, which is a common one.
// arguments have non-null Expression entries after the initial loop.
push(forest.createArguments(
beginToken.offset, new List<Expression>.from(arguments),
argumentsOriginalOrder: argumentsOriginalOrder));
}
assert(checkState(beginToken, [ValueKinds.Arguments]));
}
@override
void handleParenthesizedCondition(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
debugEvent("ParenthesizedCondition");
push(popForValue());
assert(checkState(token, [ValueKinds.Expression]));
}
@override
void handleParenthesizedExpression(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
debugEvent("ParenthesizedExpression");
Expression value = popForValue();
if (value is ShadowLargeIntLiteral) {
// We need to know that the expression was parenthesized because we will
// treat -n differently from -(n). If the expression occurs in a double
// context, -n is a double literal and -(n) is an application of unary- to
// an integer literal. And in any other context, '-' is part of the
// syntax of -n, i.e., -9223372036854775808 is OK and it is the minimum
// 64-bit integer, and '-' is an application of unary- in -(n), i.e.,
// -(9223372036854775808) is an error because the literal does not fit in
// 64-bits.
push(value..isParenthesized = true);
} else {
push(new ParenthesizedExpressionGenerator(this, token.endGroup!, value));
}
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
]),
]));
}
@override
void handleSend(Token beginToken, Token endToken) {
assert(checkState(beginToken, [
unionOfKinds([
ValueKinds.ArgumentsOrNull,
ValueKinds.ParserRecovery,
]),
ValueKinds.TypeArgumentsOrNull,
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Identifier,
ValueKinds.ParserRecovery,
ValueKinds.ProblemBuilder
])
]));
debugEvent("Send");
Object? arguments = pop();
List<TypeBuilder>? typeArguments = pop() as List<TypeBuilder>?;
Object receiver = pop()!;
// Delay adding [typeArguments] to [forest] for type aliases: They
// must be unaliased to the type arguments of the denoted type.
bool isInForest = arguments is Arguments &&
typeArguments != null &&
(receiver is! TypeUseGenerator ||
receiver.declaration is! TypeAliasBuilder);
if (isInForest) {
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(
arguments,
buildDartTypeArguments(typeArguments,
allowPotentiallyConstantType: false));
} else {
assert(typeArguments == null ||
(receiver is TypeUseGenerator &&
receiver.declaration is TypeAliasBuilder));
}
if (receiver is ParserRecovery || arguments is ParserRecovery) {
push(new ParserErrorGenerator(
this, beginToken, fasta.messageSyntheticToken));
} else if (receiver is Identifier) {
Name name = new Name(receiver.name, libraryBuilder.nameOrigin);
if (arguments == null) {
push(new PropertySelector(this, beginToken, name));
} else {
push(new InvocationSelector(
this, beginToken, name, typeArguments, arguments as Arguments,
isTypeArgumentsInForest: isInForest));
}
} else if (arguments == null) {
push(receiver);
} else {
push(finishSend(receiver, typeArguments, arguments as ArgumentsImpl,
beginToken.charOffset,
isTypeArgumentsInForest: isInForest));
}
assert(checkState(beginToken, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Initializer,
ValueKinds.ProblemBuilder,
ValueKinds.Selector,
])
]));
}
@override
Expression_Generator_Initializer finishSend(Object receiver,
List<TypeBuilder>? typeArguments, ArgumentsImpl arguments, int charOffset,
{bool isTypeArgumentsInForest = false}) {
if (receiver is Generator) {
return receiver.doInvocation(charOffset, typeArguments, arguments,
isTypeArgumentsInForest: isTypeArgumentsInForest);
} else {
return forest.createExpressionInvocation(
charOffset, toValue(receiver), arguments);
}
}
@override
void beginCascade(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
]),
]));
debugEvent("beginCascade");
Expression expression = popForValue();
if (expression is Cascade) {
push(expression);
push(_createReadOnlyVariableAccess(expression.variable, token,
expression.fileOffset, null, ReadOnlyAccessKind.LetVariable));
} else {
bool isNullAware = optional('?..', token);
if (isNullAware && !libraryBuilder.isNonNullableByDefault) {
reportMissingNonNullableSupport(token);
}
VariableDeclaration variable =
createVariableDeclarationForValue(expression);
push(new Cascade(variable, isNullAware: isNullAware)
..fileOffset = expression.fileOffset);
push(_createReadOnlyVariableAccess(variable, token, expression.fileOffset,
null, ReadOnlyAccessKind.LetVariable));
}
assert(checkState(token, [
ValueKinds.Generator,
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
]),
]));
}
@override
void endCascade() {
assert(checkState(null, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
]),
ValueKinds.Expression,
]));
debugEvent("endCascade");
Expression expression = popForEffect();
Cascade cascadeReceiver = pop() as Cascade;
cascadeReceiver.addCascadeExpression(expression);
push(cascadeReceiver);
}
@override
void beginCaseExpression(Token caseKeyword) {
debugEvent("beginCaseExpression");
super.push(constantContext);
constantContext = ConstantContext.inferred;
assert(checkState(caseKeyword, [ValueKinds.ConstantContext]));
}
@override
void endCaseExpression(Token colon) {
assert(checkState(colon, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
ValueKinds.ConstantContext,
]));
debugEvent("endCaseExpression");
Expression expression = popForValue();
constantContext = pop() as ConstantContext;
super.push(expression);
assert(checkState(colon, [ValueKinds.Expression]));
}
@override
void beginBinaryExpression(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
bool isAnd = optional("&&", token);
if (isAnd || optional("||", token)) {
Expression lhs = popForValue();
// This is matched by the call to [endNode] in
// [doLogicalExpression].
if (isAnd) {
typeInferrer.assignedVariables.beginNode();
}
push(lhs);
}
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
}
@override
void endBinaryExpression(Token token) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Selector,
]),
]));
debugEvent("BinaryExpression");
if (optional(".", token) ||
optional("..", token) ||
optional("?..", token)) {
doDotOrCascadeExpression(token);
} else if (optional("&&", token) || optional("||", token)) {
doLogicalExpression(token);
} else if (optional("??", token)) {
doIfNull(token);
} else if (optional("?.", token)) {
doIfNotNull(token);
} else {
doBinaryExpression(token);
}
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Initializer,
]),
]));
}
void doBinaryExpression(Token token) {
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
Expression right = popForValue();
Object? left = pop();
int fileOffset = offsetForToken(token);
String operator = token.stringValue!;
bool isNot = identical("!=", operator);
if (isNot || identical("==", operator)) {
if (left is Generator) {
push(left.buildEqualsOperation(token, right, isNot: isNot));
} else {
if (left is ProblemBuilder) {
ProblemBuilder problem = left;
left = buildProblem(problem.message, problem.charOffset, noLength);
}
assert(left is Expression);
push(forest.createEquals(fileOffset, left as Expression, right,
isNot: isNot));
}
} else {
Name name = new Name(operator);
if (!isBinaryOperator(operator) && !isMinusOperator(operator)) {
if (isUserDefinableOperator(operator)) {
push(buildProblem(
fasta.templateNotBinaryOperator.withArguments(token),
token.charOffset,
token.length));
} else {
push(buildProblem(fasta.templateInvalidOperator.withArguments(token),
token.charOffset, token.length));
}
} else if (left is Generator) {
push(left.buildBinaryOperation(token, name, right));
} else {
if (left is ProblemBuilder) {
ProblemBuilder problem = left;
left = buildProblem(problem.message, problem.charOffset, noLength);
}
assert(left is Expression);
push(forest.createBinary(fileOffset, left as Expression, name, right));
}
}
assert(checkState(token, <ValueKind>[
ValueKinds.Expression,
]));
}
/// Handle `a && b` and `a || b`.
void doLogicalExpression(Token token) {
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
Expression argument = popForValue();
Expression receiver = pop() as Expression;
Expression logicalExpression = forest.createLogicalExpression(
offsetForToken(token), receiver, token.stringValue!, argument);
push(logicalExpression);
if (optional("&&", token)) {
// This is matched by the call to [beginNode] in
// [beginBinaryExpression].
typeInferrer.assignedVariables.endNode(logicalExpression);
}
assert(checkState(token, <ValueKind>[
ValueKinds.Expression,
]));
}
/// Handle `a ?? b`.
void doIfNull(Token token) {
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
Expression b = popForValue();
Expression a = popForValue();
push(new IfNullExpression(a, b)..fileOffset = offsetForToken(token));
assert(checkState(token, <ValueKind>[
ValueKinds.Expression,
]));
}
/// Handle `a?.b(...)`.
void doIfNotNull(Token token) {
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Selector,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Initializer,
]),
]));
Object? send = pop();
if (send is Selector) {
push(send.withReceiver(pop(), token.charOffset, isNullAware: true));
} else {
pop();
token = token.next!;
push(buildProblem(fasta.templateExpectedIdentifier.withArguments(token),
offsetForToken(token), lengthForToken(token)));
}
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Initializer,
]),
]));
}
void doDotOrCascadeExpression(Token token) {
assert(checkState(token, <ValueKind>[
/* after . or .. */ unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Selector,
]),
/* before . or .. */ unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Initializer,
]),
]));
Object? send = pop();
if (send is Selector) {
Object? receiver = optional(".", token) ? pop() : popForValue();
push(send.withReceiver(receiver, token.charOffset));
} else if (send is IncompleteErrorGenerator) {
// Pop the "receiver" and push the error.
pop();
push(send);
} else {
// Pop the "receiver" and push the error.
pop();
token = token.next!;
push(buildProblem(fasta.templateExpectedIdentifier.withArguments(token),
offsetForToken(token), lengthForToken(token)));
}
assert(checkState(token, <ValueKind>[
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Initializer,
]),
]));
}
bool areArgumentsCompatible(FunctionNode function, Arguments arguments) {
// TODO(ahe): Implement this.
return true;
}
@override
Expression buildUnresolvedError(
Expression receiver, String name, Arguments arguments, int charOffset,
{Member? candidate,
bool isSuper: false,
required UnresolvedKind kind,
bool isStatic: false,
LocatedMessage? message}) {
int length = name.length;
int periodIndex = name.lastIndexOf(".");
if (periodIndex != -1) {
length -= periodIndex + 1;
}
Name kernelName = new Name(name, libraryBuilder.nameOrigin);
List<LocatedMessage>? context;
if (candidate != null && candidate.location != null) {
Uri uri = candidate.location!.file;
int offset = candidate.fileOffset;
Message contextMessage;
int length = noLength;
if (candidate is Constructor && candidate.isSynthetic) {
offset = candidate.enclosingClass.fileOffset;
contextMessage = fasta.templateCandidateFoundIsDefaultConstructor
.withArguments(candidate.enclosingClass.name);
} else {
if (candidate is Constructor) {
if (candidate.name.text == '') {
length = candidate.enclosingClass.name.length;
} else {
// Assume no spaces around the dot. Not perfect, but probably the
// best we can do with the information available.
length = candidate.enclosingClass.name.length + 1 + name.length;
}
} else {
length = name.length;
}
contextMessage = fasta.messageCandidateFound;
}
context = [contextMessage.withLocation(uri, offset, length)];
}
if (message == null) {
switch (kind) {
case UnresolvedKind.Unknown:
assert(!isSuper);
message = fasta.templateNameNotFound
.withArguments(name)
.withLocation(uri, charOffset, length);
break;
case UnresolvedKind.Member:
message = warnUnresolvedMember(kernelName, charOffset,
isSuper: isSuper, reportWarning: false, context: context)
.withLocation(uri, charOffset, length);
break;
case UnresolvedKind.Getter:
message = warnUnresolvedGet(kernelName, charOffset,
isSuper: isSuper, reportWarning: false, context: context)
.withLocation(uri, charOffset, length);
break;
case UnresolvedKind.Setter:
message = warnUnresolvedSet(kernelName, charOffset,
isSuper: isSuper, reportWarning: false, context: context)
.withLocation(uri, charOffset, length);
break;
case UnresolvedKind.Method:
message = warnUnresolvedMethod(kernelName, charOffset,
isSuper: isSuper, reportWarning: false, context: context)
.withLocation(uri, charOffset, length);
break;
case UnresolvedKind.Constructor:
message = warnUnresolvedConstructor(kernelName, isSuper: isSuper)
.withLocation(uri, charOffset, length);
break;
}
}
return buildProblem(
message.messageObject, message.charOffset, message.length,
context: context);
}
Message warnUnresolvedMember(Name name, int charOffset,
{bool isSuper: false,
bool reportWarning: true,
List<LocatedMessage>? context}) {
Message message = isSuper
? fasta.templateSuperclassHasNoMember.withArguments(name.text)
: fasta.templateMemberNotFound.withArguments(name.text);
if (reportWarning) {
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
@override
Message warnUnresolvedGet(Name name, int charOffset,
{bool isSuper: false,
bool reportWarning: true,
List<LocatedMessage>? context}) {
Message message = isSuper
? fasta.templateSuperclassHasNoGetter.withArguments(name.text)
: fasta.templateGetterNotFound.withArguments(name.text);
if (reportWarning) {
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
@override
Message warnUnresolvedSet(Name name, int charOffset,
{bool isSuper: false,
bool reportWarning: true,
List<LocatedMessage>? context}) {
Message message = isSuper
? fasta.templateSuperclassHasNoSetter.withArguments(name.text)
: fasta.templateSetterNotFound.withArguments(name.text);
if (reportWarning) {
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
@override
Message warnUnresolvedMethod(Name name, int charOffset,
{bool isSuper: false,
bool reportWarning: true,
List<LocatedMessage>? context}) {
String plainName = name.text;
int dotIndex = plainName.lastIndexOf(".");
if (dotIndex != -1) {
plainName = plainName.substring(dotIndex + 1);
}
// TODO(ahe): This is rather brittle. We would probably be better off with
// more precise location information in this case.
int length = plainName.length;
if (plainName.startsWith("[")) {
length = 1;
}
Message message = isSuper
? fasta.templateSuperclassHasNoMethod.withArguments(name.text)
: fasta.templateMethodNotFound.withArguments(name.text);
if (reportWarning) {
addProblemErrorIfConst(message, charOffset, length, context: context);
}
return message;
}
Message warnUnresolvedConstructor(Name name, {bool isSuper: false}) {
Message message = isSuper
? fasta.templateSuperclassHasNoConstructor.withArguments(name.text)
: fasta.templateConstructorNotFound.withArguments(name.text);
return message;
}
@override
void warnTypeArgumentsMismatch(String name, int expected, int charOffset) {
addProblemErrorIfConst(
fasta.templateTypeArgumentMismatch.withArguments(expected),
charOffset,
name.length);
}
@override
Member? lookupSuperMember(Name name, {bool isSetter: false}) {
return (declarationBuilder as ClassBuilder).lookupInstanceMember(
hierarchy, name,
isSetter: isSetter, isSuper: true);
}
@override
Constructor? lookupConstructor(Name name, {bool isSuper: false}) {
return sourceClassBuilder!.lookupConstructor(name, isSuper: isSuper);
}
@override
void handleIdentifier(Token token, IdentifierContext context) {
debugEvent("handleIdentifier");
String name = token.lexeme;
if (context.isScopeReference) {
assert(!inInitializerLeftHandSide ||
this.scope == enclosingScope ||
this.scope.parent == enclosingScope);
// This deals with this kind of initializer: `C(a) : a = a;`
Scope scope = inInitializerLeftHandSide ? enclosingScope : this.scope;
push(scopeLookup(scope, name, token));
} else {
if (!context.inDeclaration &&
constantContext != ConstantContext.none &&
!context.allowedInConstantExpression) {
addProblem(fasta.messageNotAConstantExpression, token.charOffset,
token.length);
}
if (token.isSynthetic) {
push(new ParserRecovery(offsetForToken(token)));
} else {
push(new Identifier(token));
}
}
assert(checkState(token, [
unionOfKinds([
ValueKinds.Identifier,
ValueKinds.Generator,
ValueKinds.ParserRecovery,
ValueKinds.ProblemBuilder,
]),
]));
}
/// Helper method to create a [VariableGet] of the [variable] using
/// [charOffset] as the file offset.
@override
VariableGet createVariableGet(VariableDeclaration variable, int charOffset,
{bool forNullGuardedAccess: false}) {
if (!(variable as VariableDeclarationImpl).isLocalFunction) {
typeInferrer.assignedVariables.read(variable);
}
return new VariableGetImpl(variable,
forNullGuardedAccess: forNullGuardedAccess)
..fileOffset = charOffset;
}
/// Helper method to create a [ReadOnlyAccessGenerator] on the [variable]
/// using [token] and [charOffset] for offset information and [name]
/// for `ExpressionGenerator._plainNameForRead`.
ReadOnlyAccessGenerator _createReadOnlyVariableAccess(
VariableDeclaration variable,
Token token,
int charOffset,
String? name,
ReadOnlyAccessKind kind) {
return new ReadOnlyAccessGenerator(
this, token, createVariableGet(variable, charOffset), name ?? '', kind);
}
/// Look up [name] in [scope] using [token] as location information (both to
/// report problems and as the file offset in the generated kernel code).
/// [isQualified] should be true if [name] is a qualified access (which
/// implies that it shouldn't be turned into a [ThisPropertyAccessGenerator]
/// if the name doesn't resolve in the scope).
@override
Expression_Generator_Builder scopeLookup(
Scope scope, String name, Token token,
{bool isQualified: false, PrefixBuilder? prefix}) {
int charOffset = offsetForToken(token);
if (token.isSynthetic) {
return new ParserErrorGenerator(this, token, fasta.messageSyntheticToken);
}
Builder? declaration = scope.lookup(name, charOffset, uri);
if (declaration == null &&
prefix == null &&
(sourceClassBuilder?.isPatch ?? false)) {
// The scope of a patched method includes the origin class.
declaration = sourceClassBuilder!.origin
.findStaticBuilder(name, charOffset, uri, libraryBuilder);
}
if (declaration != null &&
declaration.isDeclarationInstanceMember &&
(inFieldInitializer && !inLateFieldInitializer) &&
!inInitializerLeftHandSide) {
// We cannot access a class instance member in an initializer of a
// field.
//
// For instance
//
// class M {
// int foo = bar;
// int bar;
// }
//
return new IncompleteErrorGenerator(this, token,
fasta.templateThisAccessInFieldInitializer.withArguments(name));
}
if (declaration == null ||
(!isDeclarationInstanceContext &&
declaration.isDeclarationInstanceMember)) {
// We either didn't find a declaration or found an instance member from
// a non-instance context.
Name n = new Name(name, libraryBuilder.nameOrigin);
if (!isQualified && isDeclarationInstanceContext) {
assert(declaration == null);
if (constantContext != ConstantContext.none ||
(inFieldInitializer && !inLateFieldInitializer) &&
!inInitializerLeftHandSide) {
return new UnresolvedNameGenerator(this, token, n,
unresolvedReadKind: UnresolvedKind.Unknown);
}
if (extensionThis != null) {
// If we are in an extension instance member we interpret this as an
// implicit access on the 'this' parameter.
return PropertyAccessGenerator.make(this, token,
createVariableGet(extensionThis!, charOffset), n, false);
} else {
// This is an implicit access on 'this'.
return new ThisPropertyAccessGenerator(this, token, n);
}
} else if (ignoreMainInGetMainClosure &&
name == "main" &&
member.name == "_getMainClosure") {
return forest.createNullLiteral(charOffset);
} else {
return new UnresolvedNameGenerator(this, token, n,
unresolvedReadKind: UnresolvedKind.Unknown);
}
} else if (declaration.isTypeDeclaration) {
if (declaration is AccessErrorBuilder) {
AccessErrorBuilder accessError = declaration;
declaration = accessError.builder;
}
return new TypeUseGenerator(
this, token, declaration as TypeDeclarationBuilder, name);
} else if (declaration.isLocal) {
VariableBuilder variableBuilder = declaration as VariableBuilder;
if (constantContext != ConstantContext.none &&
!variableBuilder.isConst &&
!member.isConstructor &&
!enableConstFunctionsInLibrary) {
return new IncompleteErrorGenerator(
this, token, fasta.messageNotAConstantExpression);
}
VariableDeclaration variable = variableBuilder.variable!;
if (!variableBuilder.isAssignable) {
return _createReadOnlyVariableAccess(
variable,
token,
charOffset,
name,
variableBuilder.isConst
? ReadOnlyAccessKind.ConstVariable
: ReadOnlyAccessKind.FinalVariable);
} else {
return new VariableUseGenerator(this, token, variable);
}
} else if (declaration.isClassInstanceMember) {
if (constantContext != ConstantContext.none &&
!inInitializerLeftHandSide &&
// TODO(ahe): This is a hack because Fasta sets up the scope
// "this.field" parameters according to old semantics. Under the new
// semantics, such parameters introduces a new parameter with that
// name that should be resolved here.
!member.isConstructor) {
addProblem(
fasta.messageNotAConstantExpression, charOffset, token.length);
}
Name n = new Name(name, libraryBuilder.nameOrigin);
return new ThisPropertyAccessGenerator(this, token, n);
} else if (declaration.isExtensionInstanceMember) {
ExtensionBuilder extensionBuilder =
declarationBuilder as ExtensionBuilder;
MemberBuilder? setterBuilder =
_getCorrespondingSetterBuilder(scope, declaration, name, charOffset);
// TODO(johnniwinther): Check for constantContext like below?
if (declaration.isField) {
declaration = null;
}
if (setterBuilder != null &&
(setterBuilder.isField || setterBuilder.isStatic)) {
setterBuilder = null;
}
if (declaration == null && setterBuilder == null) {
return new UnresolvedNameGenerator(
this, token, new Name(name, libraryBuilder.nameOrigin),
unresolvedReadKind: UnresolvedKind.Unknown);
}
MemberBuilder? getterBuilder =
declaration is MemberBuilder ? declaration : null;
return new ExtensionInstanceAccessGenerator.fromBuilder(
this,
token,
extensionBuilder.extension,
name,
extensionThis!,
extensionTypeParameters,
getterBuilder,
setterBuilder);
} else if (declaration.isRegularMethod) {
assert(declaration.isStatic || declaration.isTopLevel);
MemberBuilder memberBuilder = declaration as MemberBuilder;
return new StaticAccessGenerator(
this, token, name, memberBuilder.member, null);
} else if (decla