| // 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; |
| |
|