Files
sdk/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
T
Johnni Winther 69b9af2230 [cfe] Handle extensions in Extension?.member check
Closes https://github.com/dart-lang/sdk/issues/49127

Change-Id: I26ce537dbed908789333f4aa6baa6b8487e5e993
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246447
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
2022-05-31 13:11:03 +00:00

7906 lines
279 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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/api_prototype/experimental_flags.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;
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';
import '../source/source_member_builder.dart';
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.libraryBuilder.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
LibraryFeatures get libraryFeatures => libraryBuilder.libraryFeatures;
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, uri, 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, TypeUse.fieldType,
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.checkPendingBoundsChecks(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.isOptionalPositional || initializer != null)) {
if (!parameter.initializerWasInferred) {
parameter.initializerWasInferred = true;
if (parameter.isOptionalPositional) {
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;
assert(
!(builder.function.asyncMarker == AsyncMarker.Async &&
builder.function.futureValueType == null),
"No future value type computed.");
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.isOptionalPositional) {
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(
/* metadata = */ null,
FormalParameterKind.requiredPositional,
/* modifiers = */ 0,
/* type = */ 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;
Set<String>? namedSuperParameterNames;
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);
(namedSuperParameterNames ??= <String>{}).add(formal.name);
} 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 (libraryFeatures.superParameters.isEnabled) {
ArgumentsImpl arguments = superInitializer.arguments as ArgumentsImpl;
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);
arguments.positionalAreSuperParameters = true;
}
}
if (namedSuperParametersAsArguments != null) {
// TODO(cstefantsova): Report name conflicts.
arguments.named.addAll(namedSuperParametersAsArguments);
setParents(namedSuperParametersAsArguments, arguments);
arguments.namedSuperParameterNames = namedSuperParameterNames;
}
}
} else if (initializers.last is RedirectingInitializer) {
RedirectingInitializer redirectingInitializer =
initializers.last as RedirectingInitializer;
if (sourceClassBuilder is SourceEnumBuilder &&
libraryFeatures.enhancedEnums.isEnabled) {
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 ks initializer list,
/// >unless the enclosing class is class Object.
Constructor? superTarget = lookupConstructor(emptyName, isSuper: true);
Initializer initializer;
ArgumentsImpl arguments;
List<Expression>? positionalArguments;
List<NamedExpression>? namedArguments;
if (libraryFeatures.superParameters.isEnabled) {
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 VariableGetImpl(constructor.function.positionalParameters[0],
forNullGuardedAccess: false),
new VariableGetImpl(constructor.function.positionalParameters[1],
forNullGuardedAccess: false)
]);
}
if (positionalArguments != null || namedArguments != null) {
arguments = forest.createArguments(
noLocation, positionalArguments ?? <Expression>[],
named: namedArguments);
} else {
arguments = forest.createArgumentsEmpty(noLocation);
}
arguments.positionalAreSuperParameters =
positionalSuperParametersAsArguments != null;
arguments.namedSuperParameterNames = namedSuperParameterNames;
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);
}
if (libraryFeatures.superParameters.isEnabled) {
InitializerInferenceResult inferenceResult =
typeInferrer.inferInitializer(this, initializer);
builder.addInitializer(initializer, this,
inferenceResult: inferenceResult);
} else {
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 (libraryFeatures.namedArgumentsAnywhere.isEnabled) {
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 (!libraryFeatures.namedArgumentsAnywhere.isEnabled) {
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 (libraryFeatures.namedArgumentsAnywhere.isEnabled) {
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, TypeUse.invocationTypeArgument,
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 &&
!libraryFeatures.constFunctions.isEnabled) {
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.parent, memberBuilder.member, null);
} else if (declaration is PrefixBuilder) {
assert(prefix == null);
return new PrefixUseGenerator(this, token, declaration);
} else if (declaration is LoadLibraryBuilder) {
return new LoadLibraryGenerator(this, token, declaration);
} else if (declaration.hasProblem && declaration is! AccessErrorBuilder) {
return declaration;
} else {
MemberBuilder? setterBuilder =
_getCorrespondingSetterBuilder(scope, declaration, name, charOffset);
MemberBuilder? getterBuilder =
declaration is MemberBuilder ? declaration : null;
assert(getterBuilder != null || setterBuilder != null);
StaticAccessGenerator generator = new StaticAccessGenerator.fromBuilder(
this, name, token, getterBuilder, setterBuilder);
if (constantContext != ConstantContext.none) {
Member? readTarget = generator.readTarget;
if (!(readTarget is Field && readTarget.isConst ||
// Static tear-offs are also compile time constants.
readTarget is Procedure)) {
addProblem(
fasta.messageNotAConstantExpression, charOffset, token.length);
}
}
return generator;
}
}
/// Returns the setter builder corresponding to [declaration] using the
/// [name] and [charOffset] for the lookup into [scope] if necessary.
MemberBuilder? _getCorrespondingSetterBuilder(
Scope scope, Builder declaration, String name, int charOffset) {
Builder? setter;
if (declaration.isSetter) {
setter = declaration;
} else if (declaration.isGetter) {
setter = scope.lookupSetter(name, charOffset, uri);
} else if (declaration.isField) {
MemberBuilder fieldBuilder = declaration as MemberBuilder;
if (!fieldBuilder.isAssignable) {
setter = scope.lookupSetter(name, charOffset, uri);
} else {
setter = declaration;
}
}
return setter is MemberBuilder ? setter : null;
}
@override
void handleQualified(Token period) {
debugEvent("Qualified");
Object? node = pop();
Object? qualifier = pop();
if (qualifier is ParserRecovery) {
push(qualifier);
} else if (node is ParserRecovery) {
push(node);
} else {
Identifier identifier = node as Identifier;
push(identifier.withQualifier(qualifier!));
}
}
@override
void beginLiteralString(Token token) {
debugEvent("beginLiteralString");
push(token);
}
@override
void handleStringPart(Token token) {
debugEvent("StringPart");
push(token);
}
@override
void endLiteralString(int interpolationCount, Token endToken) {
debugEvent("endLiteralString");
if (interpolationCount == 0) {
Token token = pop() as Token;
String value = unescapeString(token.lexeme, token, this);
push(forest.createStringLiteral(offsetForToken(token), value));
} else {
int count = 1 + interpolationCount * 2;
List<Object>? parts = const FixedNullableList<Object>()
.popNonNullable(stack, count, /* dummyValue = */ 0);
if (parts == null) {
push(new ParserRecovery(endToken.charOffset));
return;
}
Token first = parts.first as Token;
Token last = parts.last as Token;
Quote quote = analyzeQuote(first.lexeme);
List<Expression> expressions = <Expression>[];
// Contains more than just \' or \".
if (first.lexeme.length > 1) {
String value =
unescapeFirstStringPart(first.lexeme, quote, first, this);
if (value.isNotEmpty) {
expressions
.add(forest.createStringLiteral(offsetForToken(first), value));
}
}
for (int i = 1; i < parts.length - 1; i++) {
Object part = parts[i];
if (part is Token) {
if (part.lexeme.length != 0) {
String value = unescape(part.lexeme, quote, part, this);
expressions
.add(forest.createStringLiteral(offsetForToken(part), value));
}
} else {
expressions.add(toValue(part));
}
}
// Contains more than just \' or \".
if (last.lexeme.length > 1) {
String value = unescapeLastStringPart(
last.lexeme, quote, last, last.isSynthetic, this);
if (value.isNotEmpty) {
expressions
.add(forest.createStringLiteral(offsetForToken(last), value));
}
}
push(forest.createStringConcatenation(
offsetForToken(endToken), expressions));
}
}
@override
void handleNativeClause(Token nativeToken, bool hasName) {
debugEvent("NativeClause");
if (hasName) {
pop() as StringLiteral;
}
}
@override
void handleScript(Token token) {
debugEvent("Script");
}
@override
void handleStringJuxtaposition(Token startToken, int literalCount) {
debugEvent("StringJuxtaposition");
List<Expression> parts = popListForValue(literalCount);
List<Expression>? expressions;
// Flatten string juxtapositions of string interpolation.
for (int i = 0; i < parts.length; i++) {
Expression part = parts[i];
if (part is StringConcatenation) {
if (expressions == null) {
expressions = parts.sublist(0, i);
}
for (Expression expression in part.expressions) {
expressions.add(expression);
}
} else {
if (expressions != null) {
expressions.add(part);
}
}
}
push(forest.createStringConcatenation(
offsetForToken(startToken), expressions ?? parts));
}
@override
void handleLiteralInt(Token token) {
debugEvent("LiteralInt");
int? value = int.tryParse(token.lexeme);
// Postpone parsing of literals resulting in a negative value
// (hex literals >= 2^63). These are only allowed when not negated.
if (value == null || value < 0) {
push(forest.createIntLiteralLarge(offsetForToken(token), token.lexeme));
} else {
push(forest.createIntLiteral(offsetForToken(token), value, token.lexeme));
}
}
@override
void handleEmptyFunctionBody(Token semicolon) {
debugEvent("ExpressionFunctionBody");
endBlockFunctionBody(0, null, semicolon);
}
@override
void handleExpressionFunctionBody(Token arrowToken, Token? endToken) {
debugEvent("ExpressionFunctionBody");
endReturnStatement(true, arrowToken.next!, endToken);
}
@override
void endReturnStatement(
bool hasExpression, Token beginToken, Token? endToken) {
debugEvent("ReturnStatement");
Expression? expression = hasExpression ? popForValue() : null;
if (expression != null && inConstructor) {
push(buildProblemStatement(
fasta.messageConstructorWithReturnType, beginToken.charOffset));
} else {
push(forest.createReturnStatement(offsetForToken(beginToken), expression,
isArrow: !identical(beginToken.lexeme, "return")));
}
}
@override
void beginThenStatement(Token token) {
debugEvent("beginThenStatement");
Expression condition = popForValue();
// This is matched by the call to [deferNode] in
// [endThenStatement].
typeInferrer.assignedVariables.beginNode();
push(condition);
enterLocalScope("then");
}
@override
void endThenStatement(Token token) {
debugEvent("endThenStatement");
Object? body = pop();
exitLocalScope();
push(body);
// This is matched by the call to [beginNode] in
// [beginThenStatement] and by the call to [storeInfo] in
// [endIfStatement].
push(typeInferrer.assignedVariables.deferNode());
}
@override
void endIfStatement(Token ifToken, Token? elseToken) {
Statement? elsePart = popStatementIfNotNull(elseToken);
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesInfo =
pop() as AssignedVariablesNodeInfo<VariableDeclaration>;
Statement thenPart = popStatement();
Expression condition = pop() as Expression;
Statement node = forest.createIfStatement(
offsetForToken(ifToken), condition, thenPart, elsePart);
// This is matched by the call to [deferNode] in
// [endThenStatement].
typeInferrer.assignedVariables.storeInfo(node, assignedVariablesInfo);
push(node);
}
@override
void beginVariableInitializer(Token token) {
if ((currentLocalVariableModifiers & lateMask) != 0) {
// This is matched by the call to [endNode] in [endVariableInitializer].
typeInferrer.assignedVariables.beginNode();
}
}
@override
void endVariableInitializer(Token assignmentOperator) {
debugEvent("VariableInitializer");
assert(assignmentOperator.stringValue == "=");
AssignedVariablesNodeInfo<VariableDeclaration>? assignedVariablesInfo;
bool isLate = (currentLocalVariableModifiers & lateMask) != 0;
Expression initializer = popForValue();
if (isLate) {
assignedVariablesInfo = typeInferrer.assignedVariables
.deferNode(isClosureOrLateVariableInitializer: true);
}
pushNewLocalVariable(initializer, equalsToken: assignmentOperator);
if (isLate) {
VariableDeclaration node = peek() as VariableDeclaration;
// This is matched by the call to [beginNode] in
// [beginVariableInitializer].
typeInferrer.assignedVariables.storeInfo(node, assignedVariablesInfo!);
}
}
@override
void handleNoVariableInitializer(Token token) {
debugEvent("NoVariableInitializer");
bool isConst = (currentLocalVariableModifiers & constMask) != 0;
bool isFinal = (currentLocalVariableModifiers & finalMask) != 0;
bool isLate = (currentLocalVariableModifiers & lateMask) != 0;
Expression? initializer;
if (!optional("in", token.next!)) {
// A for-in loop-variable can't have an initializer. So let's remain
// silent if the next token is `in`. Since a for-in loop can only have
// one variable it must be followed by `in`.
if (!token.isSynthetic) {
// If [token] is synthetic it is created from error recovery.
if (isConst) {
initializer = buildProblem(
fasta.templateConstFieldWithoutInitializer
.withArguments(token.lexeme),
token.charOffset,
token.length);
} else if (!libraryBuilder.isNonNullableByDefault &&
isFinal &&
!isLate) {
initializer = buildProblem(
fasta.templateFinalFieldWithoutInitializer
.withArguments(token.lexeme),
token.charOffset,
token.length);
}
}
}
pushNewLocalVariable(initializer);
}
void pushNewLocalVariable(Expression? initializer, {Token? equalsToken}) {
Object? node = pop();
if (node is ParserRecovery) {
push(node);
return;
}
Identifier identifier = node as Identifier;
assert(currentLocalVariableModifiers != -1);
bool isConst = (currentLocalVariableModifiers & constMask) != 0;
bool isFinal = (currentLocalVariableModifiers & finalMask) != 0;
bool isLate = (currentLocalVariableModifiers & lateMask) != 0;
bool isRequired = (currentLocalVariableModifiers & requiredMask) != 0;
assert(isConst == (constantContext == ConstantContext.inferred));
VariableDeclaration variable = new VariableDeclarationImpl(identifier.name,
forSyntheticToken: identifier.token.isSynthetic,
initializer: initializer,
type: currentLocalVariableType,
isFinal: isFinal,
isConst: isConst,
isLate: isLate,
isRequired: isRequired,
hasDeclaredInitializer: initializer != null,
isStaticLate: libraryBuilder.isNonNullableByDefault &&
isFinal &&
initializer == null)
..fileOffset = identifier.charOffset
..fileEqualsOffset = offsetForToken(equalsToken);
typeInferrer.assignedVariables.declare(variable);
push(variable);
}
@override
void beginFieldInitializer(Token token) {
inFieldInitializer = true;
constantContext = member.isConst
? ConstantContext.inferred
: !member.isStatic &&
sourceClassBuilder != null &&
sourceClassBuilder!.declaresConstConstructor
? ConstantContext.required
: ConstantContext.none;
if (member is SourceFieldBuilder) {
SourceFieldBuilder fieldBuilder = member as SourceFieldBuilder;
inLateFieldInitializer = fieldBuilder.isLate;
if (fieldBuilder.isAbstract) {
addProblem(
fasta.messageAbstractFieldInitializer, token.charOffset, noLength);
} else if (fieldBuilder.isExternal) {
addProblem(
fasta.messageExternalFieldInitializer, token.charOffset, noLength);
}
} else {
inLateFieldInitializer = false;
}
}
@override
void endFieldInitializer(Token assignmentOperator, Token token) {
debugEvent("FieldInitializer");
inFieldInitializer = false;
inLateFieldInitializer = false;
assert(assignmentOperator.stringValue == "=");
push(popForValue());
constantContext = ConstantContext.none;
}
@override
void handleNoFieldInitializer(Token token) {
debugEvent("NoFieldInitializer");
constantContext = member.isConst
? ConstantContext.inferred
: !member.isStatic &&
sourceClassBuilder != null &&
sourceClassBuilder!.declaresConstConstructor
? ConstantContext.required
: ConstantContext.none;
if (constantContext == ConstantContext.inferred) {
// Creating a null value to prevent the Dart VM from crashing.
push(forest.createNullLiteral(offsetForToken(token)));
} else {
push(NullValue.FieldInitializer);
}
constantContext = ConstantContext.none;
}
@override
void endInitializedIdentifier(Token nameToken) {
// TODO(ahe): Use [InitializedIdentifier] here?
debugEvent("InitializedIdentifier");
Object? node = pop();
if (node is ParserRecovery) {
push(node);
return;
}
VariableDeclaration variable = node as VariableDeclaration;
variable.fileOffset = nameToken.charOffset;
push(variable);
declareVariable(variable, scope);
}
@override
void beginVariablesDeclaration(
Token token, Token? lateToken, Token? varFinalOrConst) {
debugEvent("beginVariablesDeclaration");
if (!libraryBuilder.isNonNullableByDefault) {
reportNonNullableModifierError(lateToken);
}
TypeBuilder? unresolvedType = pop(NullValue.TypeBuilder) as TypeBuilder?;
DartType? type = unresolvedType != null
? buildDartType(unresolvedType, TypeUse.variableType,
allowPotentiallyConstantType: false)
: null;
int modifiers = (lateToken != null ? lateMask : 0) |
Modifier.validateVarFinalOrConst(varFinalOrConst?.lexeme);
_enterLocalState(inLateLocalInitializer: lateToken != null);
super.push(currentLocalVariableModifiers);
super.push(currentLocalVariableType ?? NullValue.Type);
currentLocalVariableType = type;
currentLocalVariableModifiers = modifiers;
super.push(constantContext);
constantContext = ((modifiers & constMask) != 0)
? ConstantContext.inferred
: ConstantContext.none;
}
@override
void endVariablesDeclaration(int count, Token? endToken) {
debugEvent("VariablesDeclaration");
if (count == 1) {
Object? node = pop();
constantContext = pop() as ConstantContext;
currentLocalVariableType = pop(NullValue.Type) as DartType?;
currentLocalVariableModifiers = pop() as int;
List<Expression>? annotations = pop() as List<Expression>?;
if (node is ParserRecovery) {
push(node);
return;
}
VariableDeclaration variable = node as VariableDeclaration;
if (annotations != null) {
for (int i = 0; i < annotations.length; i++) {
variable.addAnnotation(annotations[i]);
}
(variablesWithMetadata ??= <VariableDeclaration>[]).add(variable);
}
push(variable);
} else {
List<VariableDeclaration>? variables =
const FixedNullableList<VariableDeclaration>()
.popNonNullable(stack, count, dummyVariableDeclaration);
constantContext = pop() as ConstantContext;
currentLocalVariableType = pop(NullValue.Type) as DartType?;
currentLocalVariableModifiers = pop() as int;
List<Expression>? annotations = pop() as List<Expression>?;
if (variables == null) {
push(new ParserRecovery(offsetForToken(endToken)));
return;
}
if (annotations != null) {
VariableDeclaration first = variables.first;
for (int i = 0; i < annotations.length; i++) {
first.addAnnotation(annotations[i]);
}
(multiVariablesWithMetadata ??= <List<VariableDeclaration>>[])
.add(variables);
}
push(forest.variablesDeclaration(variables, uri));
}
_exitLocalState();
}
/// Stack containing assigned variables info for try statements.
///
/// These are created in [beginTryStatement] and ended in either [beginBlock]
/// when a finally block starts or in [endTryStatement] when the try statement
/// ends. Since these need to be associated with the try statement created in
/// in [endTryStatement] we store them the stack until the try statement is
/// created.
Link<AssignedVariablesNodeInfo<VariableDeclaration>> tryStatementInfoStack =
const Link<AssignedVariablesNodeInfo<VariableDeclaration>>();
@override
void beginBlock(Token token, BlockKind blockKind) {
if (blockKind == BlockKind.tryStatement) {
// This is matched by the call to [endNode] in [endBlock].
typeInferrer.assignedVariables.beginNode();
} else if (blockKind == BlockKind.finallyClause) {
// This is matched by the call to [beginNode] in [beginTryStatement].
tryStatementInfoStack = tryStatementInfoStack
.prepend(typeInferrer.assignedVariables.deferNode());
}
debugEvent("beginBlock");
enterLocalScope("block");
}
@override
void endBlock(
int count, Token openBrace, Token closeBrace, BlockKind blockKind) {
debugEvent("Block");
Statement block = popBlock(count, openBrace, closeBrace);
exitLocalScope();
push(block);
if (blockKind == BlockKind.tryStatement) {
// This is matched by the call to [beginNode] in [beginBlock].
typeInferrer.assignedVariables.endNode(block);
}
}
@override
void handleInvalidTopLevelBlock(Token token) {
// TODO(danrubel): Consider improved recovery by adding this block
// as part of a synthetic top level function.
pop(); // block
}
@override
void handleAssignmentExpression(Token token) {
assert(checkState(token, [
unionOfKinds(<ValueKind>[
ValueKinds.Expression,
ValueKinds.Generator,
// TODO(johnniwinther): Avoid problem builders here.
ValueKinds.ProblemBuilder
]),
unionOfKinds(<ValueKind>[
ValueKinds.Expression, ValueKinds.Generator,
// TODO(johnniwinther): Avoid problem builders here.
ValueKinds.ProblemBuilder
])
]));
debugEvent("AssignmentExpression");
Expression value = popForValue();
Object? generator = pop();
if (generator is! Generator) {
push(buildProblem(fasta.messageNotAnLvalue, offsetForToken(token),
lengthForToken(token)));
} else {
push(new DelayedAssignment(
this, token, generator, value, token.stringValue!));
}
}
void enterLoop(int charOffset) {
if (peek() is LabelTarget) {
LabelTarget target = peek() as LabelTarget;
enterBreakTarget(charOffset, target.breakTarget);
enterContinueTarget(charOffset, target.continueTarget);
} else {
enterBreakTarget(charOffset);
enterContinueTarget(charOffset);
}
}
void exitLoopOrSwitch(Statement statement) {
if (problemInLoopOrSwitch != null) {
push(problemInLoopOrSwitch);
problemInLoopOrSwitch = null;
} else {
push(statement);
}
}
List<VariableDeclaration>? _buildForLoopVariableDeclarations(
variableOrExpression) {
// TODO(ahe): This can be simplified now that we have the events
// `handleForInitializer...` events.
if (variableOrExpression is Generator) {
variableOrExpression = variableOrExpression.buildForEffect();
}
if (variableOrExpression is VariableDeclaration) {
// Late for loop variables are not supported. An error has already been
// reported by the parser.
variableOrExpression.isLate = false;
return <VariableDeclaration>[variableOrExpression];
} else if (variableOrExpression is Expression) {
VariableDeclaration variable =
new VariableDeclarationImpl.forEffect(variableOrExpression);
return <VariableDeclaration>[variable];
} else if (variableOrExpression is ExpressionStatement) {
VariableDeclaration variable = new VariableDeclarationImpl.forEffect(
variableOrExpression.expression);
return <VariableDeclaration>[variable];
} else if (forest.isVariablesDeclaration(variableOrExpression)) {
return forest
.variablesDeclarationExtractDeclarations(variableOrExpression);
} else if (variableOrExpression is List<Object>) {
List<VariableDeclaration> variables = <VariableDeclaration>[];
for (Object v in variableOrExpression) {
variables.addAll(_buildForLoopVariableDeclarations(v)!);
}
return variables;
} else if (variableOrExpression == null) {
return <VariableDeclaration>[];
}
return null;
}
@override
void handleForInitializerEmptyStatement(Token token) {
debugEvent("ForInitializerEmptyStatement");
push(NullValue.Expression);
// This is matched by the call to [deferNode] in [endForStatement] or
// [endForControlFlow].
typeInferrer.assignedVariables.beginNode();
}
@override
void handleForInitializerExpressionStatement(Token token, bool forIn) {
debugEvent("ForInitializerExpressionStatement");
if (!forIn) {
// This is matched by the call to [deferNode] in [endForStatement] or
// [endForControlFlow].
typeInferrer.assignedVariables.beginNode();
}
}
@override
void handleForInitializerLocalVariableDeclaration(Token token, bool forIn) {
debugEvent("ForInitializerLocalVariableDeclaration");
if (forIn) {
// If the declaration is of the form `for (final x in ...)`, then we may
// have erroneously set the `isStaticLate` flag, so un-set it.
Object? declaration = peek();
if (declaration is VariableDeclarationImpl) {
declaration.isStaticLate = false;
}
} else {
// This is matched by the call to [deferNode] in [endForStatement] or
// [endForControlFlow].
typeInferrer.assignedVariables.beginNode();
}
}
@override
void handleForLoopParts(Token forKeyword, Token leftParen,
Token leftSeparator, int updateExpressionCount) {
push(forKeyword);
push(leftParen);
push(leftSeparator);
push(updateExpressionCount);
}
@override
void endForControlFlow(Token token) {
debugEvent("ForControlFlow");
Object? entry = pop();
int updateExpressionCount = pop() as int;
pop(); // left separator
pop(); // left parenthesis
Token forToken = pop() as Token;
List<Expression> updates = popListForEffect(updateExpressionCount);
Statement conditionStatement = popStatement(); // condition
if (constantContext != ConstantContext.none) {
pop(); // Pop variable or expression.
exitLocalScope();
typeInferrer.assignedVariables.discardNode();
handleRecoverableError(
fasta.templateCantUseControlFlowOrSpreadAsConstant
.withArguments(forToken),
forToken,
forToken);
push(invalidCollectionElement);
return;
}
// This is matched by the call to [beginNode] in
// [handleForInitializerEmptyStatement],
// [handleForInitializerExpressionStatement], and
// [handleForInitializerLocalVariableDeclaration].
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesNodeInfo =
typeInferrer.assignedVariables.popNode();
Object? variableOrExpression = pop();
exitLocalScope();
transformCollections = true;
List<VariableDeclaration> variables =
_buildForLoopVariableDeclarations(variableOrExpression)!;
typeInferrer.assignedVariables.pushNode(assignedVariablesNodeInfo);
Expression? condition;
if (conditionStatement is ExpressionStatement) {
condition = conditionStatement.expression;
} else {
assert(conditionStatement is EmptyStatement);
}
if (entry is MapLiteralEntry) {
ForMapEntry result = forest.createForMapEntry(
offsetForToken(forToken), variables, condition, updates, entry);
typeInferrer.assignedVariables.endNode(result);
push(result);
} else {
ForElement result = forest.createForElement(offsetForToken(forToken),
variables, condition, updates, toValue(entry));
typeInferrer.assignedVariables.endNode(result);
push(result);
}
}
@override
void endForStatement(Token endToken) {
assert(checkState(endToken, <ValueKind>[
/* body */ ValueKinds.Statement,
/* expression count */ ValueKinds.Integer,
/* left separator */ ValueKinds.Token,
/* left parenthesis */ ValueKinds.Token,
/* for keyword */ ValueKinds.Token,
]));
debugEvent("ForStatement");
Statement body = popStatement();
int updateExpressionCount = pop() as int;
pop(); // Left separator.
pop(); // Left parenthesis.
Token forKeyword = pop() as Token;
assert(checkState(endToken, <ValueKind>[
/* expressions */ ...repeatedKinds(
unionOfKinds(
<ValueKind>[ValueKinds.Expression, ValueKinds.Generator]),
updateExpressionCount),
/* condition */ ValueKinds.Statement,
/* variable or expression */ unionOfKinds(<ValueKind>[
ValueKinds.Generator,
ValueKinds.ExpressionOrNull,
ValueKinds.Statement,
ValueKinds.ObjectList,
ValueKinds.ParserRecovery,
]),
]));
List<Expression> updates = popListForEffect(updateExpressionCount);
Statement conditionStatement = popStatement();
// This is matched by the call to [beginNode] in
// [handleForInitializerEmptyStatement],
// [handleForInitializerExpressionStatement], and
// [handleForInitializerLocalVariableDeclaration].
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesNodeInfo =
typeInferrer.assignedVariables.deferNode();
Object? variableOrExpression = pop();
List<VariableDeclaration>? variables =
_buildForLoopVariableDeclarations(variableOrExpression);
exitLocalScope();
JumpTarget continueTarget = exitContinueTarget() as JumpTarget;
JumpTarget breakTarget = exitBreakTarget() as JumpTarget;
List<BreakStatementImpl>? continueStatements;
if (continueTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(body);
continueStatements =
continueTarget.resolveContinues(forest, labeledStatement);
body = labeledStatement;
}
Expression? condition;
if (conditionStatement is ExpressionStatement) {
condition = conditionStatement.expression;
} else {
assert(conditionStatement is EmptyStatement);
}
Statement forStatement = forest.createForStatement(
offsetForToken(forKeyword), variables, condition, updates, body);
typeInferrer.assignedVariables
.storeInfo(forStatement, assignedVariablesNodeInfo);
if (continueStatements != null) {
for (BreakStatementImpl continueStatement in continueStatements) {
continueStatement.targetStatement = forStatement;
}
}
Statement result = forStatement;
if (breakTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(result);
breakTarget.resolveBreaks(forest, labeledStatement, forStatement);
result = labeledStatement;
}
if (variableOrExpression is ParserRecovery) {
problemInLoopOrSwitch ??= buildProblemStatement(
fasta.messageSyntheticToken, variableOrExpression.charOffset,
suppressMessage: true);
}
exitLoopOrSwitch(result);
}
@override
void endAwaitExpression(Token keyword, Token endToken) {
debugEvent("AwaitExpression");
int fileOffset = offsetForToken(keyword);
Expression value = popForValue();
if (inLateLocalInitializer) {
push(buildProblem(fasta.messageAwaitInLateLocalInitializer, fileOffset,
keyword.charCount));
} else {
push(forest.createAwaitExpression(fileOffset, value));
}
}
@override
void endInvalidAwaitExpression(
Token keyword, Token endToken, fasta.MessageCode errorCode) {
debugEvent("AwaitExpression");
popForValue();
push(buildProblem(errorCode, keyword.offset, keyword.length));
}
@override
void endInvalidYieldStatement(Token keyword, Token? starToken, Token endToken,
fasta.MessageCode errorCode) {
debugEvent("YieldStatement");
popForValue();
push(buildProblemStatement(errorCode, keyword.offset));
}
@override
void handleAsyncModifier(Token? asyncToken, Token? starToken) {
debugEvent("AsyncModifier");
push(asyncMarkerFromTokens(asyncToken, starToken));
}
@override
void handleLiteralList(
int count, Token leftBracket, Token? constKeyword, Token rightBracket) {
debugEvent("LiteralList");
if (constantContext == ConstantContext.required && constKeyword == null) {
addProblem(fasta.messageMissingExplicitConst, offsetForToken(leftBracket),
noLength);
}
// TODO(danrubel): Replace this with popListForValue
// when control flow and spread collections have been enabled by default
List<Expression> expressions =
new List<Expression>.filled(count, dummyExpression, growable: true);
for (int i = count - 1; i >= 0; i--) {
Object? elem = pop();
if (elem != invalidCollectionElement) {
expressions[i] = toValue(elem);
} else {
expressions.removeAt(i);
}
}
List<TypeBuilder>? typeArguments = pop() as List<TypeBuilder>?;
DartType typeArgument;
if (typeArguments != null) {
if (typeArguments.length > 1) {
addProblem(
fasta.messageListLiteralTooManyTypeArguments,
offsetForToken(leftBracket),
lengthOfSpan(leftBracket, leftBracket.endGroup));
typeArgument = const InvalidType();
} else {
typeArgument = buildDartType(
typeArguments.single, TypeUse.literalTypeArgument,
allowPotentiallyConstantType: false);
typeArgument = instantiateToBounds(
typeArgument, coreTypes.objectClass, libraryBuilder.library);
}
} else {
typeArgument = implicitTypeArgument;
}
ListLiteral node = forest.createListLiteral(
// TODO(johnniwinther): The file offset computed below will not be
// correct if there are type arguments but no `const` keyword.
offsetForToken(constKeyword ?? leftBracket),
typeArgument,
expressions,
isConst: constKeyword != null ||
constantContext == ConstantContext.inferred);
push(node);
}
void buildLiteralSet(List<TypeBuilder>? typeArguments, Token? constKeyword,
Token leftBrace, List<dynamic>? setOrMapEntries) {
DartType typeArgument;
if (typeArguments != null) {
typeArgument = buildDartType(
typeArguments.single, TypeUse.literalTypeArgument,
allowPotentiallyConstantType: false);
typeArgument = instantiateToBounds(
typeArgument, coreTypes.objectClass, libraryBuilder.library);
} else {
typeArgument = implicitTypeArgument;
}
List<Expression> expressions = <Expression>[];
if (setOrMapEntries != null) {
for (dynamic entry in setOrMapEntries) {
if (entry is MapLiteralEntry) {
// TODO(danrubel): report the error on the colon
addProblem(fasta.templateExpectedButGot.withArguments(','),
entry.fileOffset, 1);
} else {
// TODO(danrubel): Revise once control flow and spread
// collection entries are supported.
expressions.add(entry as Expression);
}
}
}
SetLiteral node = forest.createSetLiteral(
// TODO(johnniwinther): The file offset computed below will not be
// correct if there are type arguments but no `const` keyword.
offsetForToken(constKeyword ?? leftBrace),
typeArgument,
expressions,
isConst: constKeyword != null ||
constantContext == ConstantContext.inferred);
push(node);
}
@override
void handleLiteralSetOrMap(
int count,
Token leftBrace,
Token? constKeyword,
Token rightBrace,
// TODO(danrubel): hasSetEntry parameter exists for replicating existing
// behavior and will be removed once unified collection has been enabled
bool hasSetEntry,
) {
debugEvent("LiteralSetOrMap");
if (constantContext == ConstantContext.required && constKeyword == null) {
addProblem(fasta.messageMissingExplicitConst, offsetForToken(leftBrace),
noLength);
}
List<dynamic> setOrMapEntries =
new List<dynamic>.filled(count, null, growable: true);
for (int i = count - 1; i >= 0; i--) {
Object? elem = pop();
// TODO(danrubel): Revise this to handle control flow and spread
if (elem == invalidCollectionElement) {
setOrMapEntries.removeAt(i);
} else if (elem is MapLiteralEntry) {
setOrMapEntries[i] = elem;
} else {
setOrMapEntries[i] = toValue(elem);
}
}
List<TypeBuilder>? typeArguments = pop() as List<TypeBuilder>?;
// Replicate existing behavior that has been removed from the parser.
// This will be removed once unified collections is implemented.
// Determine if this is a set or map based on type args and content
// TODO(danrubel): Since type resolution is needed to disambiguate
// set or map in some situations, consider always deferring determination
// until the type resolution phase.
final int? typeArgCount = typeArguments?.length;
bool? isSet = typeArgCount == 1
? true
: typeArgCount != null
? false
: null;
for (int i = 0; i < setOrMapEntries.length; ++i) {
if (setOrMapEntries[i] is! MapLiteralEntry &&
!isConvertibleToMapEntry(setOrMapEntries[i])) {
hasSetEntry = true;
}
}
// TODO(danrubel): If the type arguments are not known (null) then
// defer set/map determination until after type resolution as per the
// unified collection spec: https://github.com/dart-lang/language/pull/200
// rather than trying to guess as done below.
isSet ??= hasSetEntry;
if (isSet) {
buildLiteralSet(typeArguments, constKeyword, leftBrace, setOrMapEntries);
} else {
List<MapLiteralEntry> mapEntries = new List<MapLiteralEntry>.filled(
setOrMapEntries.length, dummyMapLiteralEntry);
for (int i = 0; i < setOrMapEntries.length; ++i) {
if (setOrMapEntries[i] is MapLiteralEntry) {
mapEntries[i] = setOrMapEntries[i];
} else {
mapEntries[i] = convertToMapEntry(setOrMapEntries[i], this,
typeInferrer.assignedVariables.reassignInfo);
}
}
buildLiteralMap(typeArguments, constKeyword, leftBrace, mapEntries);
}
}
@override
void handleLiteralBool(Token token) {
debugEvent("LiteralBool");
bool value = optional("true", token);
assert(value || optional("false", token));
push(forest.createBoolLiteral(offsetForToken(token), value));
}
@override
void handleLiteralDouble(Token token) {
debugEvent("LiteralDouble");
push(forest.createDoubleLiteral(
offsetForToken(token), double.parse(token.lexeme)));
}
@override
void handleLiteralNull(Token token) {
debugEvent("LiteralNull");
push(forest.createNullLiteral(offsetForToken(token)));
}
void buildLiteralMap(List<TypeBuilder>? typeArguments, Token? constKeyword,
Token leftBrace, List<MapLiteralEntry> entries) {
DartType keyType;
DartType valueType;
if (typeArguments != null) {
if (typeArguments.length != 2) {
keyType = const InvalidType();
valueType = const InvalidType();
} else {
keyType = buildDartType(typeArguments[0], TypeUse.literalTypeArgument,
allowPotentiallyConstantType: false);
valueType = buildDartType(typeArguments[1], TypeUse.literalTypeArgument,
allowPotentiallyConstantType: false);
keyType = instantiateToBounds(
keyType, coreTypes.objectClass, libraryBuilder.library);
valueType = instantiateToBounds(
valueType, coreTypes.objectClass, libraryBuilder.library);
}
} else {
DartType implicitTypeArgument = this.implicitTypeArgument;
keyType = implicitTypeArgument;
valueType = implicitTypeArgument;
}
MapLiteral node = forest.createMapLiteral(
// TODO(johnniwinther): The file offset computed below will not be
// correct if there are type arguments but no `const` keyword.
offsetForToken(constKeyword ?? leftBrace),
keyType,
valueType,
entries,
isConst: constKeyword != null ||
constantContext == ConstantContext.inferred);
push(node);
}
@override
void handleLiteralMapEntry(Token colon, Token endToken) {
debugEvent("LiteralMapEntry");
Expression value = popForValue();
Expression key = popForValue();
push(forest.createMapEntry(offsetForToken(colon), key, value));
}
String symbolPartToString(name) {
if (name is Identifier) {
return name.name;
} else if (name is Operator) {
return name.name;
} else {
return unhandled("${name.runtimeType}", "symbolPartToString", -1, uri);
}
}
@override
void endLiteralSymbol(Token hashToken, int identifierCount) {
debugEvent("LiteralSymbol");
if (identifierCount == 1) {
Object? part = pop();
if (part is ParserRecovery) {
push(new ParserErrorGenerator(
this, hashToken, fasta.messageSyntheticToken));
} else {
push(forest.createSymbolLiteral(
offsetForToken(hashToken), symbolPartToString(part)));
}
} else {
List<Identifier>? parts = const FixedNullableList<Identifier>()
.popNonNullable(stack, identifierCount, dummyIdentifier);
if (parts == null) {
push(new ParserErrorGenerator(
this, hashToken, fasta.messageSyntheticToken));
return;
}
String value = symbolPartToString(parts.first);
for (int i = 1; i < parts.length; i++) {
value += ".${symbolPartToString(parts[i])}";
}
push(forest.createSymbolLiteral(offsetForToken(hashToken), value));
}
}
@override
void handleNonNullAssertExpression(Token bang) {
assert(checkState(bang, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.Initializer,
ValueKinds.ProblemBuilder
])
]));
if (!libraryBuilder.isNonNullableByDefault) {
reportNonNullAssertExpressionNotEnabled(bang);
}
Expression operand = popForValue();
push(forest.createNullCheck(offsetForToken(bang), operand));
}
@override
void handleType(Token beginToken, Token? questionMark) {
// TODO(ahe): The scope is wrong for return types of generic functions.
debugEvent("Type");
if (!libraryBuilder.isNonNullableByDefault) {
reportErrorIfNullableType(questionMark);
}
bool isMarkedAsNullable = questionMark != null;
List<TypeBuilder>? arguments = pop() as List<TypeBuilder>?;
Object? name = pop();
if (name is QualifiedName) {
QualifiedName qualified = name;
Object prefix = qualified.qualifier;
Token suffix = qualified.suffix;
if (prefix is Generator) {
name = prefix.qualifiedLookup(suffix);
} else {
String name = getNodeName(prefix);
String displayName = debugName(name, suffix.lexeme);
int offset = offsetForToken(beginToken);
Message message = fasta.templateNotAType.withArguments(displayName);
libraryBuilder.addProblem(
message, offset, lengthOfSpan(beginToken, suffix), uri);
push(new NamedTypeBuilder.forInvalidType(
name,
libraryBuilder.nullableBuilderIfTrue(isMarkedAsNullable),
message.withLocation(
uri, offset, lengthOfSpan(beginToken, suffix))));
return;
}
}
TypeBuilder? result;
if (name is Generator) {
bool allowPotentiallyConstantType;
if (libraryBuilder.isNonNullableByDefault) {
if (libraryFeatures.constructorTearoffs.isEnabled) {
allowPotentiallyConstantType = true;
} else {
allowPotentiallyConstantType = inIsOrAsOperatorType;
}
} else {
allowPotentiallyConstantType = false;
}
result = name.buildTypeWithResolvedArguments(
libraryBuilder.nullableBuilderIfTrue(isMarkedAsNullable), arguments,
allowPotentiallyConstantType: allowPotentiallyConstantType,
forTypeLiteral: false);
// ignore: unnecessary_null_comparison
if (result == null) {
unhandled("null", "result", beginToken.charOffset, uri);
}
} else if (name is ProblemBuilder) {
// TODO(ahe): Arguments could be passed here.
libraryBuilder.addProblem(
name.message, name.charOffset, name.name.length, name.fileUri);
result = new NamedTypeBuilder.forInvalidType(
name.name,
libraryBuilder.nullableBuilderIfTrue(isMarkedAsNullable),
name.message
.withLocation(name.fileUri, name.charOffset, name.name.length));
} else {
unhandled(
"${name.runtimeType}", "handleType", beginToken.charOffset, uri);
}
push(result);
}
@override
void beginFunctionType(Token beginToken) {
debugEvent("beginFunctionType");
}
void enterFunctionTypeScope(List<TypeVariableBuilder>? typeVariables) {
debugEvent("enterFunctionTypeScope");
enterLocalScope('FunctionTypeScope',
scope.createNestedScope("function-type scope", isModifiable: true));
if (typeVariables != null) {
for (TypeVariableBuilder builder in typeVariables) {
String name = builder.name;
TypeVariableBuilder? existing = scope.lookupLocalMember(name,
setter: false) as TypeVariableBuilder?;
if (existing == null) {
scope.addLocalMember(name, builder, setter: false);
} else {
reportDuplicatedDeclaration(existing, name, builder.charOffset);
}
}
}
}
@override
void endFunctionType(Token functionToken, Token? questionMark) {
debugEvent("FunctionType");
if (!libraryBuilder.isNonNullableByDefault) {
reportErrorIfNullableType(questionMark);
}
FormalParameters formals = pop() as FormalParameters;
TypeBuilder? returnType = pop() as TypeBuilder?;
List<TypeVariableBuilder>? typeVariables =
pop() as List<TypeVariableBuilder>?;
if (typeVariables != null) {
for (TypeVariableBuilder builder in typeVariables) {
if (builder.parameter.annotations.isNotEmpty) {
if (!libraryFeatures.genericMetadata.isEnabled) {
addProblem(fasta.messageAnnotationOnFunctionTypeTypeVariable,
builder.charOffset, builder.name.length);
}
// Annotations on function types are not constant evaluated and are
// not included in the generated AST so we clear them here.
builder.parameter.annotations = const <Expression>[];
}
}
}
TypeBuilder type = formals.toFunctionType(
returnType,
libraryBuilder.nullableBuilderIfTrue(questionMark != null),
typeVariables);
exitLocalScope();
push(type);
}
@override
void handleVoidKeyword(Token token) {
debugEvent("VoidKeyword");
int offset = offsetForToken(token);
// "void" is always nullable.
push(new NamedTypeBuilder.fromTypeDeclarationBuilder(
new VoidTypeDeclarationBuilder(
const VoidType(), libraryBuilder, offset),
const NullabilityBuilder.inherent(),
fileUri: uri,
charOffset: offset,
instanceTypeVariableAccess:
InstanceTypeVariableAccessState.Unexpected));
}
@override
void handleVoidKeywordWithTypeArguments(Token token) {
assert(checkState(token, <ValueKind>[
/* arguments */ ValueKinds.TypeArgumentsOrNull,
]));
debugEvent("handleVoidKeywordWithTypeArguments");
pop(); // arguments.
handleVoidKeyword(token);
}
@override
void beginAsOperatorType(Token operator) {
_isOrAsOperatorTypeState = _isOrAsOperatorTypeState.prepend(true);
}
@override
void endAsOperatorType(Token operator) {
_isOrAsOperatorTypeState = _isOrAsOperatorTypeState.tail!;
}
@override
void handleAsOperator(Token operator) {
debugEvent("AsOperator");
DartType type = buildDartType(pop() as TypeBuilder, TypeUse.asType,
allowPotentiallyConstantType: libraryBuilder.isNonNullableByDefault);
Expression expression = popForValue();
Expression asExpression = forest.createAsExpression(
offsetForToken(operator), expression, type,
forNonNullableByDefault: libraryBuilder.isNonNullableByDefault);
push(asExpression);
}
@override
void beginIsOperatorType(Token operator) {
_isOrAsOperatorTypeState = _isOrAsOperatorTypeState.prepend(true);
}
@override
void endIsOperatorType(Token operator) {
_isOrAsOperatorTypeState = _isOrAsOperatorTypeState.tail!;
}
@override
void handleIsOperator(Token isOperator, Token? not) {
debugEvent("IsOperator");
DartType type = buildDartType(pop() as TypeBuilder, TypeUse.isType,
allowPotentiallyConstantType: libraryBuilder.isNonNullableByDefault);
Expression operand = popForValue();
Expression isExpression = forest.createIsExpression(
offsetForToken(isOperator), operand, type,
forNonNullableByDefault: libraryBuilder.isNonNullableByDefault,
notFileOffset: not != null ? offsetForToken(not) : null);
push(isExpression);
}
@override
void beginConditionalExpression(Token question) {
Expression condition = popForValue();
// This is matched by the call to [deferNode] in
// [handleConditionalExpressionColon].
typeInferrer.assignedVariables.beginNode();
push(condition);
super.beginConditionalExpression(question);
}
@override
void handleConditionalExpressionColon() {
Expression then = popForValue();
// This is matched by the call to [beginNode] in
// [beginConditionalExpression] and by the call to [storeInfo] in
// [endConditionalExpression].
push(typeInferrer.assignedVariables.deferNode());
push(then);
super.handleConditionalExpressionColon();
}
@override
void endConditionalExpression(Token question, Token colon) {
debugEvent("ConditionalExpression");
Expression elseExpression = popForValue();
Expression thenExpression = pop() as Expression;
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesInfo =
pop() as AssignedVariablesNodeInfo<VariableDeclaration>;
Expression condition = pop() as Expression;
Expression node = forest.createConditionalExpression(
offsetForToken(question), condition, thenExpression, elseExpression);
push(node);
// This is matched by the call to [deferNode] in
// [handleConditionalExpressionColon].
typeInferrer.assignedVariables.storeInfo(node, assignedVariablesInfo);
}
@override
void handleThrowExpression(Token throwToken, Token endToken) {
debugEvent("ThrowExpression");
Expression expression = popForValue();
if (constantContext != ConstantContext.none) {
push(buildProblem(
fasta.templateNotConstantExpression.withArguments('Throw'),
throwToken.offset,
throwToken.length));
} else {
push(forest.createThrow(offsetForToken(throwToken), expression));
}
}
@override
void beginFormalParameter(Token token, MemberKind kind, Token? requiredToken,
Token? covariantToken, Token? varFinalOrConst) {
// TODO(danrubel): handle required token
if (!libraryBuilder.isNonNullableByDefault) {
reportNonNullableModifierError(requiredToken);
}
push((covariantToken != null ? covariantMask : 0) |
(requiredToken != null ? requiredMask : 0) |
Modifier.validateVarFinalOrConst(varFinalOrConst?.lexeme));
push(varFinalOrConst ?? NullValue.Token);
}
@override
void endFormalParameter(
Token? thisKeyword,
Token? superKeyword,
Token? periodAfterThisOrSuper,
Token nameToken,
Token? initializerStart,
Token? initializerEnd,
FormalParameterKind kind,
MemberKind memberKind) {
debugEvent("FormalParameter");
if (thisKeyword != null) {
if (!inConstructor) {
handleRecoverableError(fasta.messageFieldInitializerOutsideConstructor,
thisKeyword, thisKeyword);
thisKeyword = null;
}
}
if (superKeyword != null) {
if (!inConstructor) {
handleRecoverableError(
fasta.messageSuperParameterInitializerOutsideConstructor,
superKeyword,
superKeyword);
superKeyword = null;
}
}
Object? nameNode = pop();
TypeBuilder? type = pop() as TypeBuilder?;
Token? varOrFinalOrConst = pop(NullValue.Token) as Token?;
if (superKeyword != null &&
varOrFinalOrConst != null &&
optional('var', varOrFinalOrConst)) {
handleRecoverableError(
fasta.templateExtraneousModifier.withArguments(varOrFinalOrConst),
varOrFinalOrConst,
varOrFinalOrConst);
}
int modifiers = pop() as int;
if (inCatchClause) {
modifiers |= finalMask;
}
List<Expression>? annotations = pop() as List<Expression>?;
if (nameNode is ParserRecovery) {
push(nameNode);
return;
}
Identifier? name = nameNode as Identifier?;
FormalParameterBuilder? parameter;
if (!inCatchClause &&
functionNestingLevel == 0 &&
memberKind != MemberKind.GeneralizedFunctionType) {
SourceFunctionBuilder member = this.member as SourceFunctionBuilder;
parameter = member.getFormal(name!);
if (parameter == null) {
// This happens when the list of formals (originally) contains a
// ParserRecovery - then the popped list becomes null.
push(new ParserRecovery(nameToken.charOffset));
return;
}
} else {
parameter = new FormalParameterBuilder(null, kind, modifiers, type,
name?.name ?? '', libraryBuilder, offsetForToken(nameToken),
fileUri: uri)
..hasDeclaredInitializer = (initializerStart != null);
}
VariableDeclaration variable = parameter.build(libraryBuilder);
Expression? initializer = name?.initializer;
if (initializer != null) {
if (member is RedirectingFactoryBuilder) {
RedirectingFactoryBuilder factory = member as RedirectingFactoryBuilder;
addProblem(
fasta.templateDefaultValueInRedirectingFactoryConstructor
.withArguments(factory.redirectionTarget.fullNameForErrors),
initializer.fileOffset,
noLength);
} else {
if (!parameter.initializerWasInferred) {
variable.initializer = initializer..parent = variable;
}
}
} else if (kind != FormalParameterKind.requiredPositional) {
variable.initializer ??= forest.createNullLiteral(noLocation)
..parent = variable;
}
if (annotations != null) {
if (functionNestingLevel == 0) {
inferAnnotations(variable, annotations);
}
variable.clearAnnotations();
for (Expression annotation in annotations) {
variable.addAnnotation(annotation);
}
}
push(parameter);
typeInferrer.assignedVariables.declare(variable);
}
@override
void endOptionalFormalParameters(
int count, Token beginToken, Token endToken) {
debugEvent("OptionalFormalParameters");
// When recovering from an empty list of optional arguments, count may be
// 0. It might be simpler if the parser didn't call this method in that
// case, however, then [beginOptionalFormalParameters] wouldn't always be
// matched by this method.
List<FormalParameterBuilder>? parameters =
const FixedNullableList<FormalParameterBuilder>()
.popNonNullable(stack, count, dummyFormalParameterBuilder);
if (parameters == null) {
push(new ParserRecovery(offsetForToken(beginToken)));
} else {
push(parameters);
}
}
@override
void beginFunctionTypedFormalParameter(Token token) {
debugEvent("beginFunctionTypedFormalParameter");
functionNestingLevel++;
}
@override
void endFunctionTypedFormalParameter(Token nameToken, Token? question) {
debugEvent("FunctionTypedFormalParameter");
if (inCatchClause || functionNestingLevel != 0) {
exitLocalScope();
}
FormalParameters formals = pop() as FormalParameters;
TypeBuilder? returnType = pop() as TypeBuilder?;
List<TypeVariableBuilder>? typeVariables =
pop() as List<TypeVariableBuilder>?;
if (!libraryBuilder.isNonNullableByDefault) {
reportErrorIfNullableType(question);
}
TypeBuilder type = formals.toFunctionType(returnType,
libraryBuilder.nullableBuilderIfTrue(question != null), typeVariables);
exitLocalScope();
push(type);
functionNestingLevel--;
}
@override
void beginFormalParameterDefaultValueExpression() {
super.push(constantContext);
constantContext = ConstantContext.required;
}
@override
void endFormalParameterDefaultValueExpression() {
debugEvent("FormalParameterDefaultValueExpression");
Object? defaultValueExpression = pop();
constantContext = pop() as ConstantContext;
push(defaultValueExpression);
}
@override
void handleValuedFormalParameter(Token equals, Token token) {
debugEvent("ValuedFormalParameter");
Expression initializer = popForValue();
Object? name = pop();
if (name is ParserRecovery) {
push(name);
} else {
push(new InitializedIdentifier(name as Identifier, initializer));
}
}
@override
void handleFormalParameterWithoutValue(Token token) {
debugEvent("FormalParameterWithoutValue");
}
@override
void beginFormalParameters(Token token, MemberKind kind) {
super.push(constantContext);
constantContext = ConstantContext.none;
}
@override
void endFormalParameters(
int count, Token beginToken, Token endToken, MemberKind kind) {
debugEvent("FormalParameters");
List<FormalParameterBuilder>? optionals;
int optionalsCount = 0;
if (count > 0 && peek() is List<FormalParameterBuilder>) {
optionals = pop() as List<FormalParameterBuilder>;
count--;
optionalsCount = optionals.length;
}
List<FormalParameterBuilder>? parameters =
const FixedNullableList<FormalParameterBuilder>().popPaddedNonNullable(
stack, count, optionalsCount, dummyFormalParameterBuilder);
if (optionals != null && parameters != null) {
parameters.setRange(count, count + optionalsCount, optionals);
}
assert(parameters?.isNotEmpty ?? true);
FormalParameters formals = new FormalParameters(parameters,
offsetForToken(beginToken), lengthOfSpan(beginToken, endToken), uri);
constantContext = pop() as ConstantContext;
push(formals);
if ((inCatchClause || functionNestingLevel != 0) &&
kind != MemberKind.GeneralizedFunctionType) {
enterLocalScope('formalParameters',
formals.computeFormalParameterScope(scope, member, this));
}
}
@override
void beginCatchClause(Token token) {
debugEvent("beginCatchClause");
inCatchClause = true;
}
@override
void endCatchClause(Token token) {
debugEvent("CatchClause");
inCatchClause = false;
push(inCatchBlock);
inCatchBlock = true;
}
@override
void handleCatchBlock(Token? onKeyword, Token? catchKeyword, Token? comma) {
debugEvent("CatchBlock");
Statement body = pop() as Statement;
inCatchBlock = pop() as bool;
if (catchKeyword != null) {
exitLocalScope();
}
FormalParameters? catchParameters =
popIfNotNull(catchKeyword) as FormalParameters?;
TypeBuilder? unresolvedExceptionType =
popIfNotNull(onKeyword) as TypeBuilder?;
DartType exceptionType;
if (unresolvedExceptionType != null) {
exceptionType = buildDartType(unresolvedExceptionType, TypeUse.catchType,
allowPotentiallyConstantType: false);
} else {
exceptionType = (libraryBuilder.isNonNullableByDefault
? coreTypes.objectNonNullableRawType
: const DynamicType());
}
FormalParameterBuilder? exception;
FormalParameterBuilder? stackTrace;
List<Statement>? compileTimeErrors;
if (catchParameters?.parameters != null) {
int parameterCount = catchParameters!.parameters!.length;
if (parameterCount > 0) {
exception = catchParameters.parameters![0];
exception.build(libraryBuilder).type = exceptionType;
if (parameterCount > 1) {
stackTrace = catchParameters.parameters![1];
stackTrace.build(libraryBuilder).type =
coreTypes.stackTraceRawType(libraryBuilder.nonNullable);
}
}
if (parameterCount > 2) {
// If parameterCount is 0, the parser reported an error already.
if (parameterCount != 0) {
for (int i = 2; i < parameterCount; i++) {
FormalParameterBuilder parameter = catchParameters.parameters![i];
compileTimeErrors ??= <Statement>[];
compileTimeErrors.add(buildProblemStatement(
fasta.messageCatchSyntaxExtraParameters, parameter.charOffset,
length: parameter.name.length));
}
}
}
}
push(forest.createCatch(
offsetForToken(onKeyword ?? catchKeyword),
exceptionType,
exception?.variable,
stackTrace?.variable,
coreTypes.stackTraceRawType(libraryBuilder.nonNullable),
body));
if (compileTimeErrors == null) {
push(NullValue.Block);
} else {
push(forest.createBlock(noLocation, noLocation, compileTimeErrors));
}
}
@override
void beginTryStatement(Token token) {
// This is matched by the call to [endNode] in [endTryStatement].
typeInferrer.assignedVariables.beginNode();
}
@override
void endTryStatement(
int catchCount, Token tryKeyword, Token? finallyKeyword) {
Statement? finallyBlock;
if (finallyKeyword != null) {
finallyBlock = pop() as Statement;
} else {
// This is matched by the call to [beginNode] in [beginTryStatement].
tryStatementInfoStack = tryStatementInfoStack
.prepend(typeInferrer.assignedVariables.deferNode());
}
List<Catch>? catchBlocks;
List<Statement>? compileTimeErrors;
if (catchCount != 0) {
List<Object?> catchBlocksAndErrors =
const FixedNullableList<Object?>().pop(stack, catchCount * 2)!;
catchBlocks =
new List<Catch>.filled(catchCount, dummyCatch, growable: true);
for (int i = 0; i < catchCount; i++) {
catchBlocks[i] = catchBlocksAndErrors[i * 2] as Catch;
Statement? error = catchBlocksAndErrors[i * 2 + 1] as Statement?;
if (error != null) {
compileTimeErrors ??= <Statement>[];
compileTimeErrors.add(error);
}
}
}
Statement tryBlock = popStatement();
int fileOffset = offsetForToken(tryKeyword);
Statement result = forest.createTryStatement(
fileOffset, tryBlock, catchBlocks, finallyBlock);
typeInferrer.assignedVariables
.storeInfo(result, tryStatementInfoStack.head);
tryStatementInfoStack = tryStatementInfoStack.tail!;
if (compileTimeErrors != null) {
compileTimeErrors.add(result);
push(forest.createBlock(noLocation, noLocation, compileTimeErrors));
} else {
push(result);
}
}
@override
void handleIndexedExpression(
Token? question, Token openSquareBracket, Token closeSquareBracket) {
assert(checkState(openSquareBracket, [
unionOfKinds([ValueKinds.Expression, ValueKinds.Generator]),
unionOfKinds(
[ValueKinds.Expression, ValueKinds.Generator, ValueKinds.Initializer])
]));
debugEvent("IndexedExpression");
Expression index = popForValue();
Object? receiver = pop();
bool isNullAware = question != null;
if (isNullAware && !libraryBuilder.isNonNullableByDefault) {
reportMissingNonNullableSupport(openSquareBracket);
}
if (receiver is Generator) {
push(receiver.buildIndexedAccess(index, openSquareBracket,
isNullAware: isNullAware));
} else if (receiver is Expression) {
push(IndexedAccessGenerator.make(this, openSquareBracket, receiver, index,
isNullAware: isNullAware));
} else {
assert(receiver is Initializer);
push(IndexedAccessGenerator.make(
this, openSquareBracket, toValue(receiver), index,
isNullAware: isNullAware));
}
}
@override
void handleUnaryPrefixExpression(Token token) {
assert(checkState(token, <ValueKind>[
unionOfKinds(<ValueKind>[
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder
]),
]));
debugEvent("UnaryPrefixExpression");
Object? receiver = pop();
if (optional("!", token)) {
push(forest.createNot(offsetForToken(token), toValue(receiver)));
} else {
String operator = token.stringValue!;
if (optional("-", token)) {
operator = "unary-";
}
int fileOffset = offsetForToken(token);
Name name = new Name(operator);
if (receiver is Generator) {
push(receiver.buildUnaryOperation(token, name));
} else {
assert(receiver is Expression);
push(forest.createUnary(fileOffset, name, receiver as Expression));
}
}
}
Name incrementOperator(Token token) {
if (optional("++", token)) return plusName;
if (optional("--", token)) return minusName;
return unhandled(token.lexeme, "incrementOperator", token.charOffset, uri);
}
@override
void handleUnaryPrefixAssignmentExpression(Token token) {
debugEvent("UnaryPrefixAssignmentExpression");
Object? generator = pop();
if (generator is Generator) {
push(generator.buildPrefixIncrement(incrementOperator(token),
offset: token.charOffset));
} else {
Expression value = toValue(generator);
push(wrapInProblem(
value, fasta.messageNotAnLvalue, value.fileOffset, noLength));
}
}
@override
void handleUnaryPostfixAssignmentExpression(Token token) {
debugEvent("UnaryPostfixAssignmentExpression");
Object? generator = pop();
if (generator is Generator) {
push(new DelayedPostfixIncrement(
this, token, generator, incrementOperator(token)));
} else {
Expression value = toValue(generator);
push(wrapInProblem(
value, fasta.messageNotAnLvalue, value.fileOffset, noLength));
}
}
@override
void endConstructorReference(Token start, Token? periodBeforeName,
Token endToken, ConstructorReferenceContext constructorReferenceContext) {
debugEvent("ConstructorReference");
pushQualifiedReference(
start, periodBeforeName, constructorReferenceContext);
}
/// A qualified reference is something that matches one of:
///
/// identifier
/// identifier typeArguments? '.' identifier
/// identifier '.' identifier typeArguments? '.' identifier
///
/// That is, one to three identifiers separated by periods and optionally one
/// list of type arguments.
///
/// A qualified reference can be used to represent both a reference to
/// compile-time constant variable (metadata) or a constructor reference
/// (used by metadata, new/const expression, and redirecting factories).
///
/// Note that the parser will report errors if metadata includes type
/// arguments, but will other preserve them for error recovery.
///
/// A constructor reference can contain up to three identifiers:
///
/// a) type typeArguments?
/// b) type typeArguments? '.' name
/// c) prefix '.' type typeArguments?
/// d) prefix '.' type typeArguments? '.' name
///
/// This isn't a legal constructor reference:
///
/// type '.' name typeArguments?
///
/// But the parser can't tell this from type c) above.
///
/// This method pops 2 (or 3 if `periodBeforeName != null`) values from the
/// stack and pushes 3 values: a generator (the type in a constructor
/// reference, or an expression in metadata), a list of type arguments, and a
/// name.
void pushQualifiedReference(Token start, Token? periodBeforeName,
ConstructorReferenceContext constructorReferenceContext) {
assert(checkState(start, [
/*suffix*/ if (periodBeforeName != null)
unionOfKinds([ValueKinds.Identifier, ValueKinds.ParserRecovery]),
/*type arguments*/ ValueKinds.TypeArgumentsOrNull,
/*type*/ unionOfKinds([
ValueKinds.Generator,
ValueKinds.QualifiedName,
ValueKinds.ProblemBuilder,
ValueKinds.ParserRecovery
]),
]));
Object? suffixObject = popIfNotNull(periodBeforeName);
Identifier? suffix;
if (suffixObject is Identifier) {
suffix = suffixObject;
} else {
assert(
suffixObject == null || suffixObject is ParserRecovery,
"Unexpected qualified name suffix $suffixObject "
"(${suffixObject.runtimeType})");
// There was a `.` without a suffix.
}
Identifier? identifier;
List<TypeBuilder>? typeArguments = pop() as List<TypeBuilder>?;
Object? type = pop();
if (type is QualifiedName) {
identifier = type;
QualifiedName qualified = type;
Object qualifier = qualified.qualifier;
assert(checkValue(
start,
unionOfKinds([ValueKinds.Generator, ValueKinds.ProblemBuilder]),
qualifier));
if (qualifier is TypeUseGenerator && suffix == null) {
type = qualifier;
if (typeArguments != null) {
// TODO(ahe): Point to the type arguments instead.
addProblem(fasta.messageConstructorWithTypeArguments,
identifier.charOffset, identifier.name.length);
}
} else if (qualifier is Generator) {
if (constructorReferenceContext !=
ConstructorReferenceContext.Implicit) {
type = qualifier.qualifiedLookup(qualified.token);
} else {
type = qualifier.buildSelectorAccess(
new PropertySelector(this, qualified.token,
new Name(qualified.name, libraryBuilder.nameOrigin)),
qualified.token.charOffset,
false);
}
identifier = null;
} else if (qualifier is ProblemBuilder) {
type = qualifier;
} else {
unhandled("${qualifier.runtimeType}", "pushQualifiedReference",
start.charOffset, uri);
}
}
String name;
if (identifier != null && suffix != null) {
name = "${identifier.name}.${suffix.name}";
} else if (identifier != null) {
name = identifier.name;
} else if (suffix != null) {
name = suffix.name;
} else {
name = "";
}
// TODO(johnniwinther): Provide sufficient offsets for pointing correctly
// to prefix, class name and suffix.
push(type);
push(typeArguments ?? NullValue.TypeArguments);
push(name);
push(suffix ?? identifier ?? NullValue.Identifier);
assert(checkState(start, [
/*constructor name identifier*/ ValueKinds.IdentifierOrNull,
/*constructor name*/ ValueKinds.Name,
/*type arguments*/ ValueKinds.TypeArgumentsOrNull,
/*class*/ unionOfKinds([
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.ParserRecovery,
ValueKinds.Expression,
]),
]));
}
@override
Expression buildStaticInvocation(Member target, Arguments arguments,
{Constness constness: Constness.implicit,
TypeAliasBuilder? typeAliasBuilder,
int charOffset: -1,
int charLength: noLength}) {
// The argument checks for the initial target of redirecting factories
// invocations are skipped in Dart 1.
List<TypeParameter> typeParameters = target.function!.typeParameters;
if (target is Constructor) {
assert(!target.enclosingClass.isAbstract);
typeParameters = target.enclosingClass.typeParameters;
}
LocatedMessage? argMessage = checkArgumentsForFunction(
target.function!, arguments, charOffset, typeParameters);
if (argMessage != null) {
return buildUnresolvedError(forest.createNullLiteral(charOffset),
target.name.text, arguments, charOffset,
candidate: target, message: argMessage, kind: UnresolvedKind.Method);
}
bool isConst = constness == Constness.explicitConst ||
constantContext != ConstantContext.none;
if (target is Constructor) {
if (constantContext == ConstantContext.required &&
constness == Constness.implicit) {
addProblem(fasta.messageMissingExplicitConst, charOffset, charLength);
}
if (isConst && !target.isConst) {
return buildProblem(
fasta.messageNonConstConstructor, charOffset, charLength);
}
ConstructorInvocation node;
if (typeAliasBuilder == null) {
node = new ConstructorInvocation(target, arguments, isConst: isConst)
..fileOffset = charOffset;
libraryBuilder.checkBoundsInConstructorInvocation(
node, typeEnvironment, uri);
} else {
TypeAliasedConstructorInvocation constructorInvocation =
node = new TypeAliasedConstructorInvocation(
typeAliasBuilder, target, arguments,
isConst: isConst)
..fileOffset = charOffset;
// No type arguments were passed, so we need not check bounds.
assert(arguments.types.isEmpty);
typeAliasedConstructorInvocations.add(constructorInvocation);
}
return node;
} else {
Procedure procedure = target as Procedure;
if (procedure.isFactory) {
if (constantContext == ConstantContext.required &&
constness == Constness.implicit) {
addProblem(fasta.messageMissingExplicitConst, charOffset, charLength);
}
if (isConst && !procedure.isConst) {
return buildProblem(
fasta.messageNonConstFactory, charOffset, charLength);
}
StaticInvocation node;
if (typeAliasBuilder == null) {
FactoryConstructorInvocation factoryInvocation =
new FactoryConstructorInvocation(target, arguments,
isConst: isConst)
..fileOffset = charOffset;
libraryBuilder.checkBoundsInFactoryInvocation(
factoryInvocation, typeEnvironment, uri,
inferred: !hasExplicitTypeArguments(arguments));
redirectingFactoryInvocations.add(factoryInvocation);
node = factoryInvocation;
} else {
TypeAliasedFactoryInvocation constructorInvocation =
new TypeAliasedFactoryInvocation(
typeAliasBuilder, target, arguments,
isConst: isConst)
..fileOffset = charOffset;
// No type arguments were passed, so we need not check bounds.
assert(arguments.types.isEmpty);
typeAliasedFactoryInvocations.add(constructorInvocation);
node = constructorInvocation;
}
return node;
} else {
assert(constness == Constness.implicit);
return new StaticInvocation(target, arguments, isConst: false)
..fileOffset = charOffset;
}
}
}
@override
Expression buildExtensionMethodInvocation(
int fileOffset, Procedure target, Arguments arguments,
{required bool isTearOff}) {
List<TypeParameter> typeParameters = target.function.typeParameters;
LocatedMessage? argMessage = checkArgumentsForFunction(
target.function, arguments, fileOffset, typeParameters,
isExtensionMemberInvocation: true);
if (argMessage != null) {
return buildUnresolvedError(forest.createNullLiteral(fileOffset),
target.name.text, arguments, fileOffset,
candidate: target, message: argMessage, kind: UnresolvedKind.Method);
}
Expression node;
if (isTearOff) {
node = new ExtensionTearOff(target, arguments);
} else {
node = new StaticInvocation(target, arguments);
}
node.fileOffset = fileOffset;
return node;
}
@override
LocatedMessage? checkArgumentsForFunction(FunctionNode function,
Arguments arguments, int offset, List<TypeParameter> typeParameters,
{bool isExtensionMemberInvocation = false}) {
int requiredPositionalParameterCountToReport =
function.requiredParameterCount;
int positionalParameterCountToReport = function.positionalParameters.length;
int positionalArgumentCountToReport =
forest.argumentsPositional(arguments).length;
if (isExtensionMemberInvocation) {
// Extension member invocations have additional synthetic parameter for
// `this`.
--requiredPositionalParameterCountToReport;
--positionalParameterCountToReport;
--positionalArgumentCountToReport;
}
if (forest.argumentsPositional(arguments).length <
function.requiredParameterCount) {
return fasta.templateTooFewArguments
.withArguments(requiredPositionalParameterCountToReport,
positionalArgumentCountToReport)
.withLocation(uri, arguments.fileOffset, noLength);
}
if (forest.argumentsPositional(arguments).length >
function.positionalParameters.length) {
return fasta.templateTooManyArguments
.withArguments(
positionalParameterCountToReport, positionalArgumentCountToReport)
.withLocation(uri, arguments.fileOffset, noLength);
}
List<NamedExpression> named = forest.argumentsNamed(arguments);
if (named.isNotEmpty) {
Set<String?> parameterNames =
new Set.of(function.namedParameters.map((a) => a.name));
for (NamedExpression argument in named) {
if (!parameterNames.contains(argument.name)) {
return fasta.templateNoSuchNamedParameter
.withArguments(argument.name)
.withLocation(uri, argument.fileOffset, argument.name.length);
}
}
}
if (function.namedParameters.isNotEmpty) {
if (libraryBuilder.isNonNullableByDefault) {
Set<String> argumentNames = new Set.of(named.map((a) => a.name));
for (VariableDeclaration parameter in function.namedParameters) {
if (parameter.isRequired && !argumentNames.contains(parameter.name)) {
return fasta.templateValueForRequiredParameterNotProvidedError
.withArguments(parameter.name!)
.withLocation(uri, arguments.fileOffset, fasta.noLength);
}
}
}
}
List<DartType> types = forest.argumentsTypeArguments(arguments);
if (typeParameters.length != types.length) {
if (types.length == 0) {
// Expected `typeParameters.length` type arguments, but none given, so
// we use type inference.
} else {
// A wrong (non-zero) amount of type arguments given. That's an error.
// TODO(jensj): Position should be on type arguments instead.
return fasta.templateTypeArgumentMismatch
.withArguments(typeParameters.length)
.withLocation(uri, offset, noLength);
}
}
return null;
}
@override
LocatedMessage? checkArgumentsForType(
FunctionType function, Arguments arguments, int offset,
{bool isExtensionMemberInvocation = false}) {
int requiredPositionalParameterCountToReport =
function.requiredParameterCount;
int positionalParameterCountToReport = function.positionalParameters.length;
int positionalArgumentCountToReport =
forest.argumentsPositional(arguments).length;
if (isExtensionMemberInvocation) {
// Extension member invocations have additional synthetic parameter for
// `this`.
--requiredPositionalParameterCountToReport;
--positionalParameterCountToReport;
--positionalArgumentCountToReport;
}
if (forest.argumentsPositional(arguments).length <
function.requiredParameterCount) {
return fasta.templateTooFewArguments
.withArguments(requiredPositionalParameterCountToReport,
positionalArgumentCountToReport)
.withLocation(uri, arguments.fileOffset, noLength);
}
if (forest.argumentsPositional(arguments).length >
function.positionalParameters.length) {
return fasta.templateTooManyArguments
.withArguments(
positionalParameterCountToReport, positionalArgumentCountToReport)
.withLocation(uri, arguments.fileOffset, noLength);
}
List<NamedExpression> named = forest.argumentsNamed(arguments);
if (named.isNotEmpty) {
Set<String> names =
new Set.of(function.namedParameters.map((a) => a.name));
for (NamedExpression argument in named) {
if (!names.contains(argument.name)) {
return fasta.templateNoSuchNamedParameter
.withArguments(argument.name)
.withLocation(uri, argument.fileOffset, argument.name.length);
}
}
}
if (function.namedParameters.isNotEmpty) {
if (libraryBuilder.isNonNullableByDefault) {
Set<String> argumentNames = new Set.of(named.map((a) => a.name));
for (NamedType parameter in function.namedParameters) {
if (parameter.isRequired && !argumentNames.contains(parameter.name)) {
return fasta.templateValueForRequiredParameterNotProvidedError
.withArguments(parameter.name)
.withLocation(uri, arguments.fileOffset, fasta.noLength);
}
}
}
}
List<Object> types = forest.argumentsTypeArguments(arguments);
List<TypeParameter> typeParameters = function.typeParameters;
if (typeParameters.length != types.length && types.length != 0) {
// A wrong (non-zero) amount of type arguments given. That's an error.
// TODO(jensj): Position should be on type arguments instead.
return fasta.templateTypeArgumentMismatch
.withArguments(typeParameters.length)
.withLocation(uri, offset, noLength);
}
return null;
}
@override
void beginNewExpression(Token token) {
debugEvent("beginNewExpression");
super.push(constantContext);
if (constantContext != ConstantContext.none) {
addProblem(
fasta.templateNotConstantExpression.withArguments('New expression'),
token.charOffset,
token.length);
}
constantContext = ConstantContext.none;
}
@override
void beginConstExpression(Token token) {
debugEvent("beginConstExpression");
super.push(constantContext);
constantContext = ConstantContext.inferred;
}
@override
void beginConstLiteral(Token token) {
debugEvent("beginConstLiteral");
super.push(constantContext);
constantContext = ConstantContext.inferred;
}
@override
void beginImplicitCreationExpression(Token token) {
debugEvent("beginImplicitCreationExpression");
super.push(constantContext);
}
@override
void endConstLiteral(Token token) {
debugEvent("endConstLiteral");
Object? literal = pop();
constantContext = pop() as ConstantContext;
push(literal);
}
@override
void endNewExpression(Token token) {
debugEvent("NewExpression");
_buildConstructorReferenceInvocation(
token.next!, token.offset, Constness.explicitNew,
inMetadata: false, inImplicitCreationContext: false);
}
void _buildConstructorReferenceInvocation(
Token nameToken, int offset, Constness constness,
{required bool inMetadata, required bool inImplicitCreationContext}) {
assert(checkState(nameToken, [
/*arguments*/ ValueKinds.Arguments,
/*constructor name identifier*/ ValueKinds.IdentifierOrNull,
/*constructor name*/ ValueKinds.Name,
/*type arguments*/ ValueKinds.TypeArgumentsOrNull,
/*class*/ unionOfKinds([
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.ParserRecovery,
ValueKinds.Expression,
]),
/*previous constant context*/ ValueKinds.ConstantContext,
]));
Arguments arguments = pop() as Arguments;
Identifier? nameLastIdentifier = pop(NullValue.Identifier) as Identifier?;
Token nameLastToken = nameLastIdentifier?.token ?? nameToken;
String name = pop() as String;
List<TypeBuilder>? typeArguments = pop() as List<TypeBuilder>?;
if (inMetadata && typeArguments != null) {
if (!libraryFeatures.genericMetadata.isEnabled) {
handleRecoverableError(fasta.messageMetadataTypeArguments,
nameLastToken.next!, nameLastToken.next!);
}
}
Object? type = pop();
ConstantContext savedConstantContext = pop() as ConstantContext;
if (type is Generator) {
push(type.invokeConstructor(
typeArguments, name, arguments, nameToken, nameLastToken, constness,
inImplicitCreationContext: inImplicitCreationContext));
} else if (type is ParserRecovery) {
push(new ParserErrorGenerator(
this, nameToken, fasta.messageSyntheticToken));
} else if (type is InvalidExpression) {
push(type);
} else if (type is Expression) {
push(createInstantiationAndInvocation(
() => type, typeArguments, name, name, arguments,
instantiationOffset: offset,
invocationOffset: nameLastToken.charOffset,
inImplicitCreationContext: inImplicitCreationContext));
} else {
String? typeName;
if (type is ProblemBuilder) {
typeName = type.fullNameForErrors;
}
push(buildUnresolvedError(forest.createNullLiteral(offset),
debugName(typeName!, name), arguments, nameToken.charOffset,
kind: UnresolvedKind.Constructor));
}
constantContext = savedConstantContext;
assert(checkState(nameToken, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
])
]));
}
@override
Expression createInstantiationAndInvocation(
Expression Function() receiverFunction,
List<TypeBuilder>? typeArguments,
String className,
String constructorName,
Arguments arguments,
{required int instantiationOffset,
required int invocationOffset,
required bool inImplicitCreationContext}) {
if (libraryFeatures.constructorTearoffs.isEnabled &&
inImplicitCreationContext) {
Expression receiver = receiverFunction();
if (typeArguments != null) {
if (receiver is StaticTearOff &&
(receiver.target.isFactory ||
isTearOffLowering(receiver.target)) ||
receiver is ConstructorTearOff ||
receiver is RedirectingFactoryTearOff) {
return buildProblem(fasta.messageConstructorTearOffWithTypeArguments,
instantiationOffset, noLength);
}
receiver = forest.createInstantiation(
instantiationOffset,
receiver,
buildDartTypeArguments(typeArguments, TypeUse.tearOffTypeArgument,
allowPotentiallyConstantType: true));
}
return forest.createMethodInvocation(invocationOffset, receiver,
new Name(constructorName, libraryBuilder.nameOrigin), arguments);
} else {
if (typeArguments != null) {
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(
arguments,
buildDartTypeArguments(
typeArguments, TypeUse.constructorTypeArgument,
allowPotentiallyConstantType: false));
}
return buildUnresolvedError(
forest.createNullLiteral(instantiationOffset),
constructorNameForDiagnostics(constructorName, className: className),
arguments,
invocationOffset,
kind: UnresolvedKind.Constructor);
}
}
@override
void endImplicitCreationExpression(Token token, Token openAngleBracket) {
debugEvent("ImplicitCreationExpression");
_buildConstructorReferenceInvocation(
token, openAngleBracket.offset, Constness.implicit,
inMetadata: false, inImplicitCreationContext: true);
}
@override
Expression buildConstructorInvocation(
TypeDeclarationBuilder? type,
Token nameToken,
Token nameLastToken,
Arguments? arguments,
String name,
List<TypeBuilder>? typeArguments,
int charOffset,
Constness constness,
{bool isTypeArgumentsInForest = false,
TypeDeclarationBuilder? typeAliasBuilder,
required UnresolvedKind unresolvedKind}) {
if (arguments == null) {
return buildProblem(fasta.messageMissingArgumentList,
nameToken.charOffset, nameToken.length);
}
if (name.isNotEmpty && arguments.types.isNotEmpty) {
// TODO(ahe): Point to the type arguments instead.
addProblem(fasta.messageConstructorWithTypeArguments,
nameToken.charOffset, nameToken.length);
}
String? errorName;
LocatedMessage? message;
if (type is TypeAliasBuilder) {
errorName = debugName(type.name, name);
TypeAliasBuilder aliasBuilder = type;
int numberOfTypeParameters = aliasBuilder.typeVariablesCount;
int numberOfTypeArguments = typeArguments?.length ?? 0;
if (typeArguments != null &&
numberOfTypeParameters != numberOfTypeArguments) {
// TODO(eernst): Use position of type arguments, not nameToken.
return evaluateArgumentsBefore(
arguments,
buildProblem(
fasta.templateTypeArgumentMismatch
.withArguments(numberOfTypeParameters),
charOffset,
noLength));
}
type = aliasBuilder.unaliasDeclaration(null,
isUsedAsClass: true,
usedAsClassCharOffset: nameToken.charOffset,
usedAsClassFileUri: uri);
List<TypeBuilder> typeArgumentBuilders = [];
if (typeArguments != null) {
for (TypeBuilder typeBuilder in typeArguments) {
typeArgumentBuilders.add(typeBuilder);
}
} else {
if (aliasBuilder.typeVariablesCount > 0) {
// Raw generic type alias used for instance creation, needs inference.
ClassBuilder classBuilder;
if (type is ClassBuilder) {
classBuilder = type;
} else {
if (type is InvalidTypeDeclarationBuilder) {
LocatedMessage message = type.message;
return evaluateArgumentsBefore(
arguments,
buildProblem(message.messageObject, nameToken.charOffset,
nameToken.lexeme.length));
}
return buildUnresolvedError(forest.createNullLiteral(charOffset),
errorName, arguments, nameLastToken.charOffset,
message: message, kind: UnresolvedKind.Constructor);
}
MemberBuilder? b = classBuilder.findConstructorOrFactory(
name, charOffset, uri, libraryBuilder);
Member? target = b?.member;
if (b == null) {
// Not found. Reported below.
} else if (b is AmbiguousMemberBuilder) {
message = b.message.withLocation(uri, charOffset, noLength);
} else if (b.isConstructor) {
if (classBuilder.isAbstract) {
return evaluateArgumentsBefore(
arguments,
buildAbstractClassInstantiationError(
fasta.templateAbstractClassInstantiation
.withArguments(type.name),
type.name,
nameToken.charOffset));
}
}
if (target is Constructor ||
(target is Procedure && target.kind == ProcedureKind.Factory)) {
Expression invocation;
invocation = buildStaticInvocation(target!, arguments,
constness: constness,
typeAliasBuilder: aliasBuilder,
charOffset: nameToken.charOffset,
charLength: nameToken.length);
return invocation;
} else {
return buildUnresolvedError(forest.createNullLiteral(charOffset),
errorName, arguments, nameLastToken.charOffset,
message: message, kind: UnresolvedKind.Constructor);
}
} else {
// Empty `typeArguments` and `aliasBuilder``is non-generic, but it
// may still unalias to a class type with some type arguments.
if (type is ClassBuilder) {
List<TypeBuilder>? unaliasedTypeArgumentBuilders =
aliasBuilder.unaliasTypeArguments(const []);
if (unaliasedTypeArgumentBuilders == null) {
// TODO(eernst): This is a wrong number of type arguments,
// occurring indirectly (in an alias of an alias, etc.).
return evaluateArgumentsBefore(
arguments,
buildProblem(
fasta.templateTypeArgumentMismatch
.withArguments(numberOfTypeParameters),
nameToken.charOffset,
nameToken.length,
suppressMessage: true));
}
List<DartType> dartTypeArguments = [];
for (TypeBuilder typeBuilder in unaliasedTypeArgumentBuilders) {
dartTypeArguments.add(typeBuilder.build(
libraryBuilder, TypeUse.constructorTypeArgument));
}
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(arguments, dartTypeArguments);
}
}
}
List<DartType> typeArgumentsToCheck = const <DartType>[];
// ignore: unnecessary_null_comparison
if (typeArgumentBuilders != null && typeArgumentBuilders.isNotEmpty) {
typeArgumentsToCheck = new List.filled(
typeArgumentBuilders.length, const DynamicType(),
growable: false);
for (int i = 0; i < typeArgumentsToCheck.length; ++i) {
typeArgumentsToCheck[i] = typeArgumentBuilders[i]
.build(libraryBuilder, TypeUse.constructorTypeArgument);
}
}
DartType typeToCheck = new TypedefType(
aliasBuilder.typedef, Nullability.nonNullable, typeArgumentsToCheck);
libraryBuilder.checkBoundsInType(
typeToCheck, typeEnvironment, uri, charOffset,
allowSuperBounded: false);
if (type is ClassBuilder) {
if (typeArguments != null) {
int numberOfTypeParameters = aliasBuilder.typeVariables?.length ?? 0;
if (numberOfTypeParameters != typeArgumentBuilders.length) {
// TODO(eernst): Use position of type arguments, not nameToken.
return evaluateArgumentsBefore(
arguments,
buildProblem(
fasta.templateTypeArgumentMismatch
.withArguments(numberOfTypeParameters),
nameToken.charOffset,
nameToken.length));
}
List<TypeBuilder>? unaliasedTypeArgumentBuilders =
aliasBuilder.unaliasTypeArguments(typeArgumentBuilders);
if (unaliasedTypeArgumentBuilders == null) {
// TODO(eernst): This is a wrong number of type arguments,
// occurring indirectly (in an alias of an alias, etc.).
return evaluateArgumentsBefore(
arguments,
buildProblem(
fasta.templateTypeArgumentMismatch
.withArguments(numberOfTypeParameters),
nameToken.charOffset,
nameToken.length,
suppressMessage: true));
}
List<DartType> dartTypeArguments = [];
for (TypeBuilder typeBuilder in unaliasedTypeArgumentBuilders) {
dartTypeArguments.add(typeBuilder.build(
libraryBuilder, TypeUse.constructorTypeArgument));
}
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(arguments, dartTypeArguments);
} else {
ClassBuilder cls = type;
if (cls.typeVariables?.isEmpty ?? true) {
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(arguments, []);
} else {
if (forest.argumentsTypeArguments(arguments).isEmpty) {
// No type arguments provided to unaliased class, use defaults.
List<DartType> result = new List<DartType>.generate(
cls.typeVariables!.length,
(int i) => cls.typeVariables![i].defaultType!.build(
cls.libraryBuilder, TypeUse.constructorTypeArgument),
growable: true);
forest.argumentsSetTypeArguments(arguments, result);
}
}
}
}
} else {
if (typeArguments != null && !isTypeArgumentsInForest) {
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(
arguments,
buildDartTypeArguments(
typeArguments, TypeUse.constructorTypeArgument,
allowPotentiallyConstantType: false));
}
}
if (type is ClassBuilder) {
MemberBuilder? b =
type.findConstructorOrFactory(name, charOffset, uri, libraryBuilder);
Member? target;
if (b == null) {
// Not found. Reported below.
} else if (b is AmbiguousMemberBuilder) {
message = b.message.withLocation(uri, charOffset, noLength);
} else if (b.isConstructor) {
if (type.isAbstract) {
return evaluateArgumentsBefore(
arguments,
buildAbstractClassInstantiationError(
fasta.templateAbstractClassInstantiation
.withArguments(type.name),
type.name,
nameToken.charOffset));
}
target = b.member;
} else {
target = b.member;
}
if (type.isEnum &&
!(libraryFeatures.enhancedEnums.isEnabled &&
target is Procedure &&
target.kind == ProcedureKind.Factory)) {
return buildProblem(fasta.messageEnumInstantiation,
nameToken.charOffset, nameToken.length);
}
if (target is Constructor ||
(target is Procedure && target.kind == ProcedureKind.Factory)) {
Expression invocation;
invocation = buildStaticInvocation(target!, arguments,
constness: constness,
charOffset: nameToken.charOffset,
charLength: nameToken.length,
typeAliasBuilder: typeAliasBuilder as TypeAliasBuilder?);
return invocation;
} else {
errorName ??= debugName(type.name, name);
}
} else if (type is InvalidTypeDeclarationBuilder) {
LocatedMessage message = type.message;
return evaluateArgumentsBefore(
arguments,
buildProblem(message.messageObject, nameToken.charOffset,
nameToken.lexeme.length));
} else {
errorName ??= debugName(type!.fullNameForErrors, name);
}
return buildUnresolvedError(forest.createNullLiteral(charOffset), errorName,
arguments, nameLastToken.charOffset,
message: message, kind: unresolvedKind);
}
@override
void endConstExpression(Token token) {
debugEvent("endConstExpression");
_buildConstructorReferenceInvocation(
token.next!, token.offset, Constness.explicitConst,
inMetadata: false, inImplicitCreationContext: false);
}
@override
void handleConstFactory(Token constKeyword) {
debugEvent("ConstFactory");
if (!libraryFeatures.constFunctions.isEnabled) {
handleRecoverableError(
fasta.messageConstFactory, constKeyword, constKeyword);
}
}
@override
void beginIfControlFlow(Token ifToken) {
// TODO(danrubel): consider removing this when control flow support is added
// if the ifToken is not needed for error reporting
push(ifToken);
}
@override
void handleThenControlFlow(Token token) {
Expression condition = popForValue();
// This is matched by the call to [deferNode] in
// [handleElseControlFlow] and by the call to [endNode] in
// [endIfControlFlow].
typeInferrer.assignedVariables.beginNode();
push(condition);
super.handleThenControlFlow(token);
}
@override
void handleElseControlFlow(Token elseToken) {
// Resolve the top of the stack so that if it's a delayed assignment it
// happens before we go into the else block.
Object? node = pop();
if (node is! MapLiteralEntry) node = toValue(node);
// This is matched by the call to [beginNode] in
// [handleThenControlFlow] and by the call to [storeInfo] in
// [endIfElseControlFlow].
push(typeInferrer.assignedVariables.deferNode());
push(node);
}
@override
void endIfControlFlow(Token token) {
debugEvent("endIfControlFlow");
Object? entry = pop();
Object? condition = pop(); // parenthesized expression
Token ifToken = pop() as Token;
transformCollections = true;
TreeNode node;
if (entry is MapLiteralEntry) {
node = forest.createIfMapEntry(
offsetForToken(ifToken), toValue(condition), entry);
} else {
node = forest.createIfElement(
offsetForToken(ifToken), toValue(condition), toValue(entry));
}
push(node);
// This is matched by the call to [beginNode] in
// [handleThenControlFlow].
typeInferrer.assignedVariables.endNode(node);
}
@override
void endIfElseControlFlow(Token token) {
debugEvent("endIfElseControlFlow");
Object? elseEntry = pop(); // else entry
Object? thenEntry = pop(); // then entry
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesInfo =
pop() as AssignedVariablesNodeInfo<VariableDeclaration>;
Object? condition = pop(); // parenthesized expression
Token ifToken = pop() as Token;
transformCollections = true;
TreeNode node;
if (thenEntry is MapLiteralEntry) {
if (elseEntry is MapLiteralEntry) {
node = forest.createIfMapEntry(
offsetForToken(ifToken), toValue(condition), thenEntry, elseEntry);
} else if (elseEntry is ControlFlowElement) {
MapLiteralEntry? elseMapEntry = elseEntry
.toMapLiteralEntry(typeInferrer.assignedVariables.reassignInfo);
if (elseMapEntry != null) {
node = forest.createIfMapEntry(offsetForToken(ifToken),
toValue(condition), thenEntry, elseMapEntry);
} else {
int offset = elseEntry.fileOffset;
node = new MapLiteralEntry(
buildProblem(
fasta.messageCantDisambiguateAmbiguousInformation, offset, 1),
new NullLiteral())
..fileOffset = offsetForToken(ifToken);
}
} else {
int offset = elseEntry is Expression
? elseEntry.fileOffset
: offsetForToken(ifToken);
node = new MapLiteralEntry(
buildProblem(fasta.templateExpectedAfterButGot.withArguments(':'),
offset, 1),
new NullLiteral())
..fileOffset = offsetForToken(ifToken);
}
} else if (elseEntry is MapLiteralEntry) {
if (thenEntry is ControlFlowElement) {
MapLiteralEntry? thenMapEntry = thenEntry
.toMapLiteralEntry(typeInferrer.assignedVariables.reassignInfo);
if (thenMapEntry != null) {
node = forest.createIfMapEntry(offsetForToken(ifToken),
toValue(condition), thenMapEntry, elseEntry);
} else {
int offset = thenEntry.fileOffset;
node = new MapLiteralEntry(
buildProblem(
fasta.messageCantDisambiguateAmbiguousInformation, offset, 1),
new NullLiteral())
..fileOffset = offsetForToken(ifToken);
}
} else {
int offset = thenEntry is Expression
? thenEntry.fileOffset
: offsetForToken(ifToken);
node = new MapLiteralEntry(
buildProblem(fasta.templateExpectedAfterButGot.withArguments(':'),
offset, 1),
new NullLiteral())
..fileOffset = offsetForToken(ifToken);
}
} else {
node = forest.createIfElement(offsetForToken(ifToken), toValue(condition),
toValue(thenEntry), toValue(elseEntry));
}
push(node);
// This is matched by the call to [deferNode] in
// [handleElseControlFlow].
typeInferrer.assignedVariables.storeInfo(node, assignedVariablesInfo);
}
@override
void handleSpreadExpression(Token spreadToken) {
debugEvent("SpreadExpression");
Object? expression = pop();
transformCollections = true;
push(forest.createSpreadElement(
offsetForToken(spreadToken), toValue(expression),
isNullAware: spreadToken.lexeme == '...?'));
}
@override
void endTypeArguments(int count, Token beginToken, Token endToken) {
debugEvent("TypeArguments");
push(const FixedNullableList<TypeBuilder>()
.popNonNullable(stack, count, dummyTypeBuilder) ??
NullValue.TypeArguments);
}
@override
void handleInvalidTypeArguments(Token token) {
debugEvent("InvalidTypeArguments");
pop(NullValue.TypeArguments);
}
@override
void handleThisExpression(Token token, IdentifierContext context) {
debugEvent("ThisExpression");
if (context.isScopeReference && isDeclarationInstanceContext) {
if (extensionThis != null) {
push(_createReadOnlyVariableAccess(extensionThis!, token,
offsetForToken(token), 'this', ReadOnlyAccessKind.ExtensionThis));
} else {
push(new ThisAccessGenerator(this, token, inInitializerLeftHandSide,
inFieldInitializer, inLateFieldInitializer));
}
} else {
push(new IncompleteErrorGenerator(
this, token, fasta.messageThisAsIdentifier));
}
}
@override
void handleSuperExpression(Token token, IdentifierContext context) {
debugEvent("SuperExpression");
if (context.isScopeReference &&
isDeclarationInstanceContext &&
extensionThis == null) {
MemberBuilder memberBuilder = member as MemberBuilder;
memberBuilder.member.transformerFlags |= TransformerFlag.superCalls;
push(new ThisAccessGenerator(this, token, inInitializerLeftHandSide,
inFieldInitializer, inLateFieldInitializer,
isSuper: true));
} else {
push(new IncompleteErrorGenerator(
this, token, fasta.messageSuperAsIdentifier));
}
}
@override
void handleAugmentSuperExpression(
Token augmentToken, Token superToken, IdentifierContext context) {
debugEvent("AugmentSuperExpression");
if (member is SourceMemberBuilder) {
SourceMemberBuilder sourceMemberBuilder = member as SourceMemberBuilder;
if (sourceMemberBuilder.isAugmentation) {
// TODO(johnniwinther): Implement augment super handling.
int fileOffset = augmentToken.charOffset;
push(forest.createAsExpression(fileOffset,
forest.createNullLiteral(fileOffset), const DynamicType(),
forNonNullableByDefault: libraryBuilder.isNonNullableByDefault));
return;
}
}
push(new IncompleteErrorGenerator(
this, augmentToken, fasta.messageInvalidAugmentSuper));
}
@override
void handleNamedArgument(Token colon) {
debugEvent("NamedArgument");
assert(checkState(colon, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
]),
unionOfKinds([
ValueKinds.Identifier,
ValueKinds.ParserRecovery,
])
]));
Expression value = popForValue();
Object? identifier = pop();
if (identifier is Identifier) {
push(new NamedExpression(identifier.name, value)
..fileOffset = identifier.charOffset);
} else {
assert(
identifier is ParserRecovery,
"Unexpected argument name: "
"${identifier} (${identifier.runtimeType})");
push(identifier);
}
}
@override
void endFunctionName(Token beginToken, Token token) {
debugEvent("FunctionName");
Identifier name = pop() as Identifier;
Token nameToken = name.token;
VariableDeclaration variable = new VariableDeclarationImpl(name.name,
forSyntheticToken: nameToken.isSynthetic,
isFinal: true,
isLocalFunction: true)
..fileOffset = name.charOffset;
// TODO(ahe): Why are we looking up in local scope, but declaring in parent
// scope?
Builder? existing = scope.lookupLocalMember(name.name, setter: false);
if (existing != null) {
reportDuplicatedDeclaration(existing, name.name, name.charOffset);
}
push(new FunctionDeclarationImpl(
variable,
// The real function node is created later.
dummyFunctionNode)
..fileOffset = beginToken.charOffset);
declareVariable(variable, scope.parent!);
}
void enterFunction() {
_enterLocalState();
debugEvent("enterFunction");
functionNestingLevel++;
push(switchScope ?? NullValue.SwitchScope);
switchScope = null;
push(inCatchBlock);
inCatchBlock = false;
// This is matched by the call to [endNode] in [pushNamedFunction] or
// [endFunctionExpression].
typeInferrer.assignedVariables.beginNode();
assert(checkState(null, [
/* inCatchBlock */ ValueKinds.Bool,
/* switch scope */ ValueKinds.SwitchScopeOrNull,
]));
}
void exitFunction() {
assert(checkState(null, [
/* inCatchBlock */ ValueKinds.Bool,
/* switch scope */ ValueKinds.SwitchScopeOrNull,
/* function type variables */ ValueKinds.TypeVariableListOrNull,
/* function block scope */ ValueKinds.Scope,
]));
debugEvent("exitFunction");
functionNestingLevel--;
inCatchBlock = pop() as bool;
switchScope = pop() as Scope?;
List<TypeVariableBuilder>? typeVariables =
pop() as List<TypeVariableBuilder>?;
exitLocalScope();
push(typeVariables ?? NullValue.TypeVariables);
_exitLocalState();
assert(checkState(null, [
ValueKinds.TypeVariableListOrNull,
]));
}
@override
void beginLocalFunctionDeclaration(Token token) {
debugEvent("beginLocalFunctionDeclaration");
enterFunction();
}
@override
void beginNamedFunctionExpression(Token token) {
debugEvent("beginNamedFunctionExpression");
List<TypeVariableBuilder>? typeVariables =
pop() as List<TypeVariableBuilder>?;
// Create an additional scope in which the named function expression is
// declared.
enterLocalScope("named function");
push(typeVariables ?? NullValue.TypeVariables);
enterFunction();
}
@override
void beginFunctionExpression(Token token) {
debugEvent("beginFunctionExpression");
enterFunction();
}
void pushNamedFunction(Token token, bool isFunctionExpression) {
Statement body = popStatement();
AsyncMarker asyncModifier = pop() as AsyncMarker;
exitLocalScope();
FormalParameters formals = pop() as FormalParameters;
Object? declaration = pop();
TypeBuilder? returnType = pop() as TypeBuilder?;
bool hasImplicitReturnType = returnType == null;
exitFunction();
List<TypeVariableBuilder>? typeParameters =
pop() as List<TypeVariableBuilder>?;
List<Expression>? annotations;
if (!isFunctionExpression) {
annotations = pop() as List<Expression>?; // Metadata.
}
FunctionNode function = formals.buildFunctionNode(libraryBuilder,
returnType, typeParameters, asyncModifier, body, token.charOffset);
if (declaration is FunctionDeclaration) {
VariableDeclaration variable = declaration.variable;
if (annotations != null) {
for (Expression annotation in annotations) {
variable.addAnnotation(annotation);
}
}
FunctionDeclarationImpl.setHasImplicitReturnType(
declaration as FunctionDeclarationImpl, hasImplicitReturnType);
if (!hasImplicitReturnType) {
checkAsyncReturnType(asyncModifier, function.returnType,
variable.fileOffset, variable.name!.length);
}
variable.type = function.computeFunctionType(libraryBuilder.nonNullable);
declaration.function = function;
function.parent = declaration;
Statement statement;
if (variable.initializer != null) {
// This must have been a compile-time error.
assert(isErroneousNode(variable.initializer!));
statement =
forest.createBlock(declaration.fileOffset, noLocation, <Statement>[
forest.createExpressionStatement(
offsetForToken(token), variable.initializer!),
declaration
]);
variable.initializer = null;
} else {
statement = declaration;
}
// This is matched by the call to [beginNode] in [enterFunction].
typeInferrer.assignedVariables
.endNode(declaration, isClosureOrLateVariableInitializer: true);
if (isFunctionExpression) {
// This is an error case. An expression is expected but we got a
// function declaration instead. We wrap it in a [BlockExpression].
exitLocalScope();
push(new BlockExpression(
forest.createBlock(declaration.fileOffset, noLocation, [statement]),
buildProblem(fasta.messageNamedFunctionExpression,
declaration.fileOffset, noLength,
// Error has already been reported by the parser.
suppressMessage: true)));
} else {
push(statement);
}
} else {
unhandled("${declaration.runtimeType}", "pushNamedFunction",
token.charOffset, uri);
}
}
@override
void endNamedFunctionExpression(Token endToken) {
debugEvent("NamedFunctionExpression");
pushNamedFunction(endToken, true);
}
@override
void endLocalFunctionDeclaration(Token token) {
debugEvent("LocalFunctionDeclaration");
pushNamedFunction(token, false);
}
@override
void endFunctionExpression(Token beginToken, Token token) {
debugEvent("FunctionExpression");
assert(checkState(beginToken, [
/* body */ ValueKinds.StatementOrNull,
/* async marker */ ValueKinds.AsyncMarker,
/* function type scope */ ValueKinds.Scope,
/* formal parameters */ ValueKinds.FormalParameters,
/* inCatchBlock */ ValueKinds.Bool,
/* switch scope */ ValueKinds.SwitchScopeOrNull,
/* function type variables */ ValueKinds.TypeVariableListOrNull,
/* function block scope */ ValueKinds.Scope,
]));
Statement body = popNullableStatement() ??
// In erroneous cases, there might not be function body. In such cases
// we use an empty statement instead.
forest.createEmptyStatement(token.charOffset);
AsyncMarker asyncModifier = pop() as AsyncMarker;
exitLocalScope();
FormalParameters formals = pop() as FormalParameters;
exitFunction();
List<TypeVariableBuilder>? typeParameters =
pop() as List<TypeVariableBuilder>?;
FunctionNode function = formals.buildFunctionNode(libraryBuilder, null,
typeParameters, asyncModifier, body, token.charOffset)
..fileOffset = beginToken.charOffset;
Expression result;
if (constantContext != ConstantContext.none) {
result = buildProblem(fasta.messageNotAConstantExpression,
formals.charOffset, formals.length);
} else {
result = new FunctionExpression(function)
..fileOffset = offsetForToken(beginToken);
}
push(result);
// This is matched by the call to [beginNode] in [enterFunction].
typeInferrer.assignedVariables
.endNode(result, isClosureOrLateVariableInitializer: true);
assert(checkState(beginToken, [
/* function expression or problem */ ValueKinds.Expression,
]));
}
@override
void beginDoWhileStatement(Token token) {
debugEvent("beginDoWhileStatement");
// This is matched by the [endNode] call in [endDoWhileStatement].
typeInferrer.assignedVariables.beginNode();
enterLoop(token.charOffset);
}
@override
void endDoWhileStatement(
Token doKeyword, Token whileKeyword, Token endToken) {
debugEvent("DoWhileStatement");
Expression condition = popForValue();
Statement body = popStatement();
JumpTarget continueTarget = exitContinueTarget()!;
JumpTarget breakTarget = exitBreakTarget()!;
List<BreakStatementImpl>? continueStatements;
if (continueTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(body);
continueStatements =
continueTarget.resolveContinues(forest, labeledStatement);
body = labeledStatement;
}
Statement doStatement =
forest.createDoStatement(offsetForToken(doKeyword), body, condition);
// This is matched by the [beginNode] call in [beginDoWhileStatement].
typeInferrer.assignedVariables.endNode(doStatement);
if (continueStatements != null) {
for (BreakStatementImpl continueStatement in continueStatements) {
continueStatement.targetStatement = doStatement;
}
}
Statement result = doStatement;
if (breakTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(result);
breakTarget.resolveBreaks(forest, labeledStatement, doStatement);
result = labeledStatement;
}
exitLoopOrSwitch(result);
}
@override
void beginForInExpression(Token token) {
enterLocalScope('forIn', scope.parent);
}
@override
void endForInExpression(Token token) {
debugEvent("ForInExpression");
Expression expression = popForValue();
exitLocalScope();
push(expression);
}
@override
void handleForInLoopParts(Token? awaitToken, Token forToken,
Token leftParenthesis, Token inKeyword) {
push(awaitToken ?? NullValue.AwaitToken);
push(forToken);
push(inKeyword);
// This is matched by the call to [deferNode] in [endForIn] or
// [endForInControlFlow].
typeInferrer.assignedVariables.beginNode();
}
@override
void endForInControlFlow(Token token) {
debugEvent("ForInControlFlow");
Object? entry = pop();
Token inToken = pop() as Token;
Token forToken = pop() as Token;
Token? awaitToken = pop(NullValue.AwaitToken) as Token?;
if (constantContext != ConstantContext.none) {
popForValue(); // Pop iterable
pop(); // Pop lvalue
exitLocalScope();
typeInferrer.assignedVariables.discardNode();
handleRecoverableError(
fasta.templateCantUseControlFlowOrSpreadAsConstant
.withArguments(forToken),
forToken,
forToken);
push(invalidCollectionElement);
return;
}
// This is matched by the call to [beginNode] in [handleForInLoopParts].
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesNodeInfo =
typeInferrer.assignedVariables.popNode();
Expression iterable = popForValue();
Object? lvalue = pop(); // lvalue
exitLocalScope();
transformCollections = true;
ForInElements elements =
_computeForInElements(forToken, inToken, lvalue, null);
typeInferrer.assignedVariables.pushNode(assignedVariablesNodeInfo);
VariableDeclaration variable = elements.variable;
Expression? problem = elements.expressionProblem;
if (entry is MapLiteralEntry) {
ForInMapEntry result = forest.createForInMapEntry(
offsetForToken(forToken),
variable,
iterable,
elements.syntheticAssignment,
elements.expressionEffects,
entry,
problem,
isAsync: awaitToken != null);
typeInferrer.assignedVariables.endNode(result);
push(result);
} else {
ForInElement result = forest.createForInElement(
offsetForToken(forToken),
variable,
iterable,
elements.syntheticAssignment,
elements.expressionEffects,
toValue(entry),
problem,
isAsync: awaitToken != null);
typeInferrer.assignedVariables.endNode(result);
push(result);
}
}
ForInElements _computeForInElements(
Token forToken, Token inToken, Object? lvalue, Statement? body) {
ForInElements elements = new ForInElements();
if (lvalue is VariableDeclaration) {
// Late for-in variables are not supported. An error has already been
// reported by the parser.
lvalue.isLate = false;
elements.explicitVariableDeclaration = lvalue;
if (lvalue.isConst) {
elements.expressionProblem = buildProblem(
fasta.messageForInLoopWithConstVariable,
lvalue.fileOffset,
lvalue.name!.length);
// As a recovery step, remove the const flag, to not confuse the
// constant evaluator further in the pipeline.
lvalue.isConst = false;
}
} else {
VariableDeclaration variable = elements.syntheticVariableDeclaration =
forest.createVariableDeclaration(offsetForToken(forToken), null,
isFinal: true);
if (lvalue is Generator) {
/// We are in this case, where `lvalue` isn't a [VariableDeclaration]:
///
/// for (lvalue in expression) body
///
/// This is normalized to:
///
/// for (final #t in expression) {
/// lvalue = #t;
/// body;
/// }
elements.syntheticAssignment = lvalue.buildAssignment(
new VariableGetImpl(variable, forNullGuardedAccess: false)
..fileOffset = inToken.offset,
voidContext: true);
} else {
Message message = forest.isVariablesDeclaration(lvalue)
? fasta.messageForInLoopExactlyOneVariable
: fasta.messageForInLoopNotAssignable;
Token token = forToken.next!.next!;
elements.expressionProblem =
buildProblem(message, offsetForToken(token), lengthForToken(token));
Statement effects;
if (forest.isVariablesDeclaration(lvalue)) {
effects = forest.createBlock(
noLocation,
noLocation,
// New list because the declarations are not a growable list.
new List<Statement>.of(
forest.variablesDeclarationExtractDeclarations(lvalue)));
} else {
effects = forest.createExpressionStatement(
noLocation, lvalue as Expression);
}
elements.expressionEffects = combineStatements(
forest.createExpressionStatement(
noLocation,
buildProblem(
message, offsetForToken(token), lengthForToken(token))),
effects);
}
}
return elements;
}
@override
void endForIn(Token endToken) {
debugEvent("ForIn");
Statement body = popStatement();
Token inKeyword = pop() as Token;
Token forToken = pop() as Token;
Token? awaitToken = pop(NullValue.AwaitToken) as Token?;
// This is matched by the call to [beginNode] in [handleForInLoopParts].
AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesNodeInfo =
typeInferrer.assignedVariables.deferNode();
Expression expression = popForValue();
Object? lvalue = pop();
exitLocalScope();
JumpTarget continueTarget = exitContinueTarget()!;
JumpTarget breakTarget = exitBreakTarget()!;
List<BreakStatementImpl>? continueStatements;
if (continueTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(body);
continueStatements =
continueTarget.resolveContinues(forest, labeledStatement);
body = labeledStatement;
}
ForInElements elements =
_computeForInElements(forToken, inKeyword, lvalue, body);
VariableDeclaration variable = elements.variable;
Expression? problem = elements.expressionProblem;
Statement forInStatement;
if (elements.explicitVariableDeclaration != null) {
forInStatement = new ForInStatement(variable, expression, body,
isAsync: awaitToken != null)
..fileOffset = awaitToken?.charOffset ?? forToken.charOffset
..bodyOffset = body.fileOffset; // TODO(ahe): Isn't this redundant?
} else {
forInStatement = new ForInStatementWithSynthesizedVariable(
variable,
expression,
elements.syntheticAssignment,
elements.expressionEffects,
body,
isAsync: awaitToken != null,
hasProblem: problem != null)
..fileOffset = awaitToken?.charOffset ?? forToken.charOffset
..bodyOffset = body.fileOffset; // TODO(ahe): Isn't this redundant?
}
typeInferrer.assignedVariables
.storeInfo(forInStatement, assignedVariablesNodeInfo);
if (continueStatements != null) {
for (BreakStatementImpl continueStatement in continueStatements) {
continueStatement.targetStatement = forInStatement;
}
}
Statement result = forInStatement;
if (breakTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(result);
breakTarget.resolveBreaks(forest, labeledStatement, forInStatement);
result = labeledStatement;
}
if (problem != null) {
result = combineStatements(
forest.createExpressionStatement(noLocation, problem), result);
}
exitLoopOrSwitch(result);
}
@override
void handleLabel(Token token) {
debugEvent("Label");
Identifier identifier = pop() as Identifier;
push(new Label(identifier.name, identifier.charOffset));
}
@override
void beginLabeledStatement(Token token, int labelCount) {
debugEvent("beginLabeledStatement");
List<Label>? labels = const FixedNullableList<Label>()
.popNonNullable(stack, labelCount, dummyLabel);
enterLocalScope('labeledStatement', scope.createNestedLabelScope());
LabelTarget target =
new LabelTarget(functionNestingLevel, uri, token.charOffset);
if (labels != null) {
for (Label label in labels) {
scope.declareLabel(label.name, target);
}
}
push(target);
}
@override
void endLabeledStatement(int labelCount) {
debugEvent("LabeledStatement");
Statement statement = pop() as Statement;
LabelTarget target = pop() as LabelTarget;
exitLocalScope();
if (target.breakTarget.hasUsers || target.continueTarget.hasUsers) {
if (forest.isVariablesDeclaration(statement)) {
internalProblem(
fasta.messageInternalProblemLabelUsageInVariablesDeclaration,
statement.fileOffset,
uri);
}
if (statement is! LabeledStatement) {
statement = forest.createLabeledStatement(statement);
}
target.breakTarget.resolveBreaks(forest, statement, statement);
List<BreakStatementImpl>? continueStatements =
target.continueTarget.resolveContinues(forest, statement);
if (continueStatements != null) {
for (BreakStatementImpl continueStatement in continueStatements) {
continueStatement.targetStatement = statement;
}
}
}
push(statement);
}
@override
void endRethrowStatement(Token rethrowToken, Token endToken) {
debugEvent("RethrowStatement");
if (inCatchBlock) {
push(forest.createRethrowStatement(
offsetForToken(rethrowToken), offsetForToken(endToken)));
} else {
push(new ExpressionStatement(buildProblem(fasta.messageRethrowNotCatch,
offsetForToken(rethrowToken), lengthForToken(rethrowToken)))
..fileOffset = offsetForToken(rethrowToken));
}
}
@override
void handleFinallyBlock(Token finallyKeyword) {
debugEvent("FinallyBlock");
// Do nothing, handled by [endTryStatement].
}
@override
void beginWhileStatement(Token token) {
debugEvent("beginWhileStatement");
// This is matched by the [endNode] call in [endWhileStatement].
typeInferrer.assignedVariables.beginNode();
enterLoop(token.charOffset);
}
@override
void endWhileStatement(Token whileKeyword, Token endToken) {
debugEvent("WhileStatement");
Statement body = popStatement();
Expression condition = popForValue();
JumpTarget continueTarget = exitContinueTarget()!;
JumpTarget breakTarget = exitBreakTarget()!;
List<BreakStatementImpl>? continueStatements;
if (continueTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(body);
continueStatements =
continueTarget.resolveContinues(forest, labeledStatement);
body = labeledStatement;
}
Statement whileStatement = forest.createWhileStatement(
offsetForToken(whileKeyword), condition, body);
if (continueStatements != null) {
for (BreakStatementImpl continueStatement in continueStatements) {
continueStatement.targetStatement = whileStatement;
}
}
Statement result = whileStatement;
if (breakTarget.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(result);
breakTarget.resolveBreaks(forest, labeledStatement, whileStatement);
result = labeledStatement;
}
exitLoopOrSwitch(result);
// This is matched by the [beginNode] call in [beginWhileStatement].
typeInferrer.assignedVariables.endNode(whileStatement);
}
@override
void handleEmptyStatement(Token token) {
debugEvent("EmptyStatement");
push(forest.createEmptyStatement(offsetForToken(token)));
}
@override
void beginAssert(Token assertKeyword, Assert kind) {
debugEvent("beginAssert");
// If in an assert initializer, make sure [inInitializer] is false so we
// use the formal parameter scope. If this is any other kind of assert,
// inInitializer should be false anyway.
inInitializerLeftHandSide = false;
}
@override
void endAssert(Token assertKeyword, Assert kind, Token leftParenthesis,
Token? commaToken, Token semicolonToken) {
debugEvent("Assert");
Expression? message = popForValueIfNotNull(commaToken);
Expression condition = popForValue();
int fileOffset = offsetForToken(assertKeyword);
/// Return a representation of an assert that appears as a statement.
AssertStatement createAssertStatement() {
// Compute start and end offsets for the condition expression.
// This code is a temporary workaround because expressions don't carry
// their start and end offsets currently.
//
// The token that follows leftParenthesis is considered to be the
// first token of the condition.
// TODO(ahe): this really should be condition.fileOffset.
int startOffset = leftParenthesis.next!.offset;
int endOffset;
// Search forward from leftParenthesis to find the last token of
// the condition - which is a token immediately followed by a commaToken,
// right parenthesis or a trailing comma.
Token? conditionBoundary = commaToken ?? leftParenthesis.endGroup;
Token conditionLastToken = leftParenthesis;
while (!conditionLastToken.isEof) {
Token nextToken = conditionLastToken.next!;
if (nextToken == conditionBoundary) {
break;
} else if (optional(',', nextToken) &&
nextToken.next == conditionBoundary) {
// The next token is trailing comma, which means current token is
// the last token of the condition.
break;
}
conditionLastToken = nextToken;
}
if (conditionLastToken.isEof) {
endOffset = startOffset = -1;
} else {
endOffset = conditionLastToken.offset + conditionLastToken.length;
}
return forest.createAssertStatement(
fileOffset, condition, message, startOffset, endOffset);
}
switch (kind) {
case Assert.Statement:
push(createAssertStatement());
break;
case Assert.Expression:
// The parser has already reported an error indicating that assert
// cannot be used in an expression.
push(buildProblem(
fasta.messageAssertAsExpression, fileOffset, assertKeyword.length));
break;
case Assert.Initializer:
push(forest.createAssertInitializer(
fileOffset, createAssertStatement()));
break;
}
}
@override
void endYieldStatement(Token yieldToken, Token? starToken, Token endToken) {
debugEvent("YieldStatement");
push(forest.createYieldStatement(offsetForToken(yieldToken), popForValue(),
isYieldStar: starToken != null));
}
@override
void beginSwitchBlock(Token token) {
debugEvent("beginSwitchBlock");
// This is matched by the [endNode] call in [endSwitchStatement].
typeInferrer.assignedVariables.beginNode();
enterLocalScope("switch block");
enterSwitchScope();
enterBreakTarget(token.charOffset);
}
@override
void beginSwitchCase(int labelCount, int expressionCount, Token firstToken) {
debugEvent("beginSwitchCase");
int count = labelCount + expressionCount;
List<Object>? labelsAndExpressions = const FixedNullableList<Object>()
.popNonNullable(stack, count, dummyLabel);
List<Label>? labels =
labelCount == 0 ? null : new List<Label>.filled(labelCount, dummyLabel);
List<Expression> expressions = new List<Expression>.filled(
expressionCount, dummyExpression,
growable: true);
int labelIndex = 0;
int expressionIndex = 0;
if (labelsAndExpressions != null) {
for (Object labelOrExpression in labelsAndExpressions) {
if (labelOrExpression is Label) {
labels![labelIndex++] = labelOrExpression;
} else {
expressions[expressionIndex++] = labelOrExpression as Expression;
}
}
}
assert(scope == switchScope);
if (labels != null) {
for (Label label in labels) {
String labelName = label.name;
if (scope.hasLocalLabel(labelName)) {
// TODO(ahe): Should validate this is a goto target.
if (!scope.claimLabel(labelName)) {
addProblem(
fasta.templateDuplicateLabelInSwitchStatement
.withArguments(labelName),
label.charOffset,
labelName.length);
}
} else {
scope.declareLabel(
labelName, createGotoTarget(firstToken.charOffset));
}
}
}
push(expressions);
push(labels ?? NullValue.Labels);
enterLocalScope("switch case");
}
@override
void endSwitchCase(
int labelCount,
int expressionCount,
Token? defaultKeyword,
Token? colonAfterDefault,
int statementCount,
Token firstToken,
Token endToken) {
debugEvent("SwitchCase");
// We always create a block here so that we later know that there's always
// one synthetic block when we finish compiling the switch statement and
// check this switch case to see if it falls through to the next case.
Statement block = popBlock(statementCount, firstToken, null);
exitLocalScope();
List<Label>? labels = pop() as List<Label>?;
List<Expression> expressions = pop() as List<Expression>;
List<int> expressionOffsets = <int>[];
for (Expression expression in expressions) {
expressionOffsets.add(expression.fileOffset);
}
assert(labels == null || labels.isNotEmpty);
push(new SwitchCaseImpl(expressions, expressionOffsets, block,
isDefault: defaultKeyword != null, hasLabel: labels != null)
..fileOffset = firstToken.charOffset);
push(labels ?? NullValue.Labels);
}
@override
void endSwitchStatement(Token switchKeyword, Token endToken) {
debugEvent("SwitchStatement");
List<SwitchCase> cases = pop() as List<SwitchCase>;
JumpTarget target = exitBreakTarget()!;
exitSwitchScope();
exitLocalScope();
Expression expression = popForValue();
Statement switchStatement = new SwitchStatement(expression, cases)
..fileOffset = switchKeyword.charOffset;
Statement result = switchStatement;
if (target.hasUsers) {
LabeledStatement labeledStatement = forest.createLabeledStatement(result);
target.resolveBreaks(forest, labeledStatement, switchStatement);
result = labeledStatement;
}
exitLoopOrSwitch(result);
// This is matched by the [beginNode] call in [beginSwitchBlock].
typeInferrer.assignedVariables.endNode(switchStatement);
}
@override
void endSwitchBlock(int caseCount, Token beginToken, Token endToken) {
debugEvent("SwitchBlock");
List<SwitchCase> cases =
new List<SwitchCase>.filled(caseCount, dummySwitchCase, growable: true);
for (int i = caseCount - 1; i >= 0; i--) {
List<Label>? labels = pop() as List<Label>?;
SwitchCase current = cases[i] = pop() as SwitchCase;
if (labels != null) {
for (Label label in labels) {
JumpTarget? target = switchScope!.lookupLabel(label.name);
if (target != null) {
target.resolveGotos(forest, current);
}
}
}
}
for (int i = 0; i < caseCount - 1; i++) {
SwitchCase current = cases[i];
Block block = current.body as Block;
// [block] is a synthetic block that is added to handle variable
// declarations in the switch case.
TreeNode? lastNode =
block.statements.isEmpty ? null : block.statements.last;
if (lastNode is Block) {
// This is a non-synthetic block.
Block block = lastNode;
lastNode = block.statements.isEmpty ? null : block.statements.last;
}
if (lastNode is ExpressionStatement) {
ExpressionStatement statement = lastNode;
lastNode = statement.expression;
}
// The rule that every case block should end with one of the predefined
// set of statements is specific to pre-NNBD code and is replaced with
// another rule based on flow analysis for NNBD code. For details, see
// the following link:
// https://github.com/dart-lang/language/blob/master/accepted/future-releases/nnbd/feature-specification.md#errors-and-warnings
if (!libraryBuilder.isNonNullableByDefault) {
if (lastNode is! BreakStatement &&
lastNode is! ContinueSwitchStatement &&
lastNode is! Rethrow &&
lastNode is! ReturnStatement &&
!forest.isThrow(lastNode)) {
block.addStatement(new ExpressionStatement(
buildFallThroughError(current.fileOffset)));
}
}
}
push(cases);
}
@override
void handleCaseMatch(Token caseKeyword, Token colon) {
debugEvent("CaseMatch");
// Do nothing. Handled by [handleSwitchCase].
}
@override
void handleBreakStatement(
bool hasTarget, Token breakKeyword, Token endToken) {
debugEvent("BreakStatement");
JumpTarget? target = breakTarget;
Identifier? identifier;
String? name;
if (hasTarget) {
identifier = pop() as Identifier;
name = identifier.name;
target = scope.lookupLabel(name);
}
if (target == null && name == null) {
push(problemInLoopOrSwitch = buildProblemStatement(
fasta.messageBreakOutsideOfLoop, breakKeyword.charOffset));
} else if (target == null || !target.isBreakTarget) {
Token labelToken = breakKeyword.next!;
push(problemInLoopOrSwitch = buildProblemStatement(
fasta.templateInvalidBreakTarget.withArguments(name!),
labelToken.charOffset,
length: labelToken.length));
} else if (target.functionNestingLevel != functionNestingLevel) {
push(buildProblemTargetOutsideLocalFunction(name, breakKeyword));
} else {
Statement statement =
forest.createBreakStatement(offsetForToken(breakKeyword), identifier);
target.addBreak(statement);
push(statement);
}
}
Statement buildProblemTargetOutsideLocalFunction(
String? name, Token keyword) {
Statement problem;
bool isBreak = optional("break", keyword);
if (name != null) {
Template<Message Function(String)> template = isBreak
? fasta.templateBreakTargetOutsideFunction
: fasta.templateContinueTargetOutsideFunction;
problem = buildProblemStatement(
template.withArguments(name), offsetForToken(keyword),
length: lengthOfSpan(keyword, keyword.next));
} else {
Message message = isBreak
? fasta.messageAnonymousBreakTargetOutsideFunction
: fasta.messageAnonymousContinueTargetOutsideFunction;
problem = buildProblemStatement(message, offsetForToken(keyword),
length: lengthForToken(keyword));
}
problemInLoopOrSwitch ??= problem;
return problem;
}
@override
void handleContinueStatement(
bool hasTarget, Token continueKeyword, Token endToken) {
debugEvent("ContinueStatement");
JumpTarget? target = continueTarget;
Identifier? identifier;
String? name;
if (hasTarget) {
identifier = pop() as Identifier;
name = identifier.name;
target = scope.lookupLabel(identifier.name);
if (target == null) {
if (switchScope == null) {
push(buildProblemStatement(
fasta.templateLabelNotFound.withArguments(name),
continueKeyword.next!.charOffset));
return;
}
switchScope!.forwardDeclareLabel(
identifier.name, target = createGotoTarget(identifier.charOffset));
}
if (target.isGotoTarget &&
target.functionNestingLevel == functionNestingLevel) {
ContinueSwitchStatement statement =
new ContinueSwitchStatement(dummySwitchCase)
..fileOffset = continueKeyword.charOffset;
target.addGoto(statement);
push(statement);
return;
}
}
if (target == null) {
push(problemInLoopOrSwitch = buildProblemStatement(
fasta.messageContinueWithoutLabelInCase, continueKeyword.charOffset,
length: continueKeyword.length));
} else if (!target.isContinueTarget) {
Token labelToken = continueKeyword.next!;
push(problemInLoopOrSwitch = buildProblemStatement(
fasta.templateInvalidContinueTarget.withArguments(name!),
labelToken.charOffset,
length: labelToken.length));
} else if (target.functionNestingLevel != functionNestingLevel) {
push(buildProblemTargetOutsideLocalFunction(name, continueKeyword));
} else {
Statement statement = forest.createContinueStatement(
offsetForToken(continueKeyword), identifier);
target.addContinue(statement);
push(statement);
}
}
@override
void beginTypeVariable(Token token) {
debugEvent("beginTypeVariable");
assert(checkState(token, [
unionOfKinds([ValueKinds.Identifier, ValueKinds.ParserRecovery]),
ValueKinds.AnnotationListOrNull,
]));
Object? name = pop();
List<Expression>? annotations = pop() as List<Expression>?;
String? typeVariableName;
int typeVariableCharOffset;
if (name is Identifier) {
typeVariableName = name.name;
typeVariableCharOffset = name.charOffset;
} else if (name is ParserRecovery) {
typeVariableName = TypeVariableBuilder.noNameSentinel;
typeVariableCharOffset = name.charOffset;
} else {
unhandled("${name.runtimeType}", "beginTypeVariable.name",
token.charOffset, uri);
}
TypeVariableBuilder variable = new TypeVariableBuilder(
typeVariableName, libraryBuilder, typeVariableCharOffset, uri,
kind: TypeVariableKind.function);
if (annotations != null) {
inferAnnotations(variable.parameter, annotations);
for (Expression annotation in annotations) {
variable.parameter.addAnnotation(annotation);
}
}
push(variable);
}
@override
void handleTypeVariablesDefined(Token token, int count) {
debugEvent("handleTypeVariablesDefined");
assert(count > 0);
List<TypeVariableBuilder>? typeVariables =
const FixedNullableList<TypeVariableBuilder>()
.popNonNullable(stack, count, dummyTypeVariableBuilder);
enterFunctionTypeScope(typeVariables);
push(typeVariables);
}
@override
void endTypeVariable(
Token token, int index, Token? extendsOrSuper, Token? variance) {
debugEvent("TypeVariable");
TypeBuilder? bound = pop() as TypeBuilder?;
// Peek to leave type parameters on top of stack.
List<TypeVariableBuilder> typeVariables =
peek() as List<TypeVariableBuilder>;
TypeVariableBuilder variable = typeVariables[index];
variable.bound = bound;
if (variance != null) {
if (!libraryFeatures.variance.isEnabled) {
reportVarianceModifierNotEnabled(variance);
}
variable.variance = Variance.fromString(variance.lexeme);
}
}
@override
void endTypeVariables(Token beginToken, Token endToken) {
debugEvent("TypeVariables");
// Peek to leave type parameters on top of stack.
List<TypeVariableBuilder> typeVariables =
peek() as List<TypeVariableBuilder>;
List<TypeBuilder> unboundTypes = [];
List<TypeVariableBuilder> unboundTypeVariables = [];
List<TypeBuilder> calculatedBounds = calculateBounds(
typeVariables,
libraryBuilder.loader.target.dynamicType,
libraryBuilder.loader.target.nullType,
libraryBuilder.loader.target.objectClassBuilder,
unboundTypes: unboundTypes,
unboundTypeVariables: unboundTypeVariables);
assert(unboundTypes.isEmpty,
"Found a type not bound to a declaration in BodyBuilder.");
for (int i = 0; i < typeVariables.length; ++i) {
typeVariables[i].defaultType = calculatedBounds[i];
typeVariables[i].finish(
libraryBuilder,
libraryBuilder.loader.target.objectClassBuilder,
libraryBuilder.loader.target.dynamicType);
}
for (int i = 0; i < unboundTypeVariables.length; ++i) {
unboundTypeVariables[i].finish(
libraryBuilder,
libraryBuilder.loader.target.objectClassBuilder,
libraryBuilder.loader.target.dynamicType);
}
libraryBuilder.processPendingNullabilities();
}
@override
void handleNoTypeVariables(Token token) {
debugEvent("NoTypeVariables");
enterFunctionTypeScope(null);
push(NullValue.TypeVariables);
}
List<TypeParameter>? typeVariableBuildersToKernel(
List<TypeVariableBuilder>? typeVariableBuilders) {
if (typeVariableBuilders == null) return null;
return new List<TypeParameter>.generate(typeVariableBuilders.length,
(int i) => typeVariableBuilders[i].parameter,
growable: true);
}
@override
void handleInvalidStatement(Token token, Message message) {
Statement statement = pop() as Statement;
push(new ExpressionStatement(
buildProblem(message, statement.fileOffset, noLength)));
}
@override
Expression buildProblem(Message message, int charOffset, int length,
{List<LocatedMessage>? context,
bool suppressMessage: false,
Expression? expression}) {
if (!suppressMessage) {
addProblem(message, charOffset, length,
wasHandled: true, context: context);
}
String text = libraryBuilder.loader.target.context
.format(message.withLocation(uri, charOffset, length), Severity.error)
.plain;
return new InvalidExpression(text, expression)..fileOffset = charOffset;
}
@override
Expression wrapInProblem(
Expression expression, Message message, int fileOffset, int length,
{List<LocatedMessage>? context}) {
Severity severity = message.code.severity;
if (severity == Severity.error) {
return wrapInLocatedProblem(
expression, message.withLocation(uri, fileOffset, length),
context: context);
} else {
addProblem(message, fileOffset, length, context: context);
return expression;
}
}
@override
Expression wrapInLocatedProblem(Expression expression, LocatedMessage message,
{List<LocatedMessage>? context}) {
// TODO(askesc): Produce explicit error expression wrapping the original.
// See [issue 29717](https://github.com/dart-lang/sdk/issues/29717)
int offset = expression.fileOffset;
if (offset == -1) {
offset = message.charOffset;
}
return buildProblem(
message.messageObject, message.charOffset, message.length,
context: context, expression: expression);
}
Expression buildFallThroughError(int charOffset) {
addProblem(fasta.messageSwitchCaseFallThrough, charOffset, noLength);
// TODO(ahe): The following doesn't make sense for the Analyzer. It should
// be moved to [Forest] or conditional on `forest is Fangorn`.
// TODO(ahe): Compute a LocatedMessage above instead?
Location? location = messages.getLocationFromUri(uri, charOffset);
return forest.createThrow(
charOffset,
buildStaticInvocation(
libraryBuilder
.loader.coreTypes.fallThroughErrorUrlAndLineConstructor,
forest.createArguments(noLocation, <Expression>[
forest.createStringLiteral(
charOffset, "${location?.file ?? uri}"),
forest.createIntLiteral(charOffset, location?.line ?? 0),
]),
constness: Constness.explicitNew,
charOffset: charOffset));
}
Expression buildAbstractClassInstantiationError(
Message message, String className,
[int charOffset = -1]) {
addProblemErrorIfConst(message, charOffset, className.length);
// TODO(ahe): The following doesn't make sense to Analyzer AST.
MemberBuilder constructor =
libraryBuilder.loader.getAbstractClassInstantiationError();
Expression invocation = buildStaticInvocation(
constructor.member,
forest.createArguments(charOffset,
<Expression>[forest.createStringLiteral(charOffset, className)]),
constness: Constness.explicitNew,
charOffset: charOffset);
return forest.createThrow(charOffset, invocation);
}
Statement buildProblemStatement(Message message, int charOffset,
{List<LocatedMessage>? context,
int? length,
bool suppressMessage: false}) {
length ??= noLength;
return new ExpressionStatement(buildProblem(message, charOffset, length,
context: context, suppressMessage: suppressMessage));
}
Statement wrapInProblemStatement(Statement statement, Message message) {
// TODO(askesc): Produce explicit error statement wrapping the original.
// See [issue 29717](https://github.com/dart-lang/sdk/issues/29717)
return buildProblemStatement(message, statement.fileOffset);
}
@override
Initializer buildInvalidInitializer(Expression expression,
[int charOffset = -1]) {
needsImplicitSuperInitializer = false;
return new ShadowInvalidInitializer(
new VariableDeclaration.forValue(expression))
..fileOffset = charOffset;
}
Initializer buildDuplicatedInitializer(Field field, Expression value,
String name, int offset, int previousInitializerOffset) {
return new ShadowInvalidFieldInitializer(
field,
value,
new VariableDeclaration.forValue(buildProblem(
fasta.templateConstructorInitializeSameInstanceVariableSeveralTimes
.withArguments(name),
offset,
noLength)))
..fileOffset = offset;
}
/// Parameter [formalType] should only be passed in the special case of
/// building a field initializer as a desugaring of an initializing formal
/// parameter. The spec says the following:
///
/// "If an explicit type is attached to the initializing formal, that is its
/// static type. Otherwise, the type of an initializing formal named _id_ is
/// _Tid_, where _Tid_ is the type of the instance variable named _id_ in the
/// immediately enclosing class. It is a static warning if the static type of
/// _id_ is not a subtype of _Tid_."
@override
List<Initializer> buildFieldInitializer(String name, int fieldNameOffset,
int assignmentOffset, Expression expression,
{FormalParameterBuilder? formal}) {
Builder? builder = declarationBuilder!.lookupLocalMember(name);
if (builder?.next != null) {
// Duplicated name, already reported.
return <Initializer>[
buildInvalidInitializer(
buildProblem(
fasta.templateDuplicatedDeclarationUse.withArguments(name),
fieldNameOffset,
name.length),
fieldNameOffset)
];
} else if (builder is SourceFieldBuilder &&
builder.isDeclarationInstanceMember) {
initializedFields ??= <String, int>{};
if (initializedFields!.containsKey(name)) {
return <Initializer>[
buildDuplicatedInitializer(builder.field, expression, name,
assignmentOffset, initializedFields![name]!)
];
}
initializedFields![name] = assignmentOffset;
if (builder.isAbstract) {
return <Initializer>[
buildInvalidInitializer(
buildProblem(fasta.messageAbstractFieldConstructorInitializer,
fieldNameOffset, name.length),
fieldNameOffset)
];
} else if (builder.isExternal) {
return <Initializer>[
buildInvalidInitializer(
buildProblem(fasta.messageExternalFieldConstructorInitializer,
fieldNameOffset, name.length),
fieldNameOffset)
];
} else if (builder.isFinal && builder.hasInitializer) {
addProblem(
fasta.templateFieldAlreadyInitializedAtDeclaration
.withArguments(name),
assignmentOffset,
noLength,
context: [
fasta.templateFieldAlreadyInitializedAtDeclarationCause
.withArguments(name)
.withLocation(uri, builder.charOffset, name.length)
]);
MemberBuilder constructor =
libraryBuilder.loader.getDuplicatedFieldInitializerError();
Expression invocation = buildStaticInvocation(
constructor.member,
forest.createArguments(assignmentOffset, <Expression>[
forest.createStringLiteral(assignmentOffset, name)
]),
constness: Constness.explicitNew,
charOffset: assignmentOffset);
return <Initializer>[
new ShadowInvalidFieldInitializer(
builder.field,
expression,
new VariableDeclaration.forValue(
forest.createThrow(assignmentOffset, invocation)))
..fileOffset = assignmentOffset
];
} else {
if (formal != null && formal.type != null) {
DartType formalType = formal.variable!.type;
if (!typeEnvironment.isSubtypeOf(formalType, builder.fieldType,
SubtypeCheckMode.withNullabilities)) {
libraryBuilder.addProblem(
fasta.templateInitializingFormalTypeMismatch.withArguments(
name,
formalType,
builder.fieldType,
libraryBuilder.isNonNullableByDefault),
assignmentOffset,
noLength,
uri,
context: [
fasta.messageInitializingFormalTypeMismatchField.withLocation(
builder.fileUri, builder.charOffset, noLength)
]);
}
}
DeclaredSourceConstructorBuilder constructorBuilder =
member as DeclaredSourceConstructorBuilder;
constructorBuilder.registerInitializedField(builder);
return builder.buildInitializer(assignmentOffset, expression,
isSynthetic: formal != null);
}
} else {
return <Initializer>[
buildInvalidInitializer(
buildProblem(
fasta.templateInitializerForStaticField.withArguments(name),
fieldNameOffset,
name.length),
fieldNameOffset)
];
}
}
@override
Initializer buildSuperInitializer(
bool isSynthetic, Constructor constructor, Arguments arguments,
[int charOffset = -1]) {
if (member.isConst && !constructor.isConst) {
addProblem(fasta.messageConstConstructorWithNonConstSuper, charOffset,
constructor.name.text.length);
}
needsImplicitSuperInitializer = false;
return new SuperInitializer(constructor, arguments)
..fileOffset = charOffset
..isSynthetic = isSynthetic;
}
@override
Initializer buildRedirectingInitializer(
Constructor constructor, Arguments arguments,
[int charOffset = -1]) {
if (sourceClassBuilder!
.checkConstructorCyclic(member.name!, constructor.name.text)) {
int length = constructor.name.text.length;
if (length == 0) length = "this".length;
addProblem(fasta.messageConstructorCyclic, charOffset, length);
// TODO(askesc): Produce invalid initializer.
}
needsImplicitSuperInitializer = false;
return new RedirectingInitializer(constructor, arguments)
..fileOffset = charOffset;
}
@override
void handleOperator(Token token) {
debugEvent("Operator");
push(new Operator(token, token.charOffset));
}
@override
void handleSymbolVoid(Token token) {
debugEvent("SymbolVoid");
push(new Identifier(token));
}
@override
void handleInvalidFunctionBody(Token token) {
if (member.isNative) {
push(NullValue.FunctionBody);
} else {
push(forest.createBlock(offsetForToken(token), noLocation, <Statement>[
buildProblemStatement(
fasta.templateExpectedFunctionBody.withArguments(token),
token.charOffset,
length: token.length)
]));
}
}
@override
void handleTypeArgumentApplication(Token openAngleBracket) {
assert(checkState(openAngleBracket, [
ValueKinds.TypeArguments,
unionOfKinds([ValueKinds.Generator, ValueKinds.Expression])
]));
List<TypeBuilder>? typeArguments =
pop() as List<TypeBuilder>?; // typeArguments
if (libraryFeatures.constructorTearoffs.isEnabled) {
Object? operand = pop();
if (operand is Generator) {
push(operand.applyTypeArguments(
openAngleBracket.charOffset, typeArguments));
} else if (operand is StaticTearOff &&
(operand.target.isFactory || isTearOffLowering(operand.target)) ||
operand is ConstructorTearOff ||
operand is RedirectingFactoryTearOff) {
push(buildProblem(fasta.messageConstructorTearOffWithTypeArguments,
openAngleBracket.charOffset, noLength));
} else {
push(new Instantiation(
toValue(operand),
buildDartTypeArguments(typeArguments, TypeUse.tearOffTypeArgument,
allowPotentiallyConstantType: true))
..fileOffset = openAngleBracket.charOffset);
}
} else {
libraryBuilder.reportFeatureNotEnabled(
libraryFeatures.constructorTearoffs,
uri,
openAngleBracket.charOffset,
noLength);
}
}
@override
TypeBuilder validateTypeVariableUse(TypeBuilder typeBuilder,
{required bool allowPotentiallyConstantType}) {
// ignore: unnecessary_null_comparison
assert(allowPotentiallyConstantType != null);
_validateTypeVariableUseInternal(typeBuilder,
allowPotentiallyConstantType: allowPotentiallyConstantType);
return typeBuilder;
}
void _validateTypeVariableUseInternal(TypeBuilder? builder,
{required bool allowPotentiallyConstantType}) {
// ignore: unnecessary_null_comparison
assert(allowPotentiallyConstantType != null);
if (builder is NamedTypeBuilder) {
if (builder.declaration!.isTypeVariable) {
TypeVariableBuilder typeParameterBuilder =
builder.declaration as TypeVariableBuilder;
TypeParameter typeParameter = typeParameterBuilder.parameter;
if (typeParameter.parent is Class ||
typeParameter.parent is Extension) {
if (constantContext != ConstantContext.none &&
(!inConstructorInitializer || !allowPotentiallyConstantType)) {
LocatedMessage message = fasta.messageTypeVariableInConstantContext
.withLocation(builder.fileUri!, builder.charOffset!,
typeParameter.name!.length);
builder.bind(
libraryBuilder,
new InvalidTypeDeclarationBuilder(
typeParameter.name!, message));
addProblem(
message.messageObject, message.charOffset, message.length);
}
}
}
if (builder.arguments != null) {
for (TypeBuilder typeBuilder in builder.arguments!) {
_validateTypeVariableUseInternal(typeBuilder,
allowPotentiallyConstantType: allowPotentiallyConstantType);
}
}
} else if (builder is FunctionTypeBuilder) {
_validateTypeVariableUseInternal(builder.returnType,
allowPotentiallyConstantType: allowPotentiallyConstantType);
if (builder.formals != null) {
for (ParameterBuilder formalParameterBuilder in builder.formals!) {
_validateTypeVariableUseInternal(formalParameterBuilder.type,
allowPotentiallyConstantType: allowPotentiallyConstantType);
}
}
}
}
@override
Expression evaluateArgumentsBefore(
Arguments? arguments, Expression expression) {
if (arguments == null) return expression;
List<Expression> expressions =
new List<Expression>.of(forest.argumentsPositional(arguments));
for (NamedExpression named in forest.argumentsNamed(arguments)) {
expressions.add(named.value);
}
for (Expression argument in expressions.reversed) {
expression = new Let(
new VariableDeclaration.forValue(argument,
isFinal: true,
type: coreTypes.objectRawType(libraryBuilder.nullable)),
expression);
}
return expression;
}
@override
bool isIdentical(Member? member) => member == coreTypes.identicalProcedure;
@override
Expression buildMethodInvocation(
Expression receiver, Name name, Arguments arguments, int offset,
{bool isConstantExpression: false, bool isNullAware: false}) {
if (constantContext != ConstantContext.none &&
!isConstantExpression &&
!libraryFeatures.constFunctions.isEnabled) {
return buildProblem(
fasta.templateNotConstantExpression
.withArguments('Method invocation'),
offset,
name.text.length);
}
if (isNullAware) {
VariableDeclarationImpl variable =
createVariableDeclarationForValue(receiver);
return new NullAwareMethodInvocation(
variable,
forest.createMethodInvocation(
offset,
createVariableGet(variable, receiver.fileOffset),
name,
arguments))
..fileOffset = receiver.fileOffset;
} else {
return forest.createMethodInvocation(offset, receiver, name, arguments);
}
}
@override
Expression buildSuperInvocation(Name name, Arguments arguments, int offset,
{bool isConstantExpression: false,
bool isNullAware: false,
bool isImplicitCall: false}) {
if (constantContext != ConstantContext.none &&
!isConstantExpression &&
!libraryFeatures.constFunctions.isEnabled) {
return buildProblem(
fasta.templateNotConstantExpression
.withArguments('Method invocation'),
offset,
name.text.length);
}
Member? target = lookupSuperMember(name);
if (target == null || (target is Procedure && !target.isAccessor)) {
if (target == null) {
warnUnresolvedMethod(name, offset, isSuper: true);
} else if (!areArgumentsCompatible(target.function!, arguments)) {
target = null;
addProblemErrorIfConst(
fasta.templateSuperclassMethodArgumentMismatch
.withArguments(name.text),
offset,
name.text.length);
}
return new SuperMethodInvocation(name, arguments, target as Procedure?)
..fileOffset = offset;
}
if (isImplicitCall) {
return buildProblem(
fasta.messageImplicitSuperCallOfNonMethod, offset, noLength);
} else {
Expression receiver = new SuperPropertyGet(name, target)
..fileOffset = offset;
return forest.createExpressionInvocation(
arguments.fileOffset, receiver, arguments);
}
}
@override
void addProblem(Message message, int charOffset, int length,
{bool wasHandled: false,
List<LocatedMessage>? context,
Severity? severity}) {
libraryBuilder.addProblem(message, charOffset, length, uri,
wasHandled: wasHandled, context: context, severity: severity);
}
@override
void addProblemErrorIfConst(Message message, int charOffset, int length,
{bool wasHandled: false, List<LocatedMessage>? context}) {
// TODO(askesc): Instead of deciding on the severity, this method should
// take two messages: one to use when a constant expression is
// required and one to use otherwise.
Severity severity = message.code.severity;
if (constantContext != ConstantContext.none) {
severity = Severity.error;
}
addProblem(message, charOffset, length,
wasHandled: wasHandled, context: context, severity: severity);
}
@override
Expression buildProblemErrorIfConst(
Message message, int charOffset, int length,
{bool wasHandled: false, List<LocatedMessage>? context}) {
addProblemErrorIfConst(message, charOffset, length,
wasHandled: wasHandled, context: context);
String text = libraryBuilder.loader.target.context
.format(message.withLocation(uri, charOffset, length), Severity.error)
.plain;
InvalidExpression expression = new InvalidExpression(text)
..fileOffset = charOffset;
return expression;
}
@override
void reportDuplicatedDeclaration(
Builder existing, String name, int charOffset) {
List<LocatedMessage>? context = existing.isSynthetic
? null
: <LocatedMessage>[
fasta.templateDuplicatedDeclarationCause
.withArguments(name)
.withLocation(
existing.fileUri!, existing.charOffset, name.length)
];
addProblem(fasta.templateDuplicatedDeclaration.withArguments(name),
charOffset, name.length,
context: context);
}
@override
void debugEvent(String name) {
// printEvent('BodyBuilder: $name');
}
@override
Expression wrapInDeferredCheck(
Expression expression, PrefixBuilder prefix, int charOffset) {
VariableDeclaration check = new VariableDeclaration.forValue(
forest.checkLibraryIsLoaded(charOffset, prefix.dependency!));
return new DeferredCheck(check, expression)..fileOffset = charOffset;
}
bool isErroneousNode(TreeNode node) {
return libraryBuilder.loader.handledErrors.isNotEmpty &&
forest.isErroneousNode(node);
}
@override
DartType buildDartType(TypeBuilder typeBuilder, TypeUse typeUse,
{required bool allowPotentiallyConstantType}) {
return validateTypeVariableUse(typeBuilder,
allowPotentiallyConstantType: allowPotentiallyConstantType)
.build(libraryBuilder, typeUse);
}
DartType buildAliasedDartType(TypeBuilder typeBuilder, TypeUse typeUse,
{required bool allowPotentiallyConstantType}) {
return validateTypeVariableUse(typeBuilder,
allowPotentiallyConstantType: allowPotentiallyConstantType)
.buildAliased(libraryBuilder, typeUse);
}
@override
List<DartType> buildDartTypeArguments(
List<TypeBuilder>? unresolvedTypes, TypeUse typeUse,
{required bool allowPotentiallyConstantType}) {
if (unresolvedTypes == null) return <DartType>[];
return new List<DartType>.generate(
unresolvedTypes.length,
(int i) => buildDartType(unresolvedTypes[i], typeUse,
allowPotentiallyConstantType: allowPotentiallyConstantType),
growable: true);
}
@override
String constructorNameForDiagnostics(String name,
{String? className, bool isSuper: false}) {
if (className == null) {
Class cls = sourceClassBuilder!.cls;
if (isSuper) {
cls = cls.superclass!;
while (cls.isMixinApplication) {
cls = cls.superclass!;
}
}
className = cls.name;
}
return name.isEmpty ? className : "$className.$name";
}
@override
void handleNewAsIdentifier(Token token) {
reportIfNotEnabled(
libraryFeatures.constructorTearoffs, token.charOffset, token.length);
}
}
abstract class EnsureLoaded {
void ensureLoaded(Member? member);
bool isLoaded(Member? member);
}
class Operator {
final Token token;
String get name => token.stringValue!;
final int charOffset;
Operator(this.token, this.charOffset);
@override
String toString() => "operator($name)";
}
class JumpTarget {
final List<Statement> users = <Statement>[];
final JumpTargetKind kind;
final int functionNestingLevel;
final Uri fileUri;
final int charOffset;
JumpTarget(
this.kind, this.functionNestingLevel, this.fileUri, this.charOffset);
bool get isBreakTarget => kind == JumpTargetKind.Break;
bool get isContinueTarget => kind == JumpTargetKind.Continue;
bool get isGotoTarget => kind == JumpTargetKind.Goto;
bool get hasUsers => users.isNotEmpty;
void addBreak(Statement statement) {
assert(isBreakTarget);
users.add(statement);
}
void addContinue(Statement statement) {
assert(isContinueTarget);
users.add(statement);
}
void addGoto(Statement statement) {
assert(isGotoTarget);
users.add(statement);
}
void resolveBreaks(
Forest forest, LabeledStatement target, Statement targetStatement) {
assert(isBreakTarget);
for (Statement user in users) {
BreakStatementImpl breakStatement = user as BreakStatementImpl;
breakStatement.target = target;
breakStatement.targetStatement = targetStatement;
}
users.clear();
}
List<BreakStatementImpl>? resolveContinues(
Forest forest, LabeledStatement target) {
assert(isContinueTarget);
List<BreakStatementImpl> statements = <BreakStatementImpl>[];
for (Statement user in users) {
BreakStatementImpl breakStatement = user as BreakStatementImpl;
breakStatement.target = target;
statements.add(breakStatement);
}
users.clear();
return statements;
}
void resolveGotos(Forest forest, SwitchCase target) {
assert(isGotoTarget);
for (Statement user in users) {
ContinueSwitchStatement continueSwitchStatement =
user as ContinueSwitchStatement;
continueSwitchStatement.target = target;
}
users.clear();
}
String get fullNameForErrors => "<jump-target>";
}
class LabelTarget implements JumpTarget {
final JumpTarget breakTarget;
final JumpTarget continueTarget;
@override
final int functionNestingLevel;
@override
final Uri fileUri;
@override
final int charOffset;
LabelTarget(this.functionNestingLevel, this.fileUri, this.charOffset)
: breakTarget = new JumpTarget(
JumpTargetKind.Break, functionNestingLevel, fileUri, charOffset),
continueTarget = new JumpTarget(
JumpTargetKind.Continue, functionNestingLevel, fileUri, charOffset);
@override
bool get hasUsers => breakTarget.hasUsers || continueTarget.hasUsers;
@override
List<Statement> get users => unsupported("users", charOffset, fileUri);
@override
JumpTargetKind get kind => unsupported("kind", charOffset, fileUri);
@override
bool get isBreakTarget => true;
@override
bool get isContinueTarget => true;
@override
bool get isGotoTarget => false;
@override
void addBreak(Statement statement) {
breakTarget.addBreak(statement);
}
@override
void addContinue(Statement statement) {
continueTarget.addContinue(statement);
}
@override
void addGoto(Statement statement) {
unsupported("addGoto", charOffset, fileUri);
}
@override
void resolveBreaks(
Forest forest, LabeledStatement target, Statement targetStatement) {
breakTarget.resolveBreaks(forest, target, targetStatement);
}
@override
List<BreakStatementImpl>? resolveContinues(
Forest forest, LabeledStatement target) {
return continueTarget.resolveContinues(forest, target);
}
@override
void resolveGotos(Forest forest, SwitchCase target) {
unsupported("resolveGotos", charOffset, fileUri);
}
@override
String get fullNameForErrors => "<label-target>";
}
class FormalParameters {
final List<FormalParameterBuilder>? parameters;
final int charOffset;
final int length;
final Uri uri;
FormalParameters(this.parameters, this.charOffset, this.length, this.uri) {
if (parameters?.isEmpty ?? false) {
throw "Empty parameters should be null";
}
}
FunctionNode buildFunctionNode(
SourceLibraryBuilder library,
TypeBuilder? returnTypeBuilder,
List<TypeVariableBuilder>? typeVariableBuilders,
AsyncMarker asyncModifier,
Statement body,
int fileEndOffset) {
DartType returnType =
returnTypeBuilder?.build(library, TypeUse.returnType) ??
const DynamicType();
int requiredParameterCount = 0;
List<VariableDeclaration> positionalParameters = <VariableDeclaration>[];
List<VariableDeclaration> namedParameters = <VariableDeclaration>[];
if (parameters != null) {
for (FormalParameterBuilder formal in parameters!) {
VariableDeclaration parameter = formal.build(
library,
);
if (formal.isPositional) {
positionalParameters.add(parameter);
if (formal.isRequiredPositional) requiredParameterCount++;
} else if (formal.isNamed) {
namedParameters.add(parameter);
}
}
namedParameters.sort((VariableDeclaration a, VariableDeclaration b) {
return a.name!.compareTo(b.name!);
});
}
List<TypeParameter>? typeParameters;
if (typeVariableBuilders != null) {
typeParameters = <TypeParameter>[];
for (TypeVariableBuilder t in typeVariableBuilders) {
typeParameters.add(t.parameter);
// Build the bound to detect cycles in typedefs.
t.bound?.build(library, TypeUse.typeParameterBound);
}
}
return new FunctionNode(body,
typeParameters: typeParameters,
positionalParameters: positionalParameters,
namedParameters: namedParameters,
requiredParameterCount: requiredParameterCount,
returnType: returnType,
asyncMarker: asyncModifier)
..fileOffset = charOffset
..fileEndOffset = fileEndOffset;
}
TypeBuilder toFunctionType(
TypeBuilder? returnType, NullabilityBuilder nullabilityBuilder,
[List<TypeVariableBuilder>? typeParameters]) {
return new FunctionTypeBuilder(returnType, typeParameters, parameters,
nullabilityBuilder, uri, charOffset);
}
Scope computeFormalParameterScope(
Scope parent, Builder declaration, ExpressionGeneratorHelper helper) {
if (parameters == null) return parent;
assert(parameters!.isNotEmpty);
Map<String, Builder> local = <String, Builder>{};
for (FormalParameterBuilder parameter in parameters!) {
Builder? existing = local[parameter.name];
if (existing != null) {
helper.reportDuplicatedDeclaration(
existing, parameter.name, parameter.charOffset);
} else {
local[parameter.name] = parameter;
}
}
return new Scope(
local: local,
parent: parent,
debugName: "formals",
isModifiable: false);
}
@override
String toString() {
return "FormalParameters($parameters, $charOffset, $uri)";
}
}
/// Returns a block like this:
///
/// {
/// statement;
/// body;
/// }
///
/// If [body] is a [Block], it's returned with [statement] prepended to it.
Block combineStatements(Statement statement, Statement body) {
if (body is Block) {
if (statement is Block) {
body.statements.insertAll(0, statement.statements);
setParents(statement.statements, body);
} else {
body.statements.insert(0, statement);
statement.parent = body;
}
return body;
} else {
return new Block(<Statement>[statement, body])
..fileOffset = statement.fileOffset;
}
}
/// DartDocTest(
/// debugName("myClassName", "myName", "myPrefix"),
/// "myPrefix.myClassName.myName"
/// )
/// DartDocTest(
/// debugName("myClassName", "myName"),
/// "myClassName.myName"
/// )
/// DartDocTest(
/// debugName("myClassName", ""),
/// "myClassName"
/// )
/// DartDocTest(
/// debugName("", ""),
/// ""
/// )
String debugName(String className, String name, [String? prefix]) {
String result = name.isEmpty ? className : "$className.$name";
return prefix == null ? result : "$prefix.$result";
}
// TODO(johnniwinther): This is a bit ad hoc. Call sites should know what kind
// of objects can be anticipated and handle these directly.
String getNodeName(Object node) {
if (node is Identifier) {
return node.name;
} else if (node is Builder) {
return node.fullNameForErrors;
} else if (node is QualifiedName) {
return flattenName(node, node.charOffset, null);
} else {
return unhandled("${node.runtimeType}", "getNodeName", -1, null);
}
}
/// A data holder used to hold the information about a label that is pushed on
/// the stack.
class Label {
String name;
int charOffset;
Label(this.name, this.charOffset);
@override
String toString() => "label($name)";
}
class ForInElements {
VariableDeclaration? explicitVariableDeclaration;
VariableDeclaration? syntheticVariableDeclaration;
Expression? syntheticAssignment;
Expression? expressionProblem;
Statement? expressionEffects;
VariableDeclaration get variable =>
(explicitVariableDeclaration ?? syntheticVariableDeclaration)!;
}
class _BodyBuilderCloner extends CloneVisitorNotMembers {
final BodyBuilder bodyBuilder;
_BodyBuilderCloner(this.bodyBuilder);
@override
TreeNode visitStaticInvocation(StaticInvocation node) {
if (node is FactoryConstructorInvocation) {
FactoryConstructorInvocation result = new FactoryConstructorInvocation(
node.target, clone(node.arguments),
isConst: node.isConst)
..hasBeenInferred = node.hasBeenInferred;
bodyBuilder.redirectingFactoryInvocations.add(result);
return result;
} else if (node is TypeAliasedFactoryInvocation) {
TypeAliasedFactoryInvocation result = new TypeAliasedFactoryInvocation(
node.typeAliasBuilder, node.target, clone(node.arguments),
isConst: node.isConst)
..hasBeenInferred = node.hasBeenInferred;
bodyBuilder.typeAliasedFactoryInvocations.add(result);
return result;
}
return super.visitStaticInvocation(node);
}
@override
TreeNode visitConstructorInvocation(ConstructorInvocation node) {
if (node is TypeAliasedConstructorInvocation) {
TypeAliasedConstructorInvocation result =
new TypeAliasedConstructorInvocation(
node.typeAliasBuilder, node.target, clone(node.arguments),
isConst: node.isConst)
..hasBeenInferred = node.hasBeenInferred;
bodyBuilder.typeAliasedConstructorInvocations.add(result);
return result;
}
return super.visitConstructorInvocation(node);
}
@override
TreeNode visitArguments(Arguments node) {
if (node is ArgumentsImpl) {
return ArgumentsImpl.clone(node, node.positional.map(clone).toList(),
node.named.map(clone).toList(), node.types.map(visitType).toList());
}
return super.visitArguments(node);
}
}
/// Returns `true` if [node] is not part of its parent member.
///
/// This computation is costly and should only be used in assertions to verify
/// that [node] has been removed from the AST.
bool isOrphaned(TreeNode node) {
TreeNode? parent = node;
Member? member;
while (parent != null) {
if (parent is Member) {
member = parent;
break;
}
parent = parent.parent;
}
if (member == null) {
return true;
}
_FindChildVisitor visitor = new _FindChildVisitor(node);
member.accept(visitor);
return !visitor.foundNode;
}
class _FindChildVisitor extends Visitor<void> with VisitorVoidMixin {
final TreeNode soughtNode;
bool foundNode = false;
_FindChildVisitor(this.soughtNode);
@override
void defaultNode(Node node) {
if (!foundNode) {
if (identical(node, soughtNode)) {
foundNode = true;
} else {
node.visitChildren(this);
}
}
}
}