blob: d28c80e19e4337047a7e679a0f8ebe134a7ad60b [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library fasta.body_builder;
import 'package:_fe_analyzer_shared/src/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, NullValues, ParserRecovery;
import 'package:_fe_analyzer_shared/src/parser/util.dart' show stripSeparators;
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/type_inference/assigned_variables.dart';
import 'package:_fe_analyzer_shared/src/util/link.dart';
import 'package:_fe_analyzer_shared/src/util/value_kind.dart';
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/names.dart' show emptyName, minusName, plusName;
import 'package:kernel/src/bounds_checks.dart' hide calculateBounds;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import '../api_prototype/experimental_flags.dart';
import '../api_prototype/lowering_predicates.dart';
import '../base/constant_context.dart' show ConstantContext;
import '../base/identifiers.dart'
show
Identifier,
InitializedIdentifier,
QualifiedName,
SimpleIdentifier,
flattenName;
import '../base/label_scope.dart';
import '../base/local_scope.dart';
import '../base/modifier.dart'
show Modifier, constMask, covariantMask, finalMask, lateMask, requiredMask;
import '../base/problems.dart' show internalProblem, unhandled, unsupported;
import '../base/scope.dart';
import '../builder/builder.dart';
import '../builder/declaration_builders.dart';
import '../builder/formal_parameter_builder.dart';
import '../builder/function_type_builder.dart';
import '../builder/invalid_type_builder.dart';
import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/named_type_builder.dart';
import '../builder/nullability_builder.dart';
import '../builder/omitted_type_builder.dart';
import '../builder/prefix_builder.dart';
import '../builder/record_type_builder.dart';
import '../builder/type_builder.dart';
import '../builder/variable_builder.dart';
import '../builder/void_type_declaration_builder.dart';
import '../codes/cfe_codes.dart'
show
LocatedMessage,
Message,
Template,
messageNamedFieldClashesWithPositionalFieldInRecord,
noLength,
templateDuplicatedRecordLiteralFieldName,
templateDuplicatedRecordLiteralFieldNameContext,
templateExperimentNotEnabledOffByDefault,
templateLocalVariableUsedBeforeDeclared,
templateLocalVariableUsedBeforeDeclaredContext;
import '../codes/cfe_codes.dart' as fasta;
import '../dill/dill_library_builder.dart' show DillLibraryBuilder;
import '../source/diet_parser.dart';
import '../source/source_field_builder.dart';
import '../source/source_library_builder.dart';
import '../source/source_member_builder.dart';
import '../source/stack_listener_impl.dart'
show StackListenerImpl, offsetForToken;
import '../source/value_kinds.dart';
import '../type_inference/inference_results.dart'
show InitializerInferenceResult;
import '../type_inference/type_inferrer.dart'
show TypeInferrer, InferredFunctionBody;
import '../type_inference/type_schema.dart' show UnknownType;
import '../util/helpers.dart';
import '../util/local_stack.dart';
import 'benchmarker.dart' show Benchmarker;
import 'body_builder_context.dart';
import 'collections.dart';
import 'constness.dart' show Constness;
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 'type_algorithms.dart' show calculateBounds;
import 'utils.dart';
// TODO(ahe): Remove this and ensure all nodes have a location.
const int noLocation = TreeNode.noOffset;
enum JumpTargetKind {
Break,
Continue,
Goto, // Continue label in switch.
}
class BodyBuilder extends StackListenerImpl
implements ExpressionGeneratorHelper {
@override
final Forest forest;
@override
final SourceLibraryBuilder libraryBuilder;
final BodyBuilderContext _context;
final ClassHierarchy hierarchy;
final CoreTypes coreTypes;
final LocalScope enclosingScope;
final bool enableNative;
// 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;
LocalScope? 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;
/// This is set to `true` when we are parsing formals.
bool inFormals = 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;
/// Level of nesting of function-type type parameters.
///
/// For instance, `X` is at nesting level 1, and `Y` is at nesting level 2 in
/// the following:
///
/// method() {
/// Function<X>(Function<Y extends X>(Y))? f;
/// }
///
/// For simplicity, non-generic functions are considered generic functions
/// with 0 type parameters.
int _structuralParameterDepthLevel = 0;
/// True if a type of a formal parameter is currently compiled.
///
/// This variable is needed to distinguish between the type of a formal
/// parameter and its initializer because in those two regions of code the
/// type variables should be interpreted differently: as structural and
/// nominal correspondingly.
bool _insideOfFormalParameterType = false;
bool get inFunctionType =>
_structuralParameterDepthLevel > 0 || _insideOfFormalParameterType;
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;
Statement? problemInLoopOrSwitch;
LocalStack<LabelScope> _labelScopes;
LocalStack<LabelScope?> _switchScopes = new LocalStack([]);
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;
/// 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 or
/// an instance member or constructor in and extension type declaration,
/// [thisVariable] holds the synthetically added variable holding the value
/// for `this`.
final VariableDeclaration? thisVariable;
final List<TypeParameter>? thisTypeParameters;
LocalStack<LocalScope> _localScopes;
Set<VariableDeclaration>? declaredInCurrentGuard;
JumpTarget? breakTarget;
JumpTarget? continueTarget;
/// Index for building unique lowered names for wildcard variables.
int wildcardVariableIndex = 0;
BodyBuilder(
{required this.libraryBuilder,
required BodyBuilderContext context,
required this.enclosingScope,
this.formalParameterScope,
required this.hierarchy,
required this.coreTypes,
this.thisVariable,
this.thisTypeParameters,
required this.uri,
required this.typeInferrer})
: _context = context,
forest = const Forest(),
enableNative = libraryBuilder.loader.target.backendTarget
.enableNative(libraryBuilder.importUri),
needsImplicitSuperInitializer =
context.needsImplicitSuperInitializer(coreTypes),
benchmarker = libraryBuilder.loader.target.benchmarker,
_localScopes = new LocalStack([enclosingScope]),
_labelScopes = new LocalStack([new LabelScopeImpl()]) {
if (formalParameterScope != null) {
for (Builder builder in formalParameterScope!.localVariables) {
if (builder is VariableBuilder) {
typeInferrer.assignedVariables.declare(builder.variable!);
}
}
}
if (thisVariable != null && context.isConstructor) {
// The this variable is not part of the [formalParameterScope] in
// constructors.
typeInferrer.assignedVariables.declare(thisVariable!);
}
}
BodyBuilder.forField(
SourceLibraryBuilder libraryBuilder,
BodyBuilderContext bodyBuilderContext,
LookupScope enclosingScope,
TypeInferrer typeInferrer,
Uri uri)
: this(
libraryBuilder: libraryBuilder,
context: bodyBuilderContext,
enclosingScope: new EnclosingLocalScope(enclosingScope),
formalParameterScope: null,
hierarchy: libraryBuilder.loader.hierarchy,
coreTypes: libraryBuilder.loader.coreTypes,
thisVariable: null,
uri: uri,
typeInferrer: typeInferrer);
BodyBuilder.forOutlineExpression(SourceLibraryBuilder library,
BodyBuilderContext bodyBuilderContext, LookupScope scope, Uri fileUri,
{LocalScope? formalParameterScope})
: this(
libraryBuilder: library,
context: bodyBuilderContext,
enclosingScope: new EnclosingLocalScope(scope),
formalParameterScope: formalParameterScope,
hierarchy: library.loader.hierarchy,
coreTypes: library.loader.coreTypes,
thisVariable: null,
uri: fileUri,
typeInferrer: library.loader.typeInferenceEngine
.createLocalTypeInferrer(
fileUri, bodyBuilderContext.thisType, library, null));
LocalScope get _localScope => _localScopes.current;
LabelScope get _labelScope => _labelScopes.current;
LabelScope? get _switchScope =>
_switchScopes.hasCurrent ? _switchScopes.current : null;
@override
LibraryFeatures get libraryFeatures => libraryBuilder.libraryFeatures;
@override
// Coverage-ignore(suite): Not run.
bool get isDartLibrary =>
libraryBuilder.origin.importUri.isScheme("dart") ||
uri.isScheme("org-dartlang-sdk");
@override
Message reportFeatureNotEnabled(
LibraryFeature feature, int charOffset, int length) {
return libraryBuilder.reportFeatureNotEnabled(
feature, uri, charOffset, length);
}
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(LocalScope localScope) {
_localScopes.push(localScope);
_labelScopes.push(new LabelScopeImpl(_labelScope));
}
void createAndEnterLocalScope(
{required String debugName, required ScopeKind kind}) {
_localScopes
.push(_localScope.createNestedScope(debugName: debugName, kind: kind));
_labelScopes.push(new LabelScopeImpl(_labelScope));
}
void exitLocalScope({List<ScopeKind>? expectedScopeKinds}) {
assert(
expectedScopeKinds == null ||
expectedScopeKinds.contains(_localScope.kind),
// Coverage-ignore(suite): Not run.
"Expected the current scope to be one of the kinds "
"${expectedScopeKinds.map((k) => "'${k}'").join(", ")}, "
"but got '${_localScope.kind}'.");
if (isGuardScope(_localScope) && declaredInCurrentGuard != null) {
for (Builder builder in _localScope.localVariables) {
if (builder is VariableBuilder) {
declaredInCurrentGuard!.remove(builder.variable);
}
}
if (declaredInCurrentGuard!.isEmpty) {
declaredInCurrentGuard = null;
}
}
_labelScopes.pop();
_localScopes.pop();
}
void enterBreakTarget(int charOffset, [JumpTarget? target]) {
push(breakTarget ?? NullValues.BreakTarget);
breakTarget = target ?? createBreakTarget(charOffset);
}
void enterContinueTarget(int charOffset, [JumpTarget? target]) {
push(continueTarget ?? NullValues.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");
createAndEnterLocalScope(
debugName: "block function body", kind: ScopeKind.functionBody);
}
@override
void beginForStatement(Token token) {
debugEvent("beginForStatement");
enterLoop(token.charOffset);
createAndEnterLocalScope(
debugName: "for statement", kind: ScopeKind.forStatement);
}
@override
void beginForControlFlow(Token? awaitToken, Token forToken) {
debugEvent("beginForControlFlow");
createAndEnterLocalScope(
debugName: "for in a collection", kind: ScopeKind.forStatement);
}
@override
void beginDoWhileStatementBody(Token token) {
debugEvent("beginDoWhileStatementBody");
createAndEnterLocalScope(
debugName: "do-while statement body",
kind: ScopeKind.statementLocalScope);
}
@override
void endDoWhileStatementBody(Token token) {
debugEvent("endDoWhileStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginWhileStatementBody(Token token) {
debugEvent("beginWhileStatementBody");
createAndEnterLocalScope(
debugName: "while statement body", kind: ScopeKind.statementLocalScope);
}
@override
void endWhileStatementBody(Token endToken) {
debugEvent("endWhileStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginForStatementBody(Token token) {
debugEvent("beginForStatementBody");
createAndEnterLocalScope(
debugName: "for statement body", kind: ScopeKind.statementLocalScope);
}
@override
void endForStatementBody(Token endToken) {
debugEvent("endForStatementBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginForInBody(Token token) {
debugEvent("beginForInBody");
createAndEnterLocalScope(
debugName: "for-in body", kind: ScopeKind.statementLocalScope);
}
@override
void endForInBody(Token endToken) {
debugEvent("endForInBody");
Object? body = pop();
exitLocalScope();
push(body);
}
@override
void beginElseStatement(Token token) {
debugEvent("beginElseStatement");
createAndEnterLocalScope(
debugName: "else", kind: ScopeKind.statementLocalScope);
}
@override
void endElseStatement(Token beginToken, Token endToken) {
debugEvent("endElseStatement");
Object? body = pop();
exitLocalScope();
push(body);
}
bool get inConstructor {
return functionNestingLevel == 0 && _context.isConstructor;
}
bool get isDeclarationInstanceContext {
return _context.isDeclarationInstanceContext;
}
@override
InstanceTypeVariableAccessState get instanceTypeVariableAccessState {
return _context.instanceTypeVariableAccessState;
}
@override
TypeEnvironment get typeEnvironment => typeInferrer.typeSchemaEnvironment;
DartType get implicitTypeArgument => const ImplicitTypeArgument();
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);
}
Pattern toPattern(Object? node) {
if (node is Pattern) {
return node;
} else if (node is Generator) {
return forest.createConstantPattern(node.buildSimpleRead());
} else if (node is Expression) {
return forest.createConstantPattern(node);
}
// Coverage-ignore(suite): Not run.
else if (node is ProblemBuilder) {
// ignore: unused_local_variable
Expression expression =
buildProblem(node.message, node.charOffset, noLength);
return forest.createConstantPattern(expression);
} else {
return unhandled("${node.runtimeType}", "toPattern", -1, uri);
}
}
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(NullValues.Block) as Statement?;
if (statement != null) {
statement = forest.wrapVariables(statement);
}
return statement;
}
void enterSwitchScope() {
_switchScopes.push(_labelScope);
}
void exitSwitchScope() {
LabelScope switchScope = _switchScope!;
LabelScope? outerSwitchScope =
_switchScopes.hasPrevious ? _switchScopes.previous : null;
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);
}
});
}
_switchScopes.pop();
}
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, LocalScope scope) {
String name = variable.name!;
Builder? existing = scope.lookupLocalVariable(name);
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;
}
if (isGuardScope(scope)) {
(declaredInCurrentGuard ??= {}).add(variable);
}
String variableName = variable.name!;
List<int>? previousOffsets =
scope.declare(variableName, new VariableBuilderImpl(variable, uri));
if (previousOffsets != null && previousOffsets.isNotEmpty) {
// 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`.
for (int previousOffset in previousOffsets) {
addProblem(
templateLocalVariableUsedBeforeDeclared.withArguments(variableName),
previousOffset,
variableName.length,
context: <LocatedMessage>[
templateLocalVariableUsedBeforeDeclaredContext
.withArguments(variableName)
.withLocation(uri, variable.fileOffset, variableName.length)
]);
}
}
}
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);
}
}
@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) {
// Coverage-ignore-block(suite): Not run.
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.readTarget is Field) &&
expression is! VariableUseGenerator &&
// TODO(johnniwinther): Stop using the type of the generator here.
// Ask a property instead.
(expression is! ReadOnlyAccessGenerator ||
// Coverage-ignore(suite): Not run.
expression is TypeUseGenerator ||
// Coverage-ignore(suite): Not run.
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, repeatedKind(ValueKinds.Expression, count)));
debugEvent("MetadataStar");
if (count == 0) {
push(NullValues.Metadata);
} else {
push(const GrowableList<Expression>()
.popNonNullable(stack, count, dummyExpression) ??
NullValues.Metadata /* Ignore parser recovery */);
}
assert(checkState(null, [ValueKinds.AnnotationListOrNull]));
}
@override
void endTopLevelFields(
Token? augmentToken,
Token? externalToken,
Token? staticToken,
Token? covariantToken,
Token? lateToken,
Token? varFinalOrConst,
int count,
Token beginToken,
Token endToken) {
debugEvent("TopLevelFields");
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");
push(count);
assert(checkState(beginToken, [ValueKinds.Integer]));
}
void finishFields() {
debugEvent("finishFields");
assert(checkState(null, [/*field count*/ ValueKinds.Integer]));
int count = pop() as int;
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 = _context.lookupLocalMember(name, required: true)!;
int fileOffset = identifier.nameOffset;
while (declaration.next != null) {
// If we have duplicates, we try to find the right declaration.
if (declaration.fileUri == uri &&
declaration.charOffset == fileOffset) {
break;
}
declaration = declaration.next!;
}
if (declaration.fileUri != uri || declaration.charOffset != fileOffset) {
// If we don't have the right declaration, skip the initializer.
continue;
}
SourceFieldBuilder fieldBuilder;
if (declaration.isField) {
fieldBuilder = declaration as SourceFieldBuilder;
} else {
continue;
}
if (initializer != null) {
if (!fieldBuilder.hasBodyBeenBuilt) {
initializer = typeInferrer
.inferFieldInitializer(this, fieldBuilder.builtType, initializer)
.expression;
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() {
_finishVariableMetadata();
libraryBuilder.checkPendingBoundsChecks(typeEnvironment);
}
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(NullValues.Block);
} else {
Statement block = popBlock(count, openBrace, closeBrace);
exitLocalScope();
push(block);
}
assert(checkState(closeBrace, [ValueKinds.StatementOrNull]));
}
void prepareInitializers() {
_localScopes
.push(_context.computeFormalParameterInitializerScope(_localScope));
if (_context.isConstructor) {
_context.prepareInitializers();
if (_context.formals != null) {
for (FormalParameterBuilder formal in _context.formals!) {
if (formal.isInitializingFormal) {
List<Initializer> initializers;
if (_context.isExternalConstructor) {
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) {
_context.addInitializer(initializer, this, inferenceResult: null);
}
}
}
}
}
}
@override
void handleNoInitializers() {
debugEvent("NoInitializers");
if (functionNestingLevel == 0) {
prepareInitializers();
_localScopes.push(formalParameterScope ??
new FixedLocalScope(
kind: ScopeKind.initializers, debugName: "initializers"));
}
}
@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) {
_localScopes.push(formalParameterScope ??
new FixedLocalScope(
kind: ScopeKind.initializers, debugName: "initializers"));
}
inConstructorInitializer = false;
}
@override
void beginInitializer(Token token) {
debugEvent("beginInitializer");
inInitializerLeftHandSide = true;
inFieldInitializer = true;
}
@override
void endInitializer(Token endToken) {
assert(checkState(endToken, [
unionOfKinds([
ValueKinds.Initializer,
ValueKinds.Generator,
ValueKinds.Expression,
])
]));
debugEvent("endInitializer");
inFieldInitializer = false;
assert(!inInitializerLeftHandSide);
Object? node = pop();
List<Initializer> initializers;
if (!(_context.isConstructor && !_context.isExternalConstructor)) {
// 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) {
// Coverage-ignore-block(suite): Not run.
initializers = <Initializer>[
// TODO(jensj): Does this offset make sense?
buildSuperInitializer(
false, node.target, node.arguments, endToken.next!.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].
// TODO(jensj): Does this offset make sense?
buildInvalidInitializer(node as Expression, endToken.next!.charOffset)
];
}
_initializers ??= <Initializer>[];
_initializers!.addAll(initializers);
}
List<Object>? createSuperParametersAsArguments(
List<FormalParameterBuilder> formals) {
List<Object>? superParametersAsArguments;
for (FormalParameterBuilder formal in formals) {
if (formal.isSuperInitializingFormal) {
if (formal.isNamed) {
(superParametersAsArguments ??= <Object>[]).add(new NamedExpression(
formal.name,
createVariableGet(formal.variable!, formal.charOffset,
forNullGuardedAccess: false))
..fileOffset = formal.charOffset);
} else {
(superParametersAsArguments ??= <Object>[]).add(createVariableGet(
formal.variable!, formal.charOffset,
forNullGuardedAccess: false));
}
}
}
return superParametersAsArguments;
}
void finishFunction(
FormalParameters? formals, AsyncMarker asyncModifier, Statement? body) {
debugEvent("finishFunction");
// Create variable get expressions for super parameters before finishing
// the analysis of the assigned variables. Creating the expressions later
// that point results in a flow analysis error.
List<Object>? superParametersAsArguments;
if (formals != null) {
List<FormalParameterBuilder>? formalParameters = formals.parameters;
if (formalParameters != null) {
superParametersAsArguments =
createSuperParametersAsArguments(formalParameters);
}
}
typeInferrer.assignedVariables.finish();
FunctionNode function = _context.function;
if (thisVariable != null) {
typeInferrer.flowAnalysis
.declare(thisVariable!, thisVariable!.type, initialized: true);
}
if (formals?.parameters != null) {
for (int i = 0; i < formals!.parameters!.length; i++) {
FormalParameterBuilder parameter = formals.parameters![i];
VariableDeclaration variable = parameter.variable!;
typeInferrer.flowAnalysis
.declare(variable, variable.type, initialized: true);
}
for (int i = 0; i < formals.parameters!.length; i++) {
FormalParameterBuilder parameter = formals.parameters![i];
Expression? initializer = parameter.variable!.initializer;
bool inferInitializer;
if (parameter.isSuperInitializingFormal) {
// Super-parameters can inherit the default value from the super
// constructor so we only handle explicit default values here.
inferInitializer = parameter.hasImmediatelyDeclaredInitializer;
} else if (initializer != null) {
inferInitializer = true;
} else {
inferInitializer = parameter.isOptional;
}
if (inferInitializer) {
if (!parameter.initializerWasInferred) {
// Coverage-ignore(suite): Not run.
initializer ??= forest.createNullLiteral(
// TODO(ahe): Should store: originParameter.fileOffset
// https://github.com/dart-lang/sdk/issues/32289
noLocation);
VariableDeclaration originParameter =
_context.getFormalParameter(i);
initializer = typeInferrer.inferParameterInitializer(
this,
initializer,
originParameter.type,
parameter.hasDeclaredInitializer);
originParameter.initializer = initializer..parent = originParameter;
parameter.initializerWasInferred = true;
}
VariableDeclaration? tearOffParameter =
_context.getTearOffParameter(i);
if (tearOffParameter != null) {
Expression tearOffInitializer =
_cloner.cloneInContext(initializer!);
tearOffParameter.initializer = tearOffInitializer
..parent = tearOffParameter;
}
}
}
}
if (_context.isConstructor) {
finishConstructor(asyncModifier, body,
superParametersAsArguments: superParametersAsArguments);
} else if (body != null) {
_context.setAsyncModifier(asyncModifier);
}
InferredFunctionBody? inferredFunctionBody;
if (body != null) {
inferredFunctionBody = typeInferrer.inferFunctionBody(
this,
_context.memberCharOffset,
_context.returnTypeContext,
asyncModifier,
body);
body = inferredFunctionBody.body;
function.emittedValueType = inferredFunctionBody.emittedValueType;
assert(function.asyncMarker == AsyncMarker.Sync ||
function.emittedValueType != null);
}
if (_context.returnType is! OmittedTypeBuilder) {
checkAsyncReturnType(asyncModifier, function.returnType,
_context.memberCharOffset, _context.memberName.length);
}
if (_context.isSetter) {
if (formals?.parameters == null ||
formals!.parameters!.length != 1 ||
formals.parameters!.single.isOptionalPositional) {
int charOffset = formals?.charOffset ??
// Coverage-ignore(suite): Not run.
body?.fileOffset ??
// Coverage-ignore(suite): Not run.
_context.memberCharOffset;
if (body == null) {
body = new EmptyStatement()..fileOffset = charOffset;
}
if (_context.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 _context.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 = (function.parent is Procedure &&
(function.parent as Procedure).isNoSuchMethodForwarder);
if (body != null) {
if (_context.isExternalFunction || isNoSuchMethodForwarder) {
body = new Block(<Statement>[
new ExpressionStatement(buildProblem(
fasta.messageExternalMethodWithBody, body.fileOffset, noLength))
..fileOffset = body.fileOffset,
body,
])
..fileOffset = body.fileOffset;
}
_context.setBody(body);
}
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
}
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'.
void ensureLoaded(Member? member) {
if (member == null) return;
Library ensureLibraryLoaded = member.enclosingLibrary;
LibraryBuilder? builder = libraryBuilder.loader
.lookupLoadedLibraryBuilder(ensureLibraryLoaded.importUri) ??
// Coverage-ignore(suite): Not run.
libraryBuilder.loader.target.dillTarget.loader
.lookupLibraryBuilder(ensureLibraryLoaded.importUri);
if (builder is DillLibraryBuilder) {
builder.ensureLoaded();
}
}
RedirectionTarget _getRedirectionTarget(Procedure factory) {
List<DartType> typeArguments = new List<DartType>.generate(
factory.function.typeParameters.length, (int i) {
return new TypeParameterType.withDefaultNullabilityForLibrary(
factory.function.typeParameters[i], factory.enclosingLibrary);
}, growable: true);
// Cyclic factories are detected earlier, so we're guaranteed to
// reach either a non-redirecting factory or an error eventually.
Member target = factory;
for (;;) {
RedirectingFactoryTarget? redirectingFactoryTarget =
target.function?.redirectingFactoryTarget;
if (redirectingFactoryTarget == null ||
redirectingFactoryTarget.isError) {
return new RedirectionTarget(target, typeArguments);
}
Member nextMember = redirectingFactoryTarget.target!;
ensureLoaded(nextMember);
List<DartType>? nextTypeArguments =
redirectingFactoryTarget.typeArguments;
if (nextTypeArguments != null) {
Substitution sub = Substitution.fromPairs(
target.function!.typeParameters, typeArguments);
typeArguments =
new List<DartType>.generate(nextTypeArguments.length, (int i) {
return sub.substituteType(nextTypeArguments[i]);
}, growable: true);
} else {
// Coverage-ignore-block(suite): Not run.
typeArguments = <DartType>[];
}
target = nextMember;
}
}
/// 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.
@override
Expression? resolveRedirectingFactoryTarget(
Procedure target, Arguments arguments, int fileOffset, bool isConst) {
Procedure initialTarget = target;
Expression replacementNode;
RedirectionTarget redirectionTarget = _getRedirectionTarget(initialTarget);
Member resolvedTarget = redirectionTarget.target;
if (redirectionTarget.typeArguments.any((type) => type is UnknownType)) {
return null;
}
RedirectingFactoryTarget? redirectingFactoryTarget =
resolvedTarget.function?.redirectingFactoryTarget;
if (redirectingFactoryTarget != null) {
// If the redirection target is itself a redirecting factory, it means
// that it is unresolved.
assert(redirectingFactoryTarget.isError);
String errorMessage = redirectingFactoryTarget.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,
isConstructorInvocation: true);
}
return replacementNode;
}
@override
Expression unaliasSingleTypeAliasedConstructorInvocation(
TypeAliasedConstructorInvocation invocation) {
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);
return new ConstructorInvocation(invocation.target, invocationArguments,
isConst: invocation.isConst);
}
@override
Expression? unaliasSingleTypeAliasedFactoryInvocation(
TypeAliasedFactoryInvocation invocation) {
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 TypeDeclarationType) {
invocationTypeArguments = unaliasedType.typeArguments;
}
Arguments invocationArguments = forest.createArguments(
noLocation, invocation.arguments.positional,
types: invocationTypeArguments,
named: invocation.arguments.named,
hasExplicitTypeArguments:
hasExplicitTypeArguments(invocation.arguments));
return resolveRedirectingFactoryTarget(invocation.target,
invocationArguments, invocation.fileOffset, invocation.isConst);
}
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]));
}
}
}
}
}
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 {
// Coverage-ignore-block(suite): Not run.
temporaryParent = new ListLiteral(expressions);
}
performBacklogComputations();
// Coverage-ignore(suite): Not run.
return temporaryParent != null ? temporaryParent.expressions : expressions;
}
// Coverage-ignore(suite): Not run.
Expression parseSingleExpression(
Parser parser, Token token, FunctionNode parameters) {
int fileOffset = offsetForToken(token);
List<NominalVariableBuilder>? typeParameterBuilders;
for (TypeParameter typeParameter in parameters.typeParameters) {
typeParameterBuilders ??= <NominalVariableBuilder>[];
typeParameterBuilders.add(new NominalVariableBuilder.fromKernel(
typeParameter,
loader: libraryBuilder.loader));
}
enterNominalVariablesScope(typeParameterBuilders);
List<FormalParameterBuilder>? formals =
parameters.positionalParameters.length == 0
? null
: new List<FormalParameterBuilder>.generate(
parameters.positionalParameters.length, (int i) {
VariableDeclaration formal = parameters.positionalParameters[i];
String formalName = formal.name!;
bool isWildcard = libraryFeatures.wildcardVariables.isEnabled &&
formalName == '_';
if (isWildcard) {
formalName =
createWildcardFormalParameterName(wildcardVariableIndex);
wildcardVariableIndex++;
}
return new FormalParameterBuilder(
FormalParameterKind.requiredPositional,
/* modifiers = */ 0,
const ImplicitTypeBuilder(),
formalName,
libraryBuilder,
formal.fileOffset,
fileUri: uri,
hasImmediatelyDeclaredInitializer: false,
isWildcard: isWildcard)
..variable = formal;
}, growable: false);
enterLocalScope(new FormalParameters(formals, fileOffset, noLength, uri)
.computeFormalParameterScope(
_localScope,
this,
wildcardVariablesEnabled: libraryFeatures.wildcardVariables.isEnabled,
));
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++) {
VariableDeclaration variable = formals[i].variable!;
typeInferrer.flowAnalysis
.declare(variable, variable.type, initialized: 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();
return fakeReturn.expression!;
}
List<Initializer>? parseInitializers(Token token,
{bool doFinishConstructor = true}) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe,
allowPatterns: libraryFeatures.patterns.isEnabled);
if (!token.isEof) {
token = parser.parseInitializers(token);
checkEmpty(token.charOffset);
} else {
handleNoInitializers();
}
if (doFinishConstructor) {
List<FormalParameterBuilder>? formals = _context.formals;
finishConstructor(AsyncMarker.Sync, null,
superParametersAsArguments: formals != null
? createSuperParametersAsArguments(formals)
: null);
}
return _initializers;
}
Expression parseFieldInitializer(Token token) {
Parser parser = new Parser(this,
useImplicitCreationExpression: useImplicitCreationExpressionInCfe,
allowPatterns: libraryFeatures.patterns.isEnabled);
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,
allowPatterns: libraryFeatures.patterns.isEnabled);
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,
allowPatterns: libraryFeatures.patterns.isEnabled);
token = parser.parseArgumentsRest(token);
ArgumentsImpl arguments = pop() as ArgumentsImpl;
checkEmpty(token.charOffset);
return arguments;
}
void finishConstructor(AsyncMarker asyncModifier, Statement? body,
{required List<Object /* Expression | NamedExpression */ >?
superParametersAsArguments}) {
/// Quotes below are from [Dart Programming Language Specification, 4th
/// Edition](
/// https://ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf).
assert(() {
if (superParametersAsArguments == null) {
return true;
}
for (Object superParameterAsArgument in superParametersAsArguments) {
if (superParameterAsArgument is! Expression &&
superParameterAsArgument is! NamedExpression) {
return false;
}
}
return true;
}(),
"Expected 'superParametersAsArguments' "
"to contain nothing but Expressions and NamedExpressions.");
assert(() {
if (superParametersAsArguments == null) {
return true;
}
int previousOffset = -1;
for (Object superParameterAsArgument in superParametersAsArguments) {
int offset;
if (superParameterAsArgument is Expression) {
offset = superParameterAsArgument.fileOffset;
} else if (superParameterAsArgument is NamedExpression) {
offset = superParameterAsArgument.value.fileOffset;
} else {
return false;
}
if (previousOffset > offset) {
return false;
}
previousOffset = offset;
}
return true;
}(),
"Expected 'superParametersAsArguments' "
"to be sorted by occurrence in file.");
FunctionNode function = _context.function;
List<FormalParameterBuilder>? formals = _context.formals;
if (formals != null) {
for (int i = 0; i < formals.length; i++) {
FormalParameterBuilder parameter = formals[i];
VariableDeclaration variable = parameter.variable!;
// TODO(paulberry): `skipDuplicateCheck` is currently needed to work
// around a failure in
// co19/Language/Expressions/Postfix_Expressions/conditional_increment_t02;
// fix this.
typeInferrer.flowAnalysis.declare(variable, variable.type,
initialized: true, skipDuplicateCheck: true);
}
}
Set<String>? namedSuperParameterNames;
List<Expression>? positionalSuperParametersAsArguments;
List<NamedExpression>? namedSuperParametersAsArguments;
if (superParametersAsArguments != null) {
for (Object superParameterAsArgument in superParametersAsArguments) {
if (superParameterAsArgument is Expression) {
(positionalSuperParametersAsArguments ??= <Expression>[])
.add(superParameterAsArgument);
} else {
NamedExpression namedSuperParameterAsArgument =
superParameterAsArgument as NamedExpression;
(namedSuperParametersAsArguments ??= <NamedExpression>[])
.add(namedSuperParameterAsArgument);
(namedSuperParameterNames ??= <String>{})
.add(namedSuperParameterAsArgument.name);
}
}
} else if (formals != null) {
for (FormalParameterBuilder formal in formals) {
if (formal.isSuperInitializingFormal) {
// Coverage-ignore-block(suite): Not run.
if (formal.isNamed) {
NamedExpression superParameterAsArgument = new NamedExpression(
formal.name,
createVariableGet(formal.variable!, formal.charOffset,
forNullGuardedAccess: false))
..fileOffset = formal.charOffset;
(namedSuperParametersAsArguments ??= <NamedExpression>[])
.add(superParameterAsArgument);
(namedSuperParameterNames ??= <String>{}).add(formal.name);
(superParametersAsArguments ??= <Object>[])
.add(superParameterAsArgument);
} else {
Expression superParameterAsArgument = createVariableGet(
formal.variable!, formal.charOffset,
forNullGuardedAccess: false);
(positionalSuperParametersAsArguments ??= <Expression>[])
.add(superParameterAsArgument);
(superParametersAsArguments ??= <Object>[])
.add(superParameterAsArgument);
}
}
}
}
List<Initializer>? initializers = _initializers;
if (initializers != null && initializers.isNotEmpty) {
if (_context.isMixinClass) {
// Report an error if a mixin class has a constructor with an
// initializer.
buildProblem(
fasta.templateIllegalMixinDueToConstructors
.withArguments(_context.className),
_context.memberCharOffset,
noLength);
}
if (initializers.last is SuperInitializer) {
SuperInitializer superInitializer =
initializers.last as SuperInitializer;
if (_context.isEnumClass) {
initializers[initializers.length - 1] = buildInvalidInitializer(
buildProblem(fasta.messageEnumConstructorSuperInitializer,
superInitializer.fileOffset, noLength))
..parent = superInitializer.parent;
} 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;
}
if (superParametersAsArguments != null) {
arguments.argumentsOriginalOrder
?.insertAll(0, superParametersAsArguments);
}
}
} else if (initializers.last is RedirectingInitializer) {
RedirectingInitializer redirectingInitializer =
initializers.last as RedirectingInitializer;
if (_context.isEnumClass && libraryFeatures.enhancedEnums.isEnabled) {
ArgumentsImpl arguments =
redirectingInitializer.arguments as ArgumentsImpl;
List<Expression> enumSyntheticArguments = [
new VariableGetImpl(function.positionalParameters[0],
forNullGuardedAccess: false)
..parent = redirectingInitializer.arguments,
new VariableGetImpl(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) => _context.inferInitializer(
initializers[index], this, typeInferrer),
growable: false);
if (!_context.isExternalConstructor) {
for (int i = 0; i < initializers.length; i++) {
_context.addInitializer(initializers[i], this,
inferenceResult: inferenceResults[i]);
}
}
}
if (asyncModifier != AsyncMarker.Sync) {
_context.addInitializer(
buildInvalidInitializer(buildProblem(
fasta.messageConstructorNotSync, body!.fileOffset, noLength)),
this,
inferenceResult: null);
}
if (needsImplicitSuperInitializer) {
/// >If no superinitializer is provided, an implicit superinitializer
/// >of the form super() is added at the end of k’s initializer list,
/// >unless the enclosing class is class Object.
Constructor? superTarget = lookupSuperConstructor(emptyName);
Initializer initializer;
ArgumentsImpl arguments;
List<Expression>? positionalArguments;
List<NamedExpression>? namedArguments;
if (libraryFeatures.superParameters.isEnabled) {
positionalArguments = positionalSuperParametersAsArguments;
namedArguments = namedSuperParametersAsArguments;
}
if (_context.isEnumClass) {
assert(function.positionalParameters.length >= 2 &&
function.positionalParameters[0].name == "#index" &&
function.positionalParameters[1].name == "#name");
(positionalArguments ??= <Expression>[]).insertAll(0, [
new VariableGetImpl(function.positionalParameters[0],
forNullGuardedAccess: false),
new VariableGetImpl(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,
_context.memberCharOffset, const <TypeParameter>[]) !=
null) {
String superclass = _context.superClassName;
int length = _context.memberName.length;
if (length == 0) {
length = _context.className.length;
}
initializer = buildInvalidInitializer(
buildProblem(
fasta.templateSuperclassHasNoDefaultConstructor
.withArguments(superclass),
_context.memberCharOffset,
length),
_context.memberCharOffset);
} else {
initializer = buildSuperInitializer(
true, superTarget, arguments, _context.memberCharOffset);
}
if (libraryFeatures.superParameters.isEnabled) {
InitializerInferenceResult inferenceResult =
_context.inferInitializer(initializer, this, typeInferrer);
_context.addInitializer(initializer, this,
inferenceResult: inferenceResult);
} else {
_context.addInitializer(initializer, this, inferenceResult: null);
}
}
if (body == null && !_context.isExternalConstructor) {
/// >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.
function.body = new EmptyStatement()..parent = function;
} else if (body != null && _context.isMixinClass && !_context.isFactory) {
// Report an error if a mixin class has a non-factory constructor with a
// body.
buildProblem(
fasta.templateIllegalMixinDueToConstructors
.withArguments(_context.className),
_context.memberCharOffset,
noLength);
}
}
@override
void handleExpressionStatement(Token beginToken, Token endToken) {
assert(checkState(endToken, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
debugEvent("ExpressionStatement");
push(forest.createExpressionStatement(
offsetForToken(endToken), 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, Token? case_, Token? when) {
debugEvent("ParenthesizedCondition");
if (case_ != null) {
Expression? guard;
if (when != null) {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Pattern,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
guard = popForValue();
}
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Pattern,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
reportIfNotEnabled(
libraryFeatures.patterns, case_.charOffset, case_.charCount);
Pattern pattern = toPattern(pop());
Expression expression = popForValue();
push(new Condition(expression,
forest.createPatternGuard(expression.fileOffset, pattern, guard)));
} else {
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
]));
push(new Condition(popForValue()));
}
assert(checkState(token, [
ValueKinds.Condition,
]));
}
@override
void endParenthesizedExpression(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 handleParenthesizedPattern(Token token) {
debugEvent("ParenthesizedPattern");
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
])
]));
// TODO(johnniwinther): Do we need a ParenthesizedPattern ?
reportIfNotEnabled(
libraryFeatures.patterns, token.charOffset, token.charCount);
Object? value = pop();
if (value is Pattern) {
push(value);
} else {
push(toValue(value));
}
}
@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);
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");
// Scope of the preceding case head or a sentinel if it's the first head.
exitLocalScope(expectedScopeKinds: const [ScopeKind.caseHead]);
createAndEnterLocalScope(debugName: "case-head", kind: ScopeKind.caseHead);
super.push(constantContext);
if (!libraryFeatures.patterns.isEnabled) {
constantContext = ConstantContext.inferred;
}
assert(checkState(caseKeyword, [ValueKinds.ConstantContext]));
}
@override
void endCaseExpression(Token caseKeyword, Token? when, Token colon) {
debugEvent("endCaseExpression");
assert(checkState(colon, [
if (when != null)
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
]),
ValueKinds.ConstantContext,
]));
Expression? guard;
if (when != null) {
guard = popForValue();
}
Object? value = pop();
constantContext = pop() as ConstantContext;
assert(
_localScopes.previous.kind == ScopeKind.switchBlock,
// Coverage-ignore(suite): Not run.
"Expected to have scope kind ${ScopeKind.switchBlock}, "
"but got ${_localScopes.previous.kind}.");
if (value is Pattern) {
super.push(new ExpressionOrPatternGuardCase.patternGuard(
caseKeyword.charOffset,
forest.createPatternGuard(caseKeyword.charOffset, value, guard)));
} else if (guard != null) {
super.push(new ExpressionOrPatternGuardCase.patternGuard(
caseKeyword.charOffset,
forest.createPatternGuard(
caseKeyword.charOffset, toPattern(value), guard)));
} else {
Expression expression = toValue(value);
super.push(new ExpressionOrPatternGuardCase.expression(
caseKeyword.charOffset, expression));
}
assert(checkState(colon, [ValueKinds.ExpressionOrPatternGuardCase]));
}
@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, Token endToken) {
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,
]),
]));
}
@override
void beginPattern(Token token) {
debugEvent("Pattern");
if (token.lexeme == "||") {
createAndEnterLocalScope(
debugName: "rhs of a binary-or pattern",
kind: ScopeKind.orPatternRight);
} else {
createAndEnterLocalScope(debugName: "pattern", kind: ScopeKind.pattern);
}
}
@override
void endPattern(Token token) {
debugEvent("Pattern");
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
]),
]));
Object pattern = pop()!;
ScopeKind scopeKind = _localScope.kind;
exitLocalScope(expectedScopeKinds: const [
ScopeKind.pattern,
ScopeKind.orPatternRight
]);
// Bring the variables into the enclosing pattern scope, unless that was
// the scope of the RHS of a binary-or pattern. In the latter case, the
// joint variables will be declared in the enclosing scope instead later in
// the process.
//
// Here we only handle the visibility of the pattern declared variables
// within the pattern itself, so we declare the pattern variables in the
// enclosing scope only if that enclosing scope is a pattern scope as well,
// that is, if its kind is [ScopeKind.pattern] or
// [ScopeKind.orPatternRight].
bool enclosingScopeIsPatternScope = _localScope.kind == ScopeKind.pattern ||
_localScope.kind == ScopeKind.orPatternRight;
if (scopeKind != ScopeKind.orPatternRight && enclosingScopeIsPatternScope) {
if (pattern is Pattern) {
for (VariableDeclaration variable in pattern.declaredVariables) {
declareVariable(variable, _localScope);
}
}
}
push(pattern);
}
@override
void beginBinaryPattern(Token token) {
debugEvent("BinaryPattern");
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
]),
]));
// In case of the binary-or pattern, its LHS and RHS should contain
// declarations of the variables with matching names, and we need to put
// them into separate scopes to avoid the naming conflict. For that, we're
// exiting the scope for the LHS, and the scope for the RHS will be created
// when the RHS will be parsed. Additionally, since it's the first time
// we're realizing that it's the binary-or pattern, we need to create the
// enclosing scope for its joint variables as well.
if (token.lexeme == "||") {
Object lhsPattern = pop()!;
// Exit the scope of the LHS.
exitLocalScope(expectedScopeKinds: const [ScopeKind.pattern]);
createAndEnterLocalScope(
debugName: "joint variables of binary-or patterns",
kind: ScopeKind.pattern);
push(lhsPattern);
}
}
@override
void endBinaryPattern(Token token) {
debugEvent("BinaryPattern");
assert(checkState(token, [
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
]),
unionOfKinds([
ValueKinds.Expression,
ValueKinds.Generator,
ValueKinds.ProblemBuilder,
ValueKinds.Pattern,
])
]));
reportIfNotEnabled(
libraryFeatures.patterns, token.charOffset, token.charCount);
Pattern right = toPattern(pop());
Pattern left = toPattern(pop());
String operator = token.lexeme;
switch (operator) {
case '&&':
push(forest.createAndPattern(token.charOffset, left, right));
break;
case '||':
Map<String, VariableDeclaration> leftVariablesByName = {
for (VariableDeclaration leftVariable in left.declaredVariables)
leftVariable.name!: leftVariable
};
for (VariableDeclaration rightVariable in right.declaredVariables) {
if (!leftVariablesByName.containsKey(rightVariable.name)) {
addProblem(
fasta.templateMissingVariablePattern
.withArguments(rightVariable.name!),
left.fileOffset,
noLength);
}
}
Map<String, VariableDeclaration> rightVariablesByName = {
for (VariableDeclaration rightVariable in right.declaredVariables)
rightVariable.name!: rightVariable
};
for (VariableDeclaration leftVariable in left.declaredVariables) {
if (!rightVariablesByName.containsKey(leftVariable.name)) {
addProblem(
fasta.templateMissingVariablePattern
.withArguments(leftVariable.name!),
right.fileOffset,
noLength);
}
}
List<VariableDeclaration> jointVariables = [
for (VariableDeclaration leftVariable in left.declaredVariables)
forest.createVariableDeclaration(
leftVariable.fileOffset, leftVariable.name!)
];
for (VariableDeclaration variable in jointVariables) {
declareVariable(variable, _localScope);
typeInferrer.assignedVariables.declare(variable);
}
push(forest.createOrPattern(token.charOffset, left, right,
orPatternJointVariables: jointVariables));
break;
// Coverage-ignore(suite): Not run.
default:
internalProblem(
fasta.templateInternalProblemUnhandled
.withArguments(operator, 'endBinaryPattern'),
token.charOffset,
uri);
}
}
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,
]),
]));
}
@override
Expression buildUnresolvedError(String name, int charOffset,
{Member? candidate,
bool isSuper = false,
required UnresolvedKind kind,
bool isStatic = false,
Arguments? arguments,
Expression? rhs,
LocatedMessage? message,
int? length}) {
// TODO(johnniwinther): Use [arguments] and [rhs] to create an unresolved
// access expression to include in the invalid expression.
if (length == null) {
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
?
// Coverage-ignore(suite): Not run.
fasta.templateSuperclassHasNoMember.withArguments(name.text)
: fasta.templateMemberNotFound.withArguments(name.text);
if (reportWarning) {
// Coverage-ignore-block(suite): Not run.
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
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) {
// Coverage-ignore-block(suite): Not run.
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
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) {
// Coverage-ignore-block(suite): Not run.
addProblemErrorIfConst(message, charOffset, name.text.length,
context: context);
}
return message;
}
Message warnUnresolvedMethod(Name name, int charOffset,
{bool isSuper = false,
bool reportWarning = true,
List<LocatedMessage>? context}) {
String plainName = name.text;