| // 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. |
| |
| // @dart = 2.9 |
| |
| library fasta.body_builder; |
| |
| import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'; |
| |
| import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity; |
| |
| import 'package:_fe_analyzer_shared/src/parser/parser.dart' |
| show |
| Assert, |
| BlockKind, |
| 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/value_kind.dart'; |
| |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show Token; |
| |
| import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart' |
| show isBinaryOperator, isMinusOperator, isUserDefinableOperator; |
| |
| import 'package:_fe_analyzer_shared/src/util/link.dart'; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/type_environment.dart'; |
| |
| import '../builder/builder.dart'; |
| import '../builder/class_builder.dart'; |
| import '../builder/constructor_builder.dart'; |
| import '../builder/declaration_builder.dart'; |
| import '../builder/enum_builder.dart'; |
| import '../builder/extension_builder.dart'; |
| import '../builder/field_builder.dart'; |
| import '../builder/formal_parameter_builder.dart'; |
| import '../builder/function_builder.dart'; |
| import '../builder/function_type_builder.dart'; |
| import '../builder/invalid_type_declaration_builder.dart'; |
| import '../builder/library_builder.dart'; |
| import '../builder/member_builder.dart'; |
| import '../builder/modifier_builder.dart'; |
| import '../builder/named_type_builder.dart'; |
| import '../builder/nullability_builder.dart'; |
| import '../builder/prefix_builder.dart'; |
| import '../builder/procedure_builder.dart'; |
| import '../builder/type_alias_builder.dart'; |
| import '../builder/type_builder.dart'; |
| import '../builder/type_declaration_builder.dart'; |
| import '../builder/type_variable_builder.dart'; |
| import '../builder/unresolved_type.dart'; |
| import '../builder/variable_builder.dart'; |
| import '../builder/void_type_declaration_builder.dart'; |
| |
| import '../constant_context.dart' show ConstantContext; |
| |
| import '../dill/dill_library_builder.dart' show DillLibraryBuilder; |
| |
| import '../fasta_codes.dart' as fasta; |
| |
| import '../fasta_codes.dart' show LocatedMessage, Message, noLength, Template; |
| |
| import '../identifiers.dart' |
| show Identifier, InitializedIdentifier, QualifiedName, flattenName; |
| |
| import '../messages.dart' as messages show getLocationFromUri; |
| |
| import '../modifier.dart' |
| show Modifier, constMask, covariantMask, finalMask, lateMask, requiredMask; |
| |
| import '../names.dart' show emptyName, minusName, plusName; |
| |
| import '../problems.dart' |
| show internalProblem, unexpected, unhandled, unsupported; |
| |
| import '../scope.dart'; |
| |
| import '../source/scope_listener.dart' |
| show |
| FixedNullableList, |
| GrowableList, |
| JumpTargetKind, |
| NullValue, |
| ParserRecovery, |
| ScopeListener; |
| |
| import '../source/source_library_builder.dart' show SourceLibraryBuilder; |
| |
| import '../source/stack_listener_impl.dart' show offsetForToken; |
| |
| import '../source/value_kinds.dart'; |
| |
| import '../type_inference/type_inferrer.dart' |
| show TypeInferrer, InferredFunctionBody; |
| |
| import '../type_inference/type_promotion.dart' |
| show TypePromoter, TypePromotionFact, TypePromotionScope; |
| |
| import '../type_inference/type_schema.dart' show UnknownType; |
| |
| import '../util/helpers.dart' show DelayedActionPerformer; |
| |
| import 'collections.dart'; |
| |
| import 'constness.dart' show Constness; |
| |
| import 'expression_generator.dart'; |
| |
| import 'expression_generator_helper.dart' show ExpressionGeneratorHelper; |
| |
| import 'forest.dart' show Forest; |
| |
| import 'implicit_type_argument.dart' show ImplicitTypeArgument; |
| |
| import 'redirecting_factory_body.dart' |
| show |
| RedirectingFactoryBody, |
| RedirectionTarget, |
| getRedirectingFactoryBody, |
| getRedirectionTarget, |
| isRedirectingFactory; |
| |
| import 'type_algorithms.dart' show calculateBounds; |
| |
| import 'kernel_api.dart'; |
| |
| import 'kernel_ast_api.dart'; |
| |
| import 'internal_ast.dart'; |
| |
| import 'kernel_builder.dart'; |
| |
| // TODO(ahe): Remove this and ensure all nodes have a location. |
| const int noLocation = TreeNode.noOffset; |
| |
| // TODO(danrubel): Remove this once control flow and spread collection support |
| // has been enabled by default. |
| const Object invalidCollectionElement = const Object(); |
| |
| class BodyBuilder extends ScopeListener<JumpTarget> |
| implements ExpressionGeneratorHelper, EnsureLoaded, DelayedActionPerformer { |
| final Forest forest; |
| |
| // TODO(ahe): Rename [library] to 'part'. |
| @override |
| final SourceLibraryBuilder libraryBuilder; |
| |
| final ModifierBuilder member; |
| |
| /// The class, mixin or extension declaration in which [member] is declared, |
| /// if any. |
| final DeclarationBuilder declarationBuilder; |
| |
| /// The class or mixin declaration in which [member] is declared, if any. |
| final ClassBuilder classBuilder; |
| |
| final ClassHierarchy hierarchy; |
| |
| @override |
| final CoreTypes coreTypes; |
| |
| final bool isDeclarationInstanceMember; |
| |
| final Scope enclosingScope; |
| |
| final bool enableNative; |
| |
| final bool stringExpectedAfterNative; |
| |
| /// Whether to ignore an unresolved reference to `main` within the body of |
| /// `_getMainClosure` when compiling the current library. |
| /// |
| /// This as a temporary workaround. The standalone VM and flutter have |
| /// special logic to resolve `main` in `_getMainClosure`, this flag is used to |
| /// ignore that reference to `main`, but only on libraries where we expect to |
| /// see it (today that is dart:_builtin and dart:ui). |
| /// |
| // TODO(ahe,sigmund): remove when the VM gets rid of the special rule, see |
| // https://github.com/dart-lang/sdk/issues/28989. |
| final bool ignoreMainInGetMainClosure; |
| |
| // TODO(ahe): Consider renaming [uri] to 'partUri'. |
| @override |
| final Uri uri; |
| |
| final TypeInferrer typeInferrer; |
| |
| final TypePromoter typePromoter; |
| |
| /// Only used when [member] is a constructor. It tracks if an implicit super |
| /// initializer is needed. |
| /// |
| /// An implicit super initializer isn't needed |
| /// |
| /// 1. if the current class is Object, |
| /// 2. if there is an explicit super initializer, |
| /// 3. if there is a redirecting (this) initializer, or |
| /// 4. if a compile-time error prevented us from generating code for an |
| /// initializer. This avoids cascading errors. |
| bool needsImplicitSuperInitializer; |
| |
| Scope formalParameterScope; |
| |
| /// This is set to true when we start parsing an initializer. We use this to |
| /// find the correct scope for initializers like in this example: |
| /// |
| /// class C { |
| /// final x; |
| /// C(x) : x = x; |
| /// } |
| /// |
| /// When parsing this initializer `x = x`, `x` must be resolved in two |
| /// different scopes. The first `x` must be resolved in the class' scope, the |
| /// second in the formal parameter scope. |
| bool inInitializer = false; |
| |
| /// Set to `true` when we are parsing a field initializer either directly |
| /// or within an initializer list. |
| /// |
| /// For instance in `<init>` in |
| /// |
| /// var foo = <init>; |
| /// class Class { |
| /// var bar = <init>; |
| /// Class() : <init>; |
| /// } |
| /// |
| /// This is used to determine whether instance properties are available. |
| bool inFieldInitializer = false; |
| |
| /// `true` if we are directly in a field initializer of a late field. |
| /// |
| /// For instance in `<init>` in |
| /// |
| /// late var foo = <init>; |
| /// class Class { |
| /// late var bar = <init>; |
| /// Class() : bar = 42; |
| /// } |
| /// |
| bool inLateFieldInitializer = false; |
| |
| /// `true` if we are directly in the initializer of a late local. |
| /// |
| /// For instance in `<init>` in |
| /// |
| /// method() { |
| /// late var foo = <init>; |
| /// } |
| /// class Class { |
| /// method() { |
| /// late var bar = <init>; |
| /// } |
| /// } |
| /// |
| bool get inLateLocalInitializer => _localInitializerState.head; |
| |
| Link<bool> _isOrAsOperatorTypeState = const Link<bool>().prepend(false); |
| |
| @override |
| bool get inIsOrAsOperatorType => _isOrAsOperatorTypeState.head; |
| |
| Link<bool> _localInitializerState = const Link<bool>().prepend(false); |
| |
| List<Initializer> _initializers; |
| |
| bool inCatchClause = false; |
| |
| bool inCatchBlock = false; |
| |
| int functionNestingLevel = 0; |
| |
| // Set when a spread element is encountered in a collection so the collection |
| // needs to be desugared after type inference. |
| bool transformCollections = false; |
| |
| // Set by type inference when a set literal is encountered that needs to be |
| // transformed because the backend target does not support set literals. |
| bool transformSetLiterals = false; |
| |
| Statement problemInLoopOrSwitch; |
| |
| Scope switchScope; |
| |
| CloneVisitorNotMembers cloner; |
| |
| ConstantContext constantContext = ConstantContext.none; |
| |
| UnresolvedType currentLocalVariableType; |
| |
| // Using non-null value to initialize this field based on performance advice |
| // from VM engineers. TODO(ahe): Does this still apply? |
| int currentLocalVariableModifiers = -1; |
| |
| /// If non-null, records instance fields which have already been initialized |
| /// and where that was. |
| Map<String, int> initializedFields; |
| |
| /// List of built redirecting factory invocations. The targets of the |
| /// invocations are to be resolved in a separate step. |
| final List<Expression> redirectingFactoryInvocations = <Expression>[]; |
| |
| /// List of redirecting factory invocations delayed for resolution. |
| /// |
| /// A resolution of a redirecting factory invocation can be delayed because |
| /// the inference in the declaration of the redirecting factory isn't done |
| /// yet. |
| final List<Expression> delayedRedirectingFactoryInvocations = <Expression>[]; |
| |
| /// List of built type aliased generative constructor invocations that |
| /// require unaliasing. |
| final List<TypeAliasedConstructorInvocationJudgment> |
| typeAliasedConstructorInvocations = []; |
| |
| /// List of built type aliased factory constructor invocations that require |
| /// unaliasing. |
| final List<TypeAliasedFactoryInvocationJudgment> |
| typeAliasedFactoryInvocations = []; |
| |
| /// Variables with metadata. Their types need to be inferred late, for |
| /// example, in [finishFunction]. |
| List<VariableDeclaration> variablesWithMetadata; |
| |
| /// More than one variable declared in a single statement that has metadata. |
| /// Their types need to be inferred late, for example, in [finishFunction]. |
| List<List<VariableDeclaration>> multiVariablesWithMetadata; |
| |
| /// If the current member is an instance member in an extension declaration, |
| /// [extensionThis] holds the synthetically add parameter holding the value |
| /// for `this`. |
| final VariableDeclaration extensionThis; |
| |
| final List<TypeParameter> extensionTypeParameters; |
| |
| BodyBuilder( |
| {this.libraryBuilder, |
| this.member, |
| this.enclosingScope, |
| this.formalParameterScope, |
| this.hierarchy, |
| this.coreTypes, |
| this.declarationBuilder, |
| this.isDeclarationInstanceMember, |
| this.extensionThis, |
| this.extensionTypeParameters, |
| this.uri, |
| this.typeInferrer}) |
| : forest = const Forest(), |
| classBuilder = |
| declarationBuilder is ClassBuilder ? declarationBuilder : null, |
| enableNative = libraryBuilder.loader.target.backendTarget |
| .enableNative(libraryBuilder.importUri), |
| stringExpectedAfterNative = libraryBuilder |
| .loader.target.backendTarget.nativeExtensionExpectsString, |
| ignoreMainInGetMainClosure = |
| libraryBuilder.importUri.scheme == 'dart' && |
| (libraryBuilder.importUri.path == "_builtin" || |
| libraryBuilder.importUri.path == "ui"), |
| needsImplicitSuperInitializer = declarationBuilder is ClassBuilder && |
| coreTypes?.objectClass != declarationBuilder.cls, |
| typePromoter = typeInferrer?.typePromoter, |
| super(enclosingScope) { |
| formalParameterScope?.forEach((String name, Builder builder) { |
| if (builder is VariableBuilder) { |
| typeInferrer?.assignedVariables?.declare(builder.variable); |
| } |
| }); |
| } |
| |
| BodyBuilder.withParents(FieldBuilder field, SourceLibraryBuilder part, |
| DeclarationBuilder declarationBuilder, TypeInferrer typeInferrer) |
| : this( |
| libraryBuilder: part, |
| member: field, |
| enclosingScope: declarationBuilder?.scope ?? field.library.scope, |
| formalParameterScope: null, |
| hierarchy: part.loader.hierarchy, |
| coreTypes: part.loader.coreTypes, |
| declarationBuilder: declarationBuilder, |
| isDeclarationInstanceMember: field.isDeclarationInstanceMember, |
| extensionThis: null, |
| uri: field.fileUri, |
| typeInferrer: typeInferrer); |
| |
| BodyBuilder.forField(FieldBuilder field, TypeInferrer typeInferrer) |
| : this.withParents( |
| field, |
| field.parent is DeclarationBuilder |
| ? field.parent.parent |
| : field.parent, |
| field.parent is DeclarationBuilder ? field.parent : null, |
| typeInferrer); |
| |
| BodyBuilder.forOutlineExpression( |
| SourceLibraryBuilder library, |
| DeclarationBuilder declarationBuilder, |
| ModifierBuilder member, |
| Scope scope, |
| Uri fileUri) |
| : this( |
| libraryBuilder: library, |
| member: member, |
| enclosingScope: scope, |
| formalParameterScope: null, |
| hierarchy: library.loader.hierarchy, |
| coreTypes: library.loader.coreTypes, |
| declarationBuilder: declarationBuilder, |
| isDeclarationInstanceMember: |
| member?.isDeclarationInstanceMember ?? false, |
| extensionThis: null, |
| uri: fileUri, |
| typeInferrer: library.loader.typeInferenceEngine |
| ?.createLocalTypeInferrer( |
| fileUri, declarationBuilder?.thisType, library, null)); |
| |
| bool get inConstructor { |
| return functionNestingLevel == 0 && member is ConstructorBuilder; |
| } |
| |
| bool get isDeclarationInstanceContext { |
| return isDeclarationInstanceMember || member is ConstructorBuilder; |
| } |
| |
| TypeEnvironment get typeEnvironment => typeInferrer?.typeSchemaEnvironment; |
| |
| DartType get implicitTypeArgument => const ImplicitTypeArgument(); |
| |
| @override |
| bool get enableExtensionTypesInLibrary { |
| return libraryBuilder.enableExtensionTypesInLibrary; |
| } |
| |
| @override |
| bool get enableConstFunctionsInLibrary { |
| return libraryBuilder.enableConstFunctionsInLibrary; |
| } |
| |
| void _enterLocalState({bool inLateLocalInitializer: false}) { |
| _localInitializerState = |
| _localInitializerState.prepend(inLateLocalInitializer); |
| } |
| |
| void _exitLocalState() { |
| _localInitializerState = _localInitializerState.tail; |
| } |
| |
| @override |
| void registerVariableAssignment(VariableDeclaration variable) { |
| typePromoter?.mutateVariable(variable, functionNestingLevel); |
| typeInferrer?.assignedVariables?.write(variable); |
| } |
| |
| @override |
| VariableDeclaration createVariableDeclarationForValue(Expression expression) { |
| VariableDeclaration variable = |
| forest.createVariableDeclarationForValue(expression); |
| typeInferrer?.assignedVariables?.declare(variable); |
| return variable; |
| } |
| |
| @override |
| void push(Object node) { |
| if (node is DartType) { |
| unhandled("DartType", "push", -1, uri); |
| } |
| inInitializer = false; |
| super.push(node); |
| } |
| |
| Expression popForValue() => toValue(pop()); |
| |
| Expression popForEffect() => toEffect(pop()); |
| |
| Expression popForValueIfNotNull(Object value) { |
| return value == null ? null : popForValue(); |
| } |
| |
| @override |
| Expression toValue(Object node) { |
| if (node is Generator) { |
| return node.buildSimpleRead(); |
| } else if (node is Expression) { |
| return node; |
| } else if (node is SuperInitializer) { |
| return buildProblem( |
| fasta.messageSuperAsExpression, node.fileOffset, noLength); |
| } else if (node is ProblemBuilder) { |
| return buildProblem(node.message, node.charOffset, noLength); |
| } else { |
| return unhandled("${node.runtimeType}", "toValue", -1, uri); |
| } |
| } |
| |
| Expression toEffect(Object node) { |
| if (node is Generator) return node.buildForEffect(); |
| return toValue(node); |
| } |
| |
| List<Expression> popListForValue(int n) { |
| List<Expression> list = |
| new List<Expression>.filled(n, null, 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, null, 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>().pop(stack, count) ?? <Statement>[]); |
| } |
| |
| Statement popStatementIfNotNull(Object value) { |
| return value == null ? null : popStatement(); |
| } |
| |
| Statement popStatement() => forest.wrapVariables(pop()); |
| |
| void enterSwitchScope() { |
| push(switchScope ?? NullValue.SwitchScope); |
| switchScope = scope; |
| } |
| |
| void exitSwitchScope() { |
| Scope outerSwitchScope = pop(); |
| if (switchScope.unclaimedForwardDeclarations != null) { |
| switchScope.unclaimedForwardDeclarations |
| .forEach((String name, JumpTarget declaration) { |
| if (outerSwitchScope == null) { |
| for (Statement statement in declaration.users) { |
| statement.parent.replaceChild( |
| statement, |
| wrapInProblemStatement(statement, |
| fasta.templateLabelNotFound.withArguments(name))); |
| } |
| } else { |
| outerSwitchScope.forwardDeclareLabel(name, declaration); |
| } |
| }); |
| } |
| switchScope = outerSwitchScope; |
| } |
| |
| void wrapVariableInitializerInError( |
| VariableDeclaration variable, |
| Template<Message Function(String name)> template, |
| List<LocatedMessage> context) { |
| String name = variable.name; |
| int offset = variable.fileOffset; |
| Message message = template.withArguments(name); |
| if (variable.initializer == null) { |
| variable.initializer = |
| buildProblem(message, offset, name.length, context: context) |
| ..parent = variable; |
| } else { |
| variable.initializer = wrapInLocatedProblem( |
| variable.initializer, message.withLocation(uri, offset, name.length), |
| context: context) |
| ..parent = variable; |
| } |
| } |
| |
| void declareVariable(VariableDeclaration variable, Scope scope) { |
| String name = variable.name; |
| Builder existing = scope.lookupLocalMember(name, setter: false); |
| if (existing != null) { |
| // This reports an error for duplicated declarations in the same scope: |
| // `{ var x; var x; }` |
| wrapVariableInitializerInError( |
| variable, fasta.templateDuplicatedDeclaration, <LocatedMessage>[ |
| fasta.templateDuplicatedDeclarationCause |
| .withArguments(name) |
| .withLocation(uri, existing.charOffset, name.length) |
| ]); |
| return; |
| } |
| LocatedMessage context = scope.declare( |
| variable.name, |
| new VariableBuilderImpl( |
| variable, member ?? classBuilder ?? libraryBuilder, uri), |
| uri); |
| if (context != null) { |
| // This case is different from the above error. In this case, the problem |
| // is using `x` before it's declared: `{ var x; { print(x); var x; |
| // }}`. In this case, we want two errors, the `x` in `print(x)` and the |
| // second (or innermost declaration) of `x`. |
| wrapVariableInitializerInError( |
| variable, |
| fasta.templateDuplicatedNamePreviouslyUsed, |
| <LocatedMessage>[context]); |
| } |
| } |
| |
| @override |
| JumpTarget createJumpTarget(JumpTargetKind kind, int charOffset) { |
| return new JumpTarget(kind, functionNestingLevel, member, charOffset); |
| } |
| |
| void inferAnnotations(TreeNode parent, List<Expression> annotations) { |
| if (annotations != null) { |
| typeInferrer?.inferMetadata(this, parent, annotations); |
| libraryBuilder.loader.transformListPostInference(annotations, |
| transformSetLiterals, transformCollections, libraryBuilder.library); |
| } |
| } |
| |
| @override |
| void beginMetadata(Token token) { |
| debugEvent("beginMetadata"); |
| super.push(constantContext); |
| constantContext = ConstantContext.inferred; |
| } |
| |
| @override |
| void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { |
| debugEvent("Metadata"); |
| Arguments arguments = pop(); |
| pushQualifiedReference(beginToken.next, periodBeforeName); |
| if (arguments != null) { |
| push(arguments); |
| _buildConstructorReferenceInvocation( |
| beginToken.next, beginToken.offset, Constness.explicitConst, |
| inMetadata: true); |
| push(popForValue()); |
| } else { |
| pop(); // Name last identifier |
| String name = pop(); |
| pop(); // Type arguments (ignored, already reported by parser). |
| Object expression = pop(); |
| if (expression is Identifier) { |
| Identifier identifier = expression; |
| expression = new UnresolvedNameGenerator(this, identifier.token, |
| new Name(identifier.name, libraryBuilder.nameOrigin)); |
| } |
| if (name?.isNotEmpty ?? false) { |
| Token period = periodBeforeName ?? beginToken.next.next; |
| Generator generator = expression; |
| expression = generator.buildPropertyAccess( |
| new IncompletePropertyAccessGenerator( |
| this, period.next, new Name(name, libraryBuilder.nameOrigin)), |
| period.next.offset, |
| false); |
| } |
| |
| ConstantContext savedConstantContext = pop(); |
| if (expression is! StaticAccessGenerator && |
| expression is! VariableUseGenerator && |
| // TODO(johnniwinther): Stop using the type of the generator here. |
| // Ask a property instead. |
| (expression is! ReadOnlyAccessGenerator || |
| expression is TypeUseGenerator || |
| expression is ParenthesizedExpressionGenerator)) { |
| Expression value = toValue(expression); |
| push(wrapInProblem(value, fasta.messageExpressionNotMetadata, |
| value.fileOffset, noLength)); |
| } else { |
| push(toValue(expression)); |
| } |
| constantContext = savedConstantContext; |
| } |
| } |
| |
| @override |
| void endMetadataStar(int count) { |
| debugEvent("MetadataStar"); |
| if (count == 0) { |
| push(NullValue.Metadata); |
| } else { |
| push(const GrowableList<Expression>().pop(stack, count) ?? |
| NullValue.Metadata /* Ignore parser recovery */); |
| } |
| } |
| |
| @override |
| void endTopLevelFields( |
| Token externalToken, |
| Token staticToken, |
| Token covariantToken, |
| Token lateToken, |
| Token varFinalOrConst, |
| int count, |
| Token beginToken, |
| Token endToken) { |
| debugEvent("TopLevelFields"); |
| if (!libraryBuilder.isNonNullableByDefault) { |
| reportNonNullableModifierError(lateToken); |
| if (externalToken != null) { |
| handleRecoverableError( |
| fasta.messageExternalField, externalToken, externalToken); |
| } |
| } |
| push(count); |
| } |
| |
| @override |
| void endClassFields( |
| Token abstractToken, |
| Token externalToken, |
| Token staticToken, |
| Token covariantToken, |
| Token lateToken, |
| Token varFinalOrConst, |
| int count, |
| Token beginToken, |
| Token endToken) { |
| debugEvent("Fields"); |
| if (!libraryBuilder.isNonNullableByDefault) { |
| reportNonNullableModifierError(lateToken); |
| if (abstractToken != null) { |
| handleRecoverableError( |
| fasta.messageAbstractClassMember, abstractToken, abstractToken); |
| } |
| if (externalToken != null) { |
| handleRecoverableError( |
| fasta.messageExternalField, externalToken, externalToken); |
| } |
| } |
| push(count); |
| } |
| |
| @override |
| void finishFields() { |
| debugEvent("finishFields"); |
| int count = pop(); |
| List<FieldBuilder> fields = <FieldBuilder>[]; |
| for (int i = 0; i < count; i++) { |
| Expression initializer = pop(); |
| Identifier identifier = pop(); |
| String name = identifier.name; |
| Builder declaration; |
| if (declarationBuilder != null) { |
| declaration = |
| declarationBuilder.lookupLocalMember(name, required: true); |
| } else { |
| declaration = libraryBuilder.lookupLocalMember(name, required: true); |
| } |
| FieldBuilder fieldBuilder; |
| if (declaration.isField && declaration.next == null) { |
| fieldBuilder = declaration; |
| } else { |
| continue; |
| } |
| fields.add(fieldBuilder); |
| if (initializer != null) { |
| if (fieldBuilder.isDuplicate) { |
| // Duplicate definition. The field might not be the correct one, |
| // so we skip inference of the initializer. |
| // Error reporting and recovery is handled elsewhere. |
| } else if (fieldBuilder.hasBodyBeenBuilt) { |
| // The initializer was already compiled (e.g., if it appear in the |
| // outline, like constant field initializers) so we do not need to |
| // perform type inference or transformations. |
| } else { |
| initializer = typeInferrer?.inferFieldInitializer( |
| this, fieldBuilder.builtType, initializer); |
| |
| if (transformCollections || transformSetLiterals) { |
| // Wrap the initializer in a temporary parent expression; the |
| // transformations need a parent relation. |
| Not wrapper = new Not(initializer); |
| libraryBuilder.loader.transformPostInference( |
| wrapper, |
| transformSetLiterals, |
| transformCollections, |
| libraryBuilder.library); |
| initializer = wrapper.operand; |
| } |
| fieldBuilder.buildBody(coreTypes, initializer); |
| } |
| } else if (!fieldBuilder.hasBodyBeenBuilt) { |
| fieldBuilder.buildBody(coreTypes, null); |
| } |
| } |
| { |
| // 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`. |
| buildDartType(pop()); // Type. |
| } |
| pop(); // Annotations. |
| |
| resolveRedirectingFactoryTargets(); |
| finishVariableMetadata(); |
| } |
| |
| @override |
| void endMember() { |
| debugEvent("Member"); |
| } |
| |
| @override |
| void endBlockFunctionBody(int count, Token openBrace, Token closeBrace) { |
| debugEvent("BlockFunctionBody"); |
| if (openBrace == null) { |
| assert(count == 0); |
| push(NullValue.Block); |
| } else { |
| Statement block = popBlock(count, openBrace, closeBrace); |
| exitLocalScope(); |
| push(block); |
| } |
| } |
| |
| void prepareInitializers() { |
| FunctionBuilder member = this.member; |
| scope = member.computeFormalParameterInitializerScope(scope); |
| if (member is ConstructorBuilder) { |
| member.prepareInitializers(); |
| if (member.formals != null) { |
| for (FormalParameterBuilder formal in member.formals) { |
| if (formal.isInitializingFormal) { |
| List<Initializer> initializers; |
| if (member.isExternal) { |
| initializers = <Initializer>[ |
| buildInvalidInitializer( |
| buildProblem( |
| fasta.messageExternalConstructorWithFieldInitializers, |
| formal.charOffset, |
| formal.name.length), |
| formal.charOffset) |
| ]; |
| } else { |
| initializers = buildFieldInitializer( |
| formal.name, |
| formal.charOffset, |
| formal.charOffset, |
| new VariableGet(formal.variable), |
| formal: formal); |
| } |
| for (Initializer initializer in initializers) { |
| member.addInitializer(initializer, this); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @override |
| void handleNoInitializers() { |
| debugEvent("NoInitializers"); |
| if (functionNestingLevel == 0) { |
| prepareInitializers(); |
| scope = formalParameterScope; |
| } |
| } |
| |
| @override |
| void beginInitializers(Token token) { |
| debugEvent("beginInitializers"); |
| if (functionNestingLevel == 0) { |
| prepareInitializers(); |
| } |
| } |
| |
| @override |
| void endInitializers(int count, Token beginToken, Token endToken) { |
| debugEvent("Initializers"); |
| if (functionNestingLevel == 0) { |
| scope = formalParameterScope; |
| } |
| } |
| |
| @override |
| void beginInitializer(Token token) { |
| debugEvent("beginInitializer"); |
| inInitializer = true; |
| inFieldInitializer = true; |
| } |
| |
| @override |
| void endInitializer(Token token) { |
| debugEvent("endInitializer"); |
| inFieldInitializer = false; |
| assert(!inInitializer); |
| Object node = pop(); |
| List<Initializer> initializers; |
| |
| final ModifierBuilder member = this.member; |
| if (!(member is ConstructorBuilder && !member.isExternal)) { |
| // An error has been reported by the parser. |
| initializers = <Initializer>[]; |
| } else if (node is Initializer) { |
| initializers = <Initializer>[node]; |
| } else if (node is Generator) { |
| initializers = node.buildFieldInitializer(initializedFields); |
| } else if (node is ConstructorInvocation) { |
| initializers = <Initializer>[ |
| buildSuperInitializer( |
| false, node.target, node.arguments, token.charOffset) |
| ]; |
| } else { |
| Expression value = toValue(node); |
| if (!forest.isThrow(node)) { |
| value = wrapInProblem(value, fasta.messageExpectedAnInitializer, |
| value.fileOffset, noLength); |
| } |
| initializers = <Initializer>[ |
| buildInvalidInitializer(node, token.charOffset) |
| ]; |
| } |
| _initializers ??= <Initializer>[]; |
| _initializers.addAll(initializers); |
| } |
| |
| DartType _computeReturnTypeContext(MemberBuilder member) { |
| if (member is ProcedureBuilder) { |
| return member.actualProcedure.function.returnType; |
| } else { |
| assert(member is ConstructorBuilder); |
| return const DynamicType(); |
| } |
| } |
| |
| @override |
| void finishFunction( |
| FormalParameters formals, AsyncMarker asyncModifier, Statement body) { |
| debugEvent("finishFunction"); |
| typePromoter?.finished(); |
| typeInferrer?.assignedVariables?.finish(); |
| |
| FunctionBuilder builder = member; |
| if (extensionThis != null) { |
| typeInferrer?.flowAnalysis?.declare(extensionThis, true); |
| } |
| if (formals?.parameters != null) { |
| for (int i = 0; i < formals.parameters.length; i++) { |
| FormalParameterBuilder parameter = formals.parameters[i]; |
| typeInferrer?.flowAnalysis?.declare(parameter.variable, true); |
| } |
| for (int i = 0; i < formals.parameters.length; i++) { |
| FormalParameterBuilder parameter = formals.parameters[i]; |
| Expression initializer = parameter.variable.initializer; |
| if (parameter.isOptional || initializer != null) { |
| if (!parameter.initializerWasInferred) { |
| parameter.initializerWasInferred = true; |
| if (parameter.isOptional) { |
| initializer ??= forest.createNullLiteral( |
| // TODO(ahe): Should store: originParameter.fileOffset |
| // https://github.com/dart-lang/sdk/issues/32289 |
| noLocation); |
| } |
| VariableDeclaration originParameter = builder.getFormalParameter(i); |
| initializer = typeInferrer?.inferParameterInitializer( |
| this, |
| initializer, |
| originParameter.type, |
| parameter.hasDeclaredInitializer); |
| originParameter.initializer = initializer..parent = originParameter; |
| libraryBuilder.loader.transformPostInference( |
| originParameter, |
| transformSetLiterals, |
| transformCollections, |
| libraryBuilder.library); |
| } |
| |
| VariableDeclaration extensionTearOffParameter = |
| builder.getExtensionTearOffParameter(i); |
| if (extensionTearOffParameter != null) { |
| cloner ??= new CloneVisitorNotMembers(); |
| Expression tearOffInitializer = cloner.clone(initializer); |
| extensionTearOffParameter.initializer = tearOffInitializer |
| ..parent = extensionTearOffParameter; |
| libraryBuilder.loader.transformPostInference( |
| extensionTearOffParameter, |
| transformSetLiterals, |
| transformCollections, |
| libraryBuilder.library); |
| } |
| } |
| } |
| } |
| if (builder is ConstructorBuilder) { |
| finishConstructor(builder, asyncModifier, body); |
| } else if (builder is ProcedureBuilder) { |
| builder.asyncModifier = asyncModifier; |
| } else { |
| unhandled("${builder.runtimeType}", "finishFunction", builder.charOffset, |
| builder.fileUri); |
| } |
| |
| InferredFunctionBody inferredFunctionBody; |
| if (body != null) { |
| inferredFunctionBody = typeInferrer?.inferFunctionBody( |
| this, |
| member.charOffset, |
| _computeReturnTypeContext(member), |
| asyncModifier, |
| body); |
| body = inferredFunctionBody.body; |
| builder.function.futureValueType = inferredFunctionBody.futureValueType; |
| libraryBuilder.loader.transformPostInference(body, transformSetLiterals, |
| transformCollections, libraryBuilder.library); |
| } |
| |
| if (builder.returnType != null) { |
| checkAsyncReturnType(asyncModifier, builder.function.returnType, |
| member.charOffset, member.name.length); |
| } |
| |
| if (builder.kind == ProcedureKind.Setter) { |
| if (formals?.parameters == null || |
| formals.parameters.length != 1 || |
| formals.parameters.single.isOptional) { |
| int charOffset = formals?.charOffset ?? |
| body?.fileOffset ?? |
| builder.member.fileOffset; |
| if (body == null) { |
| body = new EmptyStatement()..fileOffset = charOffset; |
| } |
| if (builder.formals != null) { |
| // Illegal parameters were removed by the function builder. |
| // Add them as local variable to put them in scope of the body. |
| List<Statement> statements = <Statement>[]; |
| for (FormalParameterBuilder parameter in builder.formals) { |
| statements.add(parameter.variable); |
| } |
| statements.add(body); |
| body = forest.createBlock(charOffset, noLocation, statements); |
| } |
| body = forest.createBlock(charOffset, noLocation, <Statement>[ |
| forest.createExpressionStatement( |
| noLocation, |
| // This error is added after type inference is done, so we |
| // don't need to wrap errors in SyntheticExpressionJudgment. |
| buildProblem(fasta.messageSetterWithWrongNumberOfFormals, |
| charOffset, noLength)), |
| body, |
| ]); |
| } |
| } |
| // No-such-method forwarders get their bodies injected during outline |
| // building, so we should skip them here. |
| bool isNoSuchMethodForwarder = (builder.function.parent is Procedure && |
| (builder.function.parent as Procedure).isNoSuchMethodForwarder); |
| if (body != null) { |
| if (!builder.isExternal && !isNoSuchMethodForwarder) { |
| builder.body = body; |
| } else { |
| builder.body = new Block(<Statement>[ |
| new ExpressionStatement(buildProblem( |
| fasta.messageExternalMethodWithBody, body.fileOffset, noLength)) |
| ..fileOffset = body.fileOffset, |
| body, |
| ]) |
| ..fileOffset = body.fileOffset; |
| } |
| } |
| |
| resolveRedirectingFactoryTargets(); |
| finishVariableMetadata(); |
| } |
| |
| void checkAsyncReturnType(AsyncMarker asyncModifier, DartType returnType, |
| int charOffset, int length) { |
| // For async, async*, and sync* functions with declared return types, we |
| // need to determine whether those types are valid. |
| // We use the same trick in each case below. For example to decide whether |
| // Future<T> <: [returnType] for every T, we rely on Future<Bot> and |
| // transitivity of the subtyping relation because Future<Bot> <: Future<T> |
| // for every T. |
| |
| // We use [problem == null] to signal success. |
| Message problem; |
| switch (asyncModifier) { |
| case AsyncMarker.Async: |
| DartType futureBottomType = libraryBuilder.loader.futureOfBottom; |
| if (!typeEnvironment.isSubtypeOf( |
| futureBottomType, returnType, SubtypeCheckMode.withNullabilities)) { |
| problem = fasta.messageIllegalAsyncReturnType; |
| } |
| break; |
| |
| case AsyncMarker.AsyncStar: |
| DartType streamBottomType = libraryBuilder.loader.streamOfBottom; |
| if (returnType is VoidType) { |
| problem = fasta.messageIllegalAsyncGeneratorVoidReturnType; |
| } else if (!typeEnvironment.isSubtypeOf( |
| streamBottomType, returnType, SubtypeCheckMode.withNullabilities)) { |
| problem = fasta.messageIllegalAsyncGeneratorReturnType; |
| } |
| break; |
| |
| case AsyncMarker.SyncStar: |
| DartType iterableBottomType = libraryBuilder.loader.iterableOfBottom; |
| if (returnType is VoidType) { |
| problem = fasta.messageIllegalSyncGeneratorVoidReturnType; |
| } else if (!typeEnvironment.isSubtypeOf(iterableBottomType, returnType, |
| SubtypeCheckMode.withNullabilities)) { |
| problem = fasta.messageIllegalSyncGeneratorReturnType; |
| } |
| break; |
| |
| case AsyncMarker.Sync: |
| break; // skip |
| case AsyncMarker.SyncYielding: |
| unexpected("async, async*, sync, or sync*", "$asyncModifier", |
| member.charOffset, uri); |
| break; |
| } |
| |
| 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.builders[ensureLibraryLoaded.importUri] ?? |
| libraryBuilder.loader.target.dillTarget.loader |
| .builders[ensureLibraryLoaded.importUri]; |
| if (builder is DillLibraryBuilder) { |
| builder.ensureLoaded(); |
| } |
| } |
| |
| /// Check if the containing library of the [member] has been loaded. |
| /// |
| /// This is designed for use with asserts. |
| /// See [ensureLoaded] for a description of what 'loaded' means and the ideas |
| /// behind that. |
| bool isLoaded(Member member) { |
| if (member == null) return true; |
| Library ensureLibraryLoaded = member.enclosingLibrary; |
| LibraryBuilder builder = |
| libraryBuilder.loader.builders[ensureLibraryLoaded.importUri] ?? |
| libraryBuilder.loader.target.dillTarget.loader |
| .builders[ensureLibraryLoaded.importUri]; |
| if (builder is DillLibraryBuilder) { |
| return builder.isBuiltAndMarked; |
| } |
| return true; |
| } |
| |
| // TODO(eernst): Rename this method now that it handles more tasks. |
| void resolveRedirectingFactoryTargets() { |
| _unaliasTypeAliasedConstructorInvocations(); |
| _unaliasTypeAliasedFactoryInvocations(); |
| _resolveRedirectingFactoryTargets( |
| redirectingFactoryInvocations, delayedRedirectingFactoryInvocations); |
| } |
| |
| /// Return an [Expression] resolving the argument invocation. |
| /// |
| /// The arguments specify the [StaticInvocation] whose `.target` is |
| /// [target], `.arguments` is [arguments], `.fileOffset` is [fileOffset], |
| /// and `.isConst` is [isConst]. |
| /// Returns null if the invocation can't be resolved. |
| Expression _resolveRedirectingFactoryTarget( |
| Procedure target, Arguments arguments, int fileOffset, bool isConst) { |
| Procedure initialTarget = target; |
| Expression replacementNode; |
| |
| RedirectionTarget redirectionTarget = |
| getRedirectionTarget(initialTarget, this); |
| Member resolvedTarget = redirectionTarget?.target; |
| if (redirectionTarget != null && |
| redirectionTarget.typeArguments.any((type) => type is UnknownType)) { |
| return null; |
| } |
| |
| if (resolvedTarget == null) { |
| String name = constructorNameForDiagnostics(initialTarget.name.text, |
| className: initialTarget.enclosingClass.name); |
| // TODO(dmitryas): Report this error earlier. |
| replacementNode = buildProblem( |
| fasta.templateCyclicRedirectingFactoryConstructors |
| .withArguments(name), |
| initialTarget.fileOffset, |
| name.length); |
| } else if (resolvedTarget is Constructor && |
| resolvedTarget.enclosingClass.isAbstract) { |
| replacementNode = evaluateArgumentsBefore( |
| forest.createArguments(noLocation, arguments.positional, |
| types: arguments.types, named: arguments.named), |
| buildAbstractClassInstantiationError( |
| fasta.templateAbstractRedirectedClassInstantiation |
| .withArguments(resolvedTarget.enclosingClass.name), |
| resolvedTarget.enclosingClass.name, |
| initialTarget.fileOffset)); |
| } else { |
| RedirectingFactoryBody redirectingFactoryBody = |
| getRedirectingFactoryBody(resolvedTarget); |
| if (redirectingFactoryBody != null) { |
| // If the redirection target is itself a redirecting factory, it means |
| // that it is unresolved. |
| assert(redirectingFactoryBody.isUnresolved); |
| String errorName = redirectingFactoryBody.unresolvedName; |
| replacementNode = buildProblem( |
| fasta.templateMethodNotFound.withArguments(errorName), |
| fileOffset, |
| noLength, |
| suppressMessage: true); |
| } else { |
| Substitution substitution = Substitution.fromPairs( |
| initialTarget.function.typeParameters, arguments.types); |
| arguments.types.clear(); |
| arguments.types.length = redirectionTarget.typeArguments.length; |
| for (int i = 0; i < arguments.types.length; i++) { |
| arguments.types[i] = |
| substitution.substituteType(redirectionTarget.typeArguments[i]); |
| } |
| |
| replacementNode = buildStaticInvocation( |
| resolvedTarget, |
| forest.createArguments(noLocation, arguments.positional, |
| types: arguments.types, |
| named: arguments.named, |
| hasExplicitTypeArguments: hasExplicitTypeArguments(arguments)), |
| constness: |
| isConst ? Constness.explicitConst : Constness.explicitNew, |
| charOffset: fileOffset); |
| } |
| } |
| return replacementNode; |
| } |
| |
| void _resolveRedirectingFactoryTargets( |
| List<Expression> redirectingFactoryInvocations, |
| List<Expression> delayedRedirectingFactoryInvocations) { |
| for (StaticInvocation invocation in redirectingFactoryInvocations) { |
| // If the invocation was invalid, it or its parent has already been |
| // desugared into an exception throwing expression. There is nothing to |
| // resolve anymore. Note that in the case where the invocation's parent |
| // was invalid, type inference won't reach the invocation node and won't |
| // set its inferredType field. If type inference is disabled, reach to |
| // the outermost parent to check if the node is a dead code. |
| if (invocation.parent == null) continue; |
| if (typeInferrer != null) { |
| if (invocation is FactoryConstructorInvocationJudgment && |
| !invocation.hasBeenInferred) { |
| continue; |
| } |
| } else { |
| TreeNode parent = invocation.parent; |
| while (parent is! Component && parent != null) { |
| parent = parent.parent; |
| } |
| if (parent == null) continue; |
| } |
| Expression replacement = _resolveRedirectingFactoryTarget( |
| invocation.target, |
| invocation.arguments, |
| invocation.fileOffset, |
| invocation.isConst); |
| if (replacement == null) { |
| delayedRedirectingFactoryInvocations?.add(invocation); |
| } else { |
| invocation.replaceWith(replacement); |
| } |
| } |
| redirectingFactoryInvocations.clear(); |
| } |
| |
| void _unaliasTypeAliasedConstructorInvocations() { |
| for (TypeAliasedConstructorInvocationJudgment invocation |
| in typeAliasedConstructorInvocations) { |
| DartType unaliasedType = new TypedefType( |
| invocation.typeAliasBuilder.typedef, |
| Nullability.nonNullable, |
| invocation.arguments.types) |
| .unalias; |
| List<DartType> invocationTypeArguments = null; |
| if (unaliasedType is InterfaceType) { |
| invocationTypeArguments = unaliasedType.typeArguments; |
| } |
| Arguments invocationArguments = forest.createArguments( |
| noLocation, invocation.arguments.positional, |
| types: invocationTypeArguments, named: invocation.arguments.named); |
| invocation.replaceWith(new ConstructorInvocation( |
| invocation.target, invocationArguments, |
| isConst: invocation.isConst)); |
| } |
| typeAliasedConstructorInvocations.clear(); |
| } |
| |
| void _unaliasTypeAliasedFactoryInvocations() { |
| for (TypeAliasedFactoryInvocationJudgment invocation |
| in typeAliasedFactoryInvocations) { |
| DartType unaliasedType = new TypedefType( |
| invocation.typeAliasBuilder.typedef, |
| Nullability.nonNullable, |
| invocation.arguments.types) |
| .unalias; |
| List<DartType> invocationTypeArguments = null; |
| if (unaliasedType is InterfaceType) { |
| invocationTypeArguments = unaliasedType.typeArguments; |
| } |
| Arguments invocationArguments = forest.createArguments( |
| noLocation, invocation.arguments.positional, |
| types: invocationTypeArguments, |
| named: invocation.arguments.named, |
| hasExplicitTypeArguments: |
| hasExplicitTypeArguments(invocation.arguments)); |
| invocation.replaceWith(_resolveRedirectingFactoryTarget(invocation.target, |
| invocationArguments, invocation.fileOffset, invocation.isConst)); |
| } |
| typeAliasedFactoryInvocations.clear(); |
| } |
| |
| /// Perform actions that were delayed |
| /// |
| /// An action can be delayed, for instance, because it depends on some |
| /// calculations in another library. For example, a resolution of a |
| /// redirecting factory invocation depends on the type inference in the |
| /// redirecting factory. |
| void performDelayedActions() { |
| if (delayedRedirectingFactoryInvocations.isNotEmpty) { |
| _resolveRedirectingFactoryTargets( |
| delayedRedirectingFactoryInvocations, null); |
| if (delayedRedirectingFactoryInvocations.isNotEmpty) { |
| for (StaticInvocation invocation |
| in delayedRedirectingFactoryInvocations) { |
| internalProblem( |
| fasta.templateInternalProblemUnhandled.withArguments( |
| invocation.target.name.text, 'performDelayedActions'), |
| invocation.fileOffset, |
| uri); |
| } |
| } |
| } |
| } |
| |
| bool get hasDelayedActions { |
| return delayedRedirectingFactoryInvocations.isNotEmpty; |
| } |
| |
| void finishVariableMetadata() { |
| List<VariableDeclaration> variablesWithMetadata = |
| this.variablesWithMetadata; |
| this.variablesWithMetadata = null; |
| List<List<VariableDeclaration>> multiVariablesWithMetadata = |
| this.multiVariablesWithMetadata; |
| this.multiVariablesWithMetadata = null; |
| |
| if (variablesWithMetadata != null) { |
| for (int i = 0; i < variablesWithMetadata.length; i++) { |
| inferAnnotations( |
| variablesWithMetadata[i], variablesWithMetadata[i].annotations); |
| } |
| } |
| if (multiVariablesWithMetadata != null) { |
| for (int i = 0; i < multiVariablesWithMetadata.length; i++) { |
| List<VariableDeclaration> variables = multiVariablesWithMetadata[i]; |
| List<Expression> annotations = variables.first.annotations; |
| inferAnnotations(variables.first, annotations); |
| for (int i = 1; i < variables.length; i++) { |
| cloner ??= new CloneVisitorNotMembers(); |
| VariableDeclaration variable = variables[i]; |
| for (int i = 0; i < annotations.length; i++) { |
| variable.addAnnotation(cloner.clone(annotations[i])); |
| } |
| } |
| } |
| } |
| } |
| |
| @override |
| List<Expression> finishMetadata(TreeNode parent) { |
| List<Expression> expressions = pop(); |
| 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 is Class) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is Library) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is LibraryDependency) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is LibraryPart) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is Member) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is Typedef) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is TypeParameter) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else if (parent is VariableDeclaration) { |
| for (Expression expression in expressions) { |
| parent.addAnnotation(expression); |
| } |
| } else { |
| temporaryParent = new ListLiteral(expressions); |
| } |
| resolveRedirectingFactoryTargets(); |
| finishVariableMetadata(); |
| return temporaryParent != null ? temporaryParent.expressions : expressions; |
| } |
| |
| @override |
| Expression parseSingleExpression( |
| Parser parser, Token token, FunctionNode parameters) { |
| assert(redirectingFactoryInvocations.isEmpty); |
| int fileOffset = offsetForToken(token); |
| List<TypeVariableBuilder> typeParameterBuilders; |
| for (TypeParameter typeParameter in parameters.typeParameters) { |
| typeParameterBuilders ??= <TypeVariableBuilder>[]; |
| typeParameterBuilders.add( |
| new TypeVariableBuilder.fromKernel(typeParameter, libraryBuilder)); |
| } |
| enterFunctionTypeScope(typeParameterBuilders); |
| |
| List<FormalParameterBuilder> formals = |
| parameters.positionalParameters.length == 0 |
| ? null |
| : new List<FormalParameterBuilder>.filled( |
| parameters.positionalParameters.length, null); |
| for (int i = 0; i < parameters.positionalParameters.length; i++) { |
| VariableDeclaration formal = parameters.positionalParameters[i]; |
| formals[i] = new FormalParameterBuilder( |
| null, 0, null, formal.name, libraryBuilder, formal.fileOffset, |
| fileUri: uri) |
| ..variable = formal; |
| } |
| enterLocalScope( |
| null, |
| new FormalParameters(formals, fileOffset, noLength, uri) |
| .computeFormalParameterScope(scope, member, this)); |
| |
| token = parser.parseExpression(parser.syntheticPreviousToken(token)); |
| |
| Expression expression = popForValue(); |
| Token eof = token.next; |
| |
| if (!eof.isEof) { |
| expression = wrapInLocatedProblem( |
| expression, |
| fasta.messageExpectedOneExpression |
| .withLocation(uri, eof.charOffset, eof.length)); |
| } |
| |
| ReturnStatementImpl fakeReturn = new ReturnStatementImpl(true, expression); |
| if (formals != null) { |
| for (int i = 0; i < formals.length; i++) { |
| typeInferrer?.flowAnalysis?.declare(formals[i].variable, true); |
| } |
| } |
| InferredFunctionBody inferredFunctionBody = typeInferrer?.inferFunctionBody( |
| this, fileOffset, const DynamicType(), AsyncMarker.Sync, fakeReturn); |
| assert( |
| fakeReturn == inferredFunctionBody.body, |
| "Previously implicit assumption about inferFunctionBody " |
| "not returning anything different."); |
| |
| resolveRedirectingFactoryTargets(); |
| libraryBuilder.loader.transformPostInference(fakeReturn, |
| transformSetLiterals, transformCollections, libraryBuilder.library); |
| |
| return fakeReturn.expression; |
| } |
| |
| void parseInitializers(Token token) { |
| Parser parser = new Parser(this); |
| if (!token.isEof) { |
| token = parser.parseInitializers(token); |
| checkEmpty(token.charOffset); |
| } else { |
| handleNoInitializers(); |
| } |
| // We are passing [AsyncMarker.Sync] because the error will be reported |
| // already. |
| finishConstructor(member, AsyncMarker.Sync, null); |
| } |
| |
| Expression parseFieldInitializer(Token token) { |
| Parser parser = new Parser(this); |
| token = parser.parseExpression(parser.syntheticPreviousToken(token)); |
| Expression expression = popForValue(); |
| checkEmpty(token.charOffset); |
| return expression; |
| } |
| |
| Expression parseAnnotation(Token token) { |
| Parser parser = new Parser(this); |
| token = parser.parseMetadata(parser.syntheticPreviousToken(token)); |
| Expression annotation = pop(); |
| checkEmpty(token.charOffset); |
| return annotation; |
| } |
| |
| void finishConstructor( |
| ConstructorBuilder builder, AsyncMarker asyncModifier, Statement body) { |
| /// Quotes below are from [Dart Programming Language Specification, 4th |
| /// Edition]( |
| /// https://ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf). |
| assert(builder == member); |
| Constructor constructor = builder.actualConstructor; |
| List<FormalParameterBuilder> formals = builder.formals; |
| if (formals != null) { |
| for (int i = 0; i < formals.length; i++) { |
| FormalParameterBuilder parameter = formals[i]; |
| typeInferrer?.flowAnalysis?.declare(parameter.variable, true); |
| } |
| } |
| if (_initializers != null) { |
| for (Initializer initializer in _initializers) { |
| typeInferrer?.inferInitializer(this, initializer); |
| } |
| if (!builder.isExternal) { |
| for (Initializer initializer in _initializers) { |
| builder.addInitializer(initializer, this); |
| } |
| } |
| } |
| if (asyncModifier != AsyncMarker.Sync) { |
| constructor.initializers.add(buildInvalidInitializer(buildProblem( |
| fasta.messageConstructorNotSync, body.fileOffset, noLength))); |
| } |
| if (needsImplicitSuperInitializer) { |
| /// >If no superinitializer is provided, an implicit superinitializer |
| /// >of the form super() is added at the end of k’s initializer list, |
| /// >unless the enclosing class is class Object. |
| Constructor superTarget = lookupConstructor(emptyName, isSuper: true); |
| Initializer initializer; |
| Arguments arguments = forest.createArgumentsEmpty(noLocation); |
| if (superTarget == null || |
| checkArgumentsForFunction(superTarget.function, arguments, |
| builder.charOffset, const <TypeParameter>[]) != |
| null) { |
| String superclass = classBuilder.supertypeBuilder.fullNameForErrors; |
| int length = constructor.name.text.length; |
| if (length == 0) { |
| length = (constructor.parent as Class).name.length; |
| } |
| initializer = buildInvalidInitializer( |
| buildProblem( |
| fasta.templateSuperclassHasNoDefaultConstructor |
| .withArguments(superclass), |
| builder.charOffset, |
| length), |
| builder.charOffset); |
| } else { |
| initializer = buildSuperInitializer( |
| true, superTarget, arguments, builder.charOffset); |
| } |
| constructor.initializers.add(initializer); |
| } |
| setParents(constructor.initializers, constructor); |
| libraryBuilder.loader.transformListPostInference(constructor.initializers, |
| transformSetLiterals, transformCollections, libraryBuilder.library); |
| if (body == null) { |
| /// >If a generative constructor c is not a redirecting constructor |
| /// >and no body is provided, then c implicitly has an empty body {}. |
| /// We use an empty statement instead. |
| constructor.function.body = new EmptyStatement(); |
| constructor.function.body.parent = constructor.function; |
| } |
| } |
| |
| @override |
| void handleExpressionStatement(Token token) { |
| debugEvent("ExpressionStatement"); |
| push(forest.createExpressionStatement( |
| offsetForToken(token), popForEffect())); |
| } |
| |
| @override |
| void endArguments(int count, Token beginToken, Token endToken) { |
| debugEvent("Arguments"); |
| List<Object> arguments = count == 0 |
| ? <Object>[] |
| : const FixedNullableList<Object>().pop(stack, count); |
| if (arguments == null) { |
| push(new ParserRecovery(beginToken.charOffset)); |
| return; |
| } |
| int firstNamedArgumentIndex = arguments.length; |
| for (int i = 0; i < arguments.length; i++) { |
| Object node = arguments[i]; |
| if (node is NamedExpression) { |
| firstNamedArgumentIndex = |
| i < firstNamedArgumentIndex ? i : firstNamedArgumentIndex; |
| } else { |
| Expression argument = toValue(node); |
| arguments[i] = argument; |
| if (i > firstNamedArgumentIndex) { |
| arguments[i] = new NamedExpression( |
| "#$i", |
| buildProblem(fasta.messageExpectedNamedArgument, |
| argument.fileOffset, noLength)) |
| ..fileOffset = beginToken.charOffset; |
| } |
| } |
| } |
| if (firstNamedArgumentIndex < arguments.length) { |
| List<Expression> positional = new List<Expression>.from( |
| arguments.getRange(0, firstNamedArgumentIndex)); |
| List<NamedExpression> named = new List<NamedExpression>.from( |
| arguments.getRange(firstNamedArgumentIndex, arguments.length)); |
| push(forest.createArguments(beginToken.offset, positional, named: named)); |
| } 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. |
| push(forest.createArguments( |
| beginToken.offset, new List<Expression>.from(arguments))); |
| } |
| } |
| |
| @override |
| void handleParenthesizedCondition(Token token) { |
| debugEvent("ParenthesizedCondition"); |
| push(popForValue()); |
| } |
| |
| @override |
| void handleParenthesizedExpression(Token token) { |
| 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)); |
| } |
| } |
| |
| @override |
| void handleSend(Token beginToken, Token endToken) { |
| assert(checkState(beginToken, [ |
| ValueKinds.ArgumentsOrNull, |
| ValueKinds.TypeArgumentsOrNull, |
| unionOfKinds([ |
| ValueKinds.Expression, |
| ValueKinds.Generator, |
| ValueKinds.Identifier, |
| ValueKinds.ParserRecovery, |
| ValueKinds.ProblemBuilder |
| ]) |
| ])); |
| debugEvent("Send"); |
| Arguments arguments = pop(); |
| List<UnresolvedType> typeArguments = pop(); |
| 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 != null && |
| typeArguments != null && |
| (receiver is! TypeUseGenerator || |
| (receiver is TypeUseGenerator && |
| receiver.declaration is! TypeAliasBuilder)); |
| if (isInForest) { |
| assert(forest.argumentsTypeArguments(arguments).isEmpty); |
| forest.argumentsSetTypeArguments( |
| arguments, buildDartTypeArguments(typeArguments)); |
| } else { |
| assert(typeArguments == null || |
| (receiver is TypeUseGenerator && |
| receiver.declaration is TypeAliasBuilder)); |
| } |
| if (receiver is Identifier) { |
| Name name = new Name(receiver.name, libraryBuilder.nameOrigin); |
| if (arguments == null) { |
| push(new IncompletePropertyAccessGenerator(this, beginToken, name)); |
| } else { |
| push(new SendAccessGenerator( |
| this, beginToken, name, typeArguments, arguments, |
| isTypeArgumentsInForest: isInForest)); |
| } |
| } else if (receiver is ParserRecovery) { |
| push(new ParserErrorGenerator(this, null, fasta.messageSyntheticToken)); |
| } else if (arguments == null) { |
| push(receiver); |
| } else { |
| push(finishSend(receiver, typeArguments, arguments, beginToken.charOffset, |
| isTypeArgumentsInForest: isInForest)); |
| } |
| assert(checkState(beginToken, [ |
| unionOfKinds([ |
| ValueKinds.Expression, |
| ValueKinds.Generator, |
| ValueKinds.Initializer, |
| ValueKinds.ProblemBuilder |
| ]) |
| ])); |
| } |
| |
| @override |
| finishSend(Object receiver, List<UnresolvedType> typeArguments, |
| Arguments 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) { |
| debugEvent("beginCascade"); |
| Expression expression = popForValue(); |
| if (expression is Cascade) { |
| push(expression); |
| push(_createReadOnlyVariableAccess(expression.variable, token, |
| expression.fileOffset, null, ReadOnlyAccessKind.LetVariable)); |
| } else { |
| bool isNullAware = optional('?..', token); |
| if (isNullAware && !libraryBuilder.isNonNullableByDefault) { |
| reportMissingNonNullableSupport(token); |
| } |
| VariableDeclaration variable = |
| createVariableDeclarationForValue(expression); |
| push(new Cascade(variable, isNullAware: isNullAware) |
| ..fileOffset = expression.fileOffset); |
| push(_createReadOnlyVariableAccess(variable, token, expression.fileOffset, |
| null, ReadOnlyAccessKind.LetVariable)); |
| } |
| } |
| |
| @override |
| void endCascade() { |
| debugEvent("endCascade"); |
| Expression expression = popForEffect(); |
| Cascade cascadeReceiver = pop(); |
| cascadeReceiver.addCascadeExpression(expression); |
| push(cascadeReceiver); |
| } |
| |
| @override |
| void beginCaseExpression(Token caseKeyword) { |
| debugEvent("beginCaseExpression"); |
| super.push(constantContext); |
| constantContext = ConstantContext.inferred; |
| } |
| |
| @override |
| void endCaseExpression(Token colon) { |
| debugEvent("endCaseExpression"); |
| Expression expression = popForValue(); |
| constantContext = pop(); |
| super.push(expression); |
| } |
| |
| @override |
| void beginBinaryExpression(Token token) { |
| bool isAnd = optional("&&", token); |
| if (isAnd || optional("||", token)) { |
| Expression lhs = popForValue(); |
| typePromoter?.enterLogicalExpression(lhs, token.stringValue); |
| // This is matched by the call to [endNode] in |
| // [doLogicalExpression]. |
| if (isAnd) { |
| typeInferrer?.assignedVariables?.beginNode(); |
| } |
| push(lhs); |
| } |
| } |
| |
| @override |
| void endBinaryExpression(Token token) { |
| 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); |
| } |
| } |
| |
| 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, 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, name, right)); |
| } |
| } |
| } |
| |
| /// Handle `a && b` and `a || b`. |
| void doLogicalExpression(Token token) { |
| Expression argument = popForValue(); |
| Expression receiver = pop(); |
| Expression logicalExpression = forest.createLogicalExpression( |
| offsetForToken(token), receiver, token.stringValue, argument); |
| typePromoter?.exitLogicalExpression(argument, logicalExpression); |
| push(logicalExpression); |
| if (optional("&&", token)) { |
| // This is matched by the call to [beginNode] in |
| // [beginBinaryExpression]. |
| typeInferrer?.assignedVariables?.endNode(logicalExpression); |
| } |
| } |
| |
| /// Handle `a ?? b`. |
| void doIfNull(Token token) { |
| Expression b = popForValue(); |
| Expression a = popForValue(); |
| push(new IfNullExpression(a, b)..fileOffset = offsetForToken(token)); |
| } |
| |
| /// Handle `a?.b(...)`. |
| void doIfNotNull(Token token) { |
| Object send = pop(); |
| if (send is IncompleteSendGenerator) { |
| push(send.withReceiver(pop(), token.charOffset, isNullAware: true)); |
| } else { |
| pop(); |
| token = token.next; |
| push(buildProblem(fasta.templateExpectedIdentifier.withArguments(token), |
| offsetForToken(token), lengthForToken(token))); |
| } |
| } |
| |
| void doDotOrCascadeExpression(Token token) { |
| Object send = pop(); |
| if (send is IncompleteSendGenerator) { |
| Object receiver = optional(".", token) ? pop() : popForValue(); |
| push(send.withReceiver(receiver, token.charOffset)); |
| } else { |
| pop(); |
| token = token.next; |
| push(buildProblem(fasta.templateExpectedIdentifier.withArguments(token), |
| offsetForToken(token), lengthForToken(token))); |
| } |
| } |
| |
| bool areArgumentsCompatible(FunctionNode function, Arguments arguments) { |
| // TODO(ahe): Implement this. |
| return true; |
| } |
| |
| @override |
| Expression throwNoSuchMethodError( |
| Expression receiver, String name, Arguments arguments, int charOffset, |
| {Member candidate, |
| bool isSuper: false, |
| bool isGetter: false, |
| bool isSetter: false, |
| bool isStatic: false, |
| LocatedMessage message}) { |
| int length = name.length; |
| int periodIndex = name.lastIndexOf("."); |
| if (periodIndex != -1) { |
| length -= periodIndex + 1; |
| } |
| Name kernelName = new Name(name, libraryBuilder.nameOrigin); |
| List<LocatedMessage> context; |
| if (candidate != null && candidate.location != null) { |
| Uri uri = candidate.location.file; |
| int offset = candidate.fileOffset; |
| Message contextMessage; |
| int length = noLength; |
| if (candidate is Constructor && candidate.isSynthetic) { |
| offset = candidate.enclosingClass.fileOffset; |
| contextMessage = fasta.templateCandidateFoundIsDefaultConstructor |
| .withArguments(candidate.enclosingClass.name); |
| } else { |
| if (candidate is Constructor) { |
| if (candidate.name.text == '') { |
| length = candidate.enclosingClass.name.length; |
| } else { |
| // Assume no spaces around the dot. Not perfect, but probably the |
| // best we can do with the information available. |
| length = candidate.enclosingClass.name.length + 1 + name.length; |
| } |
| } else { |
| length = name.length; |
| } |
| contextMessage = fasta.messageCandidateFound; |
| } |
| context = [contextMessage.withLocation(uri, offset, length)]; |
| } |
| if (message == null) { |
| if (isGetter) { |
| message = warnUnresolvedGet(kernelName, charOffset, |
| isSuper: isSuper, reportWarning: false, context: context) |
| .withLocation(uri, charOffset, length); |
| } else if (isSetter) { |
| message = warnUnresolvedSet(kernelName, charOffset, |
| isSuper: isSuper, reportWarning: false, context: context) |
| .withLocation(uri, charOffset, length); |
| } else { |
| message = warnUnresolvedMethod(kernelName, charOffset, |
| isSuper: isSuper, reportWarning: false, context: context) |
| .withLocation(uri, charOffset, length); |
| } |
| } |
| return buildProblem( |
| message.messageObject, message.charOffset, message.length, |
| context: context); |
| } |
| |
| @override |
| Message warnUnresolvedGet(Name name, int charOffset, |
| {bool isSuper: false, |
| bool reportWarning: true, |
| List<LocatedMessage> context}) { |
| Message message = isSuper |
| ? fasta.templateSuperclassHasNoGetter.withArguments(name.text) |
| : fasta.templateGetterNotFound.withArguments(name.text); |
| if (reportWarning) { |
| addProblemErrorIfConst(message, charOffset, name.text.length, |
| context: context); |
| } |
| return message; |
| } |
| |
| @override |
| Message warnUnresolvedSet(Name name, int charOffset, |
| {bool isSuper: false, |
| bool reportWarning: true, |
| List<LocatedMessage> context}) { |
| Message message = isSuper |
| ? fasta.templateSuperclassHasNoSetter.withArguments(name.text) |
| : fasta.templateSetterNotFound.withArguments(name.text); |
| if (reportWarning) { |
| addProblemErrorIfConst(message, charOffset, name.text.length, |
| context: context); |
| } |
| return message; |
| } |
| |
| @override |
| Message warnUnresolvedMethod(Name name, int charOffset, |
| {bool isSuper: false, |
| bool reportWarning: true, |
| List<LocatedMessage> context}) { |
| String plainName = name.text; |
| int dotIndex = plainName.lastIndexOf("."); |
| if (dotIndex != -1) { |
| plainName = plainName.substring(dotIndex + 1); |
| } |
| // TODO(ahe): This is rather brittle. We would probably be better off with |
| // more precise location information in this case. |
| int length = plainName.length; |
| if (plainName.startsWith("[")) { |
| length = 1; |
| } |
| Message message = isSuper |
| ? fasta.templateSuperclassHasNoMethod.withArguments(name.text) |
| : fasta.templateMethodNotFound.withArguments(name.text); |
| if (reportWarning) { |
| addProblemErrorIfConst(message, charOffset, length, context: context); |
| } |
| return message; |
| } |
| |
| @override |
| void warnTypeArgumentsMismatch(String name, int expected, int charOffset) { |
| addProblemErrorIfConst( |
| fasta.templateTypeArgumentMismatch.withArguments(expected), |
| charOffset, |
| name.length); |
| } |
| |
| @override |
| Member lookupInstanceMember(Name name, |
| {bool isSetter: false, bool isSuper: false}) { |
| return classBuilder.lookupInstanceMember(hierarchy, name, |
| isSetter: isSetter, isSuper: isSuper); |
| } |
| |
| @override |
| Constructor lookupConstructor(Name name, {bool isSuper}) { |
| return classBuilder.lookupConstructor(name, isSuper: isSuper); |
| } |
| |
| @override |
| void handleIdentifier(Token token, IdentifierContext context) { |
| debugEvent("handleIdentifier"); |
| String name = token.lexeme; |
| if (context.isScopeReference) { |
| assert(!inInitializer || |
| this.scope == enclosingScope || |
| this.scope.parent == enclosingScope); |
| // This deals with this kind of initializer: `C(a) : a = a;` |
| Scope scope = inInitializer ? enclosingScope : this.scope; |
| push(scopeLookup(scope, name, token)); |
| return; |
| } else if (context.inDeclaration) { |
| if (context == IdentifierContext.topLevelVariableDeclaration || |
| context == IdentifierContext.fieldDeclaration) { |
| constantContext = member.isConst |
| ? ConstantContext.inferred |
| : !member.isStatic && |
| classBuilder != null && |
| classBuilder.declaresConstConstructor |
| ? ConstantContext.required |
| : ConstantContext.none; |
| } |
| } else if (constantContext != ConstantContext.none && |
| !context.allowedInConstantExpression) { |
| addProblem( |
| fasta.messageNotAConstantExpression, token.charOffset, token.length); |
| } |
| if (token.isSynthetic) { |
| push(new ParserRecovery(offsetForToken(token))); |
| } else { |
| push(new Identifier(token)); |
| } |
| } |
| |
| /// Helper method to create a [VariableGet] of the [variable] using |
| /// [charOffset] as the file offset. |
| @override |
| VariableGet createVariableGet(VariableDeclaration variable, int charOffset, |
| {bool forNullGuardedAccess: false}) { |
| if (!(variable as VariableDeclarationImpl).isLocalFunction) { |
| typeInferrer?.assignedVariables?.read(variable); |
| } |
| Object fact = |
| typePromoter?.getFactForAccess(variable, functionNestingLevel); |
| Object scope = typePromoter?.currentScope; |
| return new VariableGetImpl(variable, fact, scope, |
| forNullGuardedAccess: forNullGuardedAccess) |
| ..fileOffset = charOffset; |
| } |
| |
| /// Helper method to create a [ReadOnlyAccessGenerator] on the [variable] |
| /// using [token] and [charOffset] for offset information and [name] |
| /// for `ExpressionGenerator._plainNameForRead`. |
| ReadOnlyAccessGenerator _createReadOnlyVariableAccess( |
| VariableDeclaration variable, |
| Token token, |
| int charOffset, |
| String name, |
| ReadOnlyAccessKind kind) { |
| return new ReadOnlyAccessGenerator( |
| this, token, createVariableGet(variable, charOffset), name, kind); |
| } |
| |
| /// Look up [name] in [scope] using [token] as location information (both to |
| /// report problems and as the file offset in the generated kernel code). |
| /// [isQualified] should be true if [name] is a qualified access (which |
| /// implies that it shouldn't be turned into a [ThisPropertyAccessGenerator] |
| /// if the name doesn't resolve in the scope). |
| @override |
| scopeLookup(Scope scope, String name, Token token, |
| {bool isQualified: false, PrefixBuilder prefix}) { |
| int charOffset = offsetForToken(token); |
| if (token.isSynthetic) { |
| return new ParserErrorGenerator(this, token, fasta.messageSyntheticToken); |
| } |
| Builder declaration = scope.lookup(name, charOffset, uri); |
| if (declaration == null && |
| prefix == null && |
| (classBuilder?.isPatch ?? false)) { |
| // The scope of a patched method includes the origin class. |
| declaration = classBuilder.origin |
| .findStaticBuilder(name, charOffset, uri, libraryBuilder); |
| } |
| if (declaration != null && |
| declaration.isDeclarationInstanceMember && |
| (inFieldInitializer && !inLateFieldInitializer) && |
| !inInitializer) { |
| // We cannot access a class instance member in an initializer of a |
| // field. |
| // |
| // For instance |
| // |
| // class M { |
| // int foo = bar; |
| // int bar; |
| // } |
| // |
| return new IncompleteErrorGenerator(this, token, |
| fasta.templateThisAccessInFieldInitializer.withArguments(name)); |
| } |
| if (declaration == null || |
| (!isDeclarationInstanceContext && |
| declaration.isDeclarationInstanceMember)) { |
| // We either didn't find a declaration or found an instance member from |
| // a non-instance context. |
| Name n = new Name(name, libraryBuilder.nameOrigin); |
| if (!isQualified && isDeclarationInstanceContext) { |
| assert(declaration == null); |
| if (constantContext != ConstantContext.none || |
| (inFieldInitializer && !inLateFieldInitializer) && !inInitializer) { |
| return new UnresolvedNameGenerator(this, token, n); |
| } |
| if (extensionThis != null) { |
| // If we are in an extension instance member we interpret this as an |
| // implicit access on the 'this' parameter. |
| return PropertyAccessGenerator.make(this, token, |
| createVariableGet(extensionThis, charOffset), n, false); |
| } else { |
| // This is an implicit access on 'this'. |
| return new ThisPropertyAccessGenerator(this, token, n); |
| } |
| } else if (ignoreMainInGetMainClosure && |
| name == "main" && |
| member?.name == "_getMainClosure") { |
| return forest.createNullLiteral(charOffset); |
| } else { |
| return new UnresolvedNameGenerator(this, token, n); |
| } |
| } else if (declaration.isTypeDeclaration) { |
| if (declaration is AccessErrorBuilder) { |
| AccessErrorBuilder accessError = declaration; |
| declaration = accessError.builder; |
| } |
| return new TypeUseGenerator(this, token, declaration, name); |
| } else if (declaration.isLocal) { |
| VariableBuilder variableBuilder = declaration; |
| if (constantContext != ConstantContext.none && |
| !variableBuilder.isConst && |
| !member.isConstructor && |
| !enableConstFunctionsInLibrary) { |
| return new IncompleteErrorGenerator( |
| this, token, fasta.messageNotAConstantExpression); |
| } |
| VariableDeclaration variable = variableBuilder.variable; |
| if (!variableBuilder.isAssignable) { |
| return _createReadOnlyVariableAccess( |
| variable, |
| token, |
| charOffset, |
| name, |
| variableBuilder.isConst |
| ? ReadOnlyAccessKind.ConstVariable |
| : ReadOnlyAccessKind.FinalVariable); |
| } else { |
| return new VariableUseGenerator(this, token, variable); |
| } |
| } else if (declaration.isClassInstanceMember) { |
| if (constantContext != ConstantContext.none && |
| !inInitializer && |
| // TODO(ahe): This is a hack because Fasta sets up the scope |
| // "this.field" parameters according to old semantics. Under the new |
| // semantics, such parameters introduces a new parameter with that |
| // name that should be resolved here. |
| !member.isConstructor) { |
| addProblem( |
| fasta.messageNotAConstantExpression, charOffset, token.length); |
| } |
| Name n = new Name(name, libraryBuilder.nameOrigin); |
| return new ThisPropertyAccessGenerator(this, token, n); |
| } else if (declaration.isExtensionInstanceMember) { |
| ExtensionBuilder extensionBuilder = declarationBuilder; |
| MemberBuilder setterBuilder = |
| _getCorrespondingSetterBuilder(scope, declaration, name, charOffset); |
| // TODO(johnniwinther): Check for constantContext like below? |
| if (declaration.isField) { |
| declaration = null; |
| } |
| if (setterBuilder != null && |
| (setterBuilder.isField || setterBuilder.isStatic)) { |
| setterBuilder = null; |
| } |
| if (declaration == null && setterBuilder == null) { |
| return new UnresolvedNameGenerator( |
| this, token, new Name(name, libraryBuilder.nameOrigin)); |
| } |
| MemberBuilder getterBuilder = |
| declaration is MemberBuilder ? declaration : null; |
| return new ExtensionInstanceAccessGenerator.fromBuilder( |
| this, |
| token, |
| extensionBuilder.extension, |
| name, |
| extensionThis, |
| extensionTypeParameters, |
| getterBuilder, |
| setterBuilder); |
| } else if (declaration.isRegularMethod) { |
| assert(declaration.isStatic || declaration.isTopLevel); |
| MemberBuilder memberBuilder = declaration; |
| return new StaticAccessGenerator( |
| this, token, name, memberBuilder.member, null); |
| } else if (declaration is PrefixBuilder) { |
| assert(prefix == null); |
| return new PrefixUseGenerator(this, token, declaration); |
| } else if (declaration is LoadLibraryBuilder) { |
| return new LoadLibraryGenerator(this, token, declaration); |
| } else if (declaration.hasProblem && declaration is! AccessErrorBuilder) { |
| return declaration; |
| } else { |
| MemberBuilder setterBuilder = |
| _getCorrespondingSetterBuilder(scope, declaration, name, charOffset); |
| MemberBuilder getterBuilder = |
| declaration is MemberBuilder ? declaration : null; |
| assert(getterBuilder != null || setterBuilder != null); |
| StaticAccessGenerator generator = new StaticAccessGenerator.fromBuilder( |
| this, name, token, getterBuilder, setterBuilder); |
| if (constantContext != ConstantContext.none) { |
| Member readTarget = generator.readTarget; |
| if (!(readTarget is Field && readTarget.isConst || |
| // Static tear-offs are also compile time constants. |
| readTarget is Procedure)) { |
| addProblem( |
| fasta.messageNotAConstantExpression, charOffset, token.length); |
| } |
| } |
| return generator; |
| } |
| } |
| |
| /// Returns the setter builder corresponding to [declaration] using the |
| /// [name] and [charOffset] for the lookup into [scope] if necessary. |
| MemberBuilder _getCorrespondingSetterBuilder( |
| Scope scope, Builder declaration, String name, int charOffset) { |
| Builder setter; |
| if (declaration.isSetter) { |
| setter = declaration; |
| } else if (declaration.isGetter) { |
| setter = scope.lookupSetter(name, charOffset, uri); |
| } else if (declaration.isField) { |
| MemberBuilder fieldBuilder = declaration; |
| if (!fieldBuilder.isAssignable) { |
| setter = scope.lookupSetter(name, charOffset, uri); |
| } else { |
| setter = declaration; |
| } |
| } |
| return setter is MemberBuilder ? setter : null; |
| } |
| |
| @override |
| void handleQualified(Token period) { |
| debugEvent("Qualified"); |
| Object node = pop(); |
| Object qualifier = pop(); |
| if (qualifier is ParserRecovery) { |
| push(qualifier); |
| } else if (node is ParserRecovery) { |
| push(node); |
| } else { |
| Identifier identifier = node; |
| push(identifier.withQualifier(qualifier)); |
| } |
| } |
| |
| @override |
| void beginLiteralString(Token token) { |
| debugEvent("beginLiteralString"); |
| push(token); |
| } |
| |
| @override |
| void handleStringPart(Token token) { |
| debugEvent("StringPart"); |
| push(token); |
| } |
| |
| @override |
| void endLiteralString(int interpolationCount, Token endToken) { |
| debugEvent("endLiteralString"); |
| if (interpolationCount == 0) { |
| Token token = pop(); |
| String value = unescapeString(token.lexeme, token, this); |
| push(forest.createStringLiteral(offsetForToken(token), value)); |
| } else { |
| int count = 1 + interpolationCount * 2; |
| List<Object> parts = const FixedNullableList<Object>().pop(stack, count); |
| if (parts == null) { |
| push(new ParserRecovery(endToken.charOffset)); |
| return; |
| } |
| Token first = parts.first; |
| Token last = parts.last; |
| Quote quote = analyzeQuote(first.lexeme); |
| List<Expression> expressions = <Expression>[]; |
| // Contains more than just \' or \". |
| if (first.lexeme.length > 1) { |
| String value = |
| unescapeFirstStringPart(first.lexeme, quote, first, this); |
| if (value.isNotEmpty) { |
| expressions |
| .add(forest.createStringLiteral(offsetForToken(first), value)); |
| } |
| } |
| for (int i = 1; i < parts.length - 1; i++) { |
| Object part = parts[i]; |
| if (part is Token) { |
| if (part.lexeme.length != 0) { |
| String value = unescape(part.lexeme, quote, part, this); |
| expressions |
| .add(forest.createStringLiteral(offsetForToken(part), value)); |
| } |
| } else { |
| expressions.add(toValue(part)); |
| } |
| } |
| // Contains more than just \' or \". |
| if (last.lexeme.length > 1) { |
| String value = unescapeLastStringPart( |
| last.lexeme, quote, last, last.isSynthetic, this); |
| if (value.isNotEmpty) { |
| expressions |
| .add(forest.createStringLiteral(offsetForToken(last), value)); |
| } |
| } |
| push(forest.createStringConcatenation( |
| offsetForToken(endToken), expressions)); |
| } |
| } |
| |
| @override |
| void handleNativeClause(Token nativeToken, bool hasName) { |
| debugEvent("NativeClause"); |
| if (hasName) { |
| pop() as StringLiteral; |
| } |
| } |
| |
| @override |
| void handleScript(Token token) { |
| debugEvent("Script"); |
| } |
| |
| @override |
| void handleStringJuxtaposition(Token startToken, int literalCount) { |
| debugEvent("StringJuxtaposition"); |
| List<Expression> parts = popListForValue(literalCount); |
| List<Expression> expressions; |
| // Flatten string juxtapositions of string interpolation. |
| for (int i = 0; i < parts.length; i++) { |
| Expression part = parts[i]; |
| if (part is StringConcatenation) { |
| if (expressions == null) { |
| expressions = parts.sublist(0, i); |
| } |
| for (Expression expression in part.expressions) { |
| expressions.add(expression); |
| } |
| } else { |
| if (expressions != null) { |
| expressions.add(part); |
| } |
| } |
| } |
| push(forest.createStringConcatenation( |
| offsetForToken(startToken), expressions ?? parts)); |
| } |
| |
| @override |
| void handleLiteralInt(Token token) { |
| debugEvent("LiteralInt"); |
| int value = int.tryParse(token.lexeme); |
| // Postpone parsing of literals resulting in a negative value |
| // (hex literals >= 2^63). These are only allowed when not negated. |
| if (value == null || value < 0) { |
| push(forest.createIntLiteralLarge(offsetForToken(token), token.lexeme)); |
| } else { |
| push(forest.createIntLiteral(offsetForToken(token), value, token.lexeme)); |
| } |
| } |
| |
| @override |
| void handleEmptyFunctionBody(Token semicolon) { |
| debugEvent("ExpressionFunctionBody"); |
| endBlockFunctionBody(0, null, semicolon); |
| } |
| |
| @override |
| void handleExpressionFunctionBody(Token arrowToken, Token endToken) { |
| debugEvent("ExpressionFunctionBody"); |
| endReturnStatement(true, arrowToken.next, endToken); |
| } |
| |
| @override |
| void endReturnStatement( |
| bool hasExpression, Token beginToken, Token endToken) { |
| debugEvent("ReturnStatement"); |
| Expression expression = hasExpression ? popForValue() : null; |
| if (expression != null && inConstructor) { |
| push(buildProblemStatement( |
| fasta.messageConstructorWithReturnType, beginToken.charOffset)); |
| } else { |
| push(forest.createReturnStatement(offsetForToken(beginToken), expression, |
| isArrow: !identical(beginToken.lexeme, "return"))); |
| } |
| } |
| |
| @override |
| void beginThenStatement(Token token) { |
| Expression condition = popForValue(); |
| enterThenForTypePromotion(condition); |
| // This is matched by the call to [deferNode] in |
| // [endThenStatement]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| push(condition); |
| super.beginThenStatement(token); |
| } |
| |
| @override |
| void endThenStatement(Token token) { |
| typePromoter?.enterElse(); |
| super.endThenStatement(token); |
| // This is matched by the call to [beginNode] in |
| // [beginThenStatement] and by the call to [storeInfo] in |
| // [endIfStatement]. |
| push(typeInferrer?.assignedVariables?.deferNode()); |
| } |
| |
| @override |
| void endIfStatement(Token ifToken, Token elseToken) { |
| Statement elsePart = popStatementIfNotNull(elseToken); |
| AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesInfo = |
| pop(); |
| Statement thenPart = popStatement(); |
| Expression condition = pop(); |
| typePromoter?.exitConditional(); |
| Statement node = forest.createIfStatement( |
| offsetForToken(ifToken), condition, thenPart, elsePart); |
| // This is matched by the call to [deferNode] in |
| // [endThenStatement]. |
| typeInferrer?.assignedVariables?.storeInfo(node, assignedVariablesInfo); |
| push(node); |
| } |
| |
| @override |
| void beginVariableInitializer(Token token) { |
| if ((currentLocalVariableModifiers & lateMask) != 0) { |
| // This is matched by the call to [endNode] in [endVariableInitializer]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| } |
| } |
| |
| @override |
| void endVariableInitializer(Token assignmentOperator) { |
| debugEvent("VariableInitializer"); |
| assert(assignmentOperator.stringValue == "="); |
| AssignedVariablesNodeInfo<VariableDeclaration> assignedVariablesInfo; |
| bool isLate = (currentLocalVariableModifiers & lateMask) != 0; |
| Expression initializer = popForValue(); |
| if (isLate) { |
| assignedVariablesInfo = typeInferrer?.assignedVariables |
| ?.deferNode(isClosureOrLateVariableInitializer: true); |
| } |
| pushNewLocalVariable(initializer, equalsToken: assignmentOperator); |
| if (isLate) { |
| VariableDeclaration node = peek(); |
| // This is matched by the call to [beginNode] in |
| // [beginVariableInitializer]. |
| typeInferrer?.assignedVariables?.storeInfo(node, assignedVariablesInfo); |
| } |
| } |
| |
| @override |
| void handleNoVariableInitializer(Token token) { |
| debugEvent("NoVariableInitializer"); |
| bool isConst = (currentLocalVariableModifiers & constMask) != 0; |
| bool isFinal = (currentLocalVariableModifiers & finalMask) != 0; |
| bool isLate = (currentLocalVariableModifiers & lateMask) != 0; |
| Expression initializer; |
| if (!optional("in", token.next)) { |
| // A for-in loop-variable can't have an initializer. So let's remain |
| // silent if the next token is `in`. Since a for-in loop can only have |
| // one variable it must be followed by `in`. |
| if (!token.isSynthetic) { |
| // If [token] is synthetic it is created from error recovery. |
| if (isConst) { |
| initializer = buildProblem( |
| fasta.templateConstFieldWithoutInitializer |
| .withArguments(token.lexeme), |
| token.charOffset, |
| token.length); |
| } else if (!libraryBuilder.isNonNullableByDefault && |
| isFinal && |
| !isLate) { |
| initializer = buildProblem( |
| fasta.templateFinalFieldWithoutInitializer |
| .withArguments(token.lexeme), |
| token.charOffset, |
| token.length); |
| } |
| } |
| } |
| pushNewLocalVariable(initializer); |
| } |
| |
| void pushNewLocalVariable(Expression initializer, {Token equalsToken}) { |
| Object node = pop(); |
| if (node is ParserRecovery) { |
| push(node); |
| return; |
| } |
| Identifier identifier = node; |
| assert(currentLocalVariableModifiers != -1); |
| bool isConst = (currentLocalVariableModifiers & constMask) != 0; |
| bool isFinal = (currentLocalVariableModifiers & finalMask) != 0; |
| bool isLate = (currentLocalVariableModifiers & lateMask) != 0; |
| bool isRequired = (currentLocalVariableModifiers & requiredMask) != 0; |
| assert(isConst == (constantContext == ConstantContext.inferred)); |
| VariableDeclaration variable = new VariableDeclarationImpl( |
| identifier.name, functionNestingLevel, |
| forSyntheticToken: identifier.token.isSynthetic, |
| initializer: initializer, |
| type: buildDartType(currentLocalVariableType), |
| isFinal: isFinal, |
| isConst: isConst, |
| isLate: isLate, |
| isRequired: isRequired, |
| hasDeclaredInitializer: initializer != null, |
| isStaticLate: libraryBuilder.isNonNullableByDefault && |
| isFinal && |
| initializer == null) |
| ..fileOffset = identifier.charOffset |
| ..fileEqualsOffset = offsetForToken(equalsToken); |
| typeInferrer?.assignedVariables?.declare(variable); |
| libraryBuilder.checkBoundsInVariableDeclaration( |
| variable, typeEnvironment, uri); |
| push(variable); |
| } |
| |
| @override |
| void beginFieldInitializer(Token token) { |
| inFieldInitializer = true; |
| if (member is FieldBuilder) { |
| FieldBuilder fieldBuilder = member; |
| inLateFieldInitializer = fieldBuilder.isLate; |
| if (fieldBuilder.isAbstract) { |
| addProblem( |
| fasta.messageAbstractFieldInitializer, token.charOffset, noLength); |
| } else if (fieldBuilder.isExternal) { |
| addProblem( |
| fasta.messageExternalFieldInitializer, token.charOffset, noLength); |
| } |
| } else { |
| inLateFieldInitializer = false; |
| } |
| } |
| |
| @override |
| void endFieldInitializer(Token assignmentOperator, Token token) { |
| debugEvent("FieldInitializer"); |
| inFieldInitializer = false; |
| inLateFieldInitializer = false; |
| assert(assignmentOperator.stringValue == "="); |
| push(popForValue()); |
| } |
| |
| @override |
| void handleNoFieldInitializer(Token token) { |
| debugEvent("NoFieldInitializer"); |
| if (constantContext == ConstantContext.inferred) { |
| // Creating a null value to prevent the Dart VM from crashing. |
| push(forest.createNullLiteral(offsetForToken(token))); |
| } else { |
| push(NullValue.FieldInitializer); |
| } |
| } |
| |
| @override |
| void endInitializedIdentifier(Token nameToken) { |
| // TODO(ahe): Use [InitializedIdentifier] here? |
| debugEvent("InitializedIdentifier"); |
| Object node = pop(); |
| if (node is ParserRecovery) { |
| push(node); |
| return; |
| } |
| VariableDeclaration variable = node; |
| variable.fileOffset = nameToken.charOffset; |
| push(variable); |
| declareVariable(variable, scope); |
| } |
| |
| @override |
| void beginVariablesDeclaration( |
| Token token, Token lateToken, Token varFinalOrConst) { |
| debugEvent("beginVariablesDeclaration"); |
| if (!libraryBuilder.isNonNullableByDefault) { |
| reportNonNullableModifierError(lateToken); |
| } |
| UnresolvedType type = pop(); |
| int modifiers = (lateToken != null ? lateMask : 0) | |
| Modifier.validateVarFinalOrConst(varFinalOrConst?.lexeme); |
| _enterLocalState(inLateLocalInitializer: lateToken != null); |
| super.push(currentLocalVariableModifiers); |
| super.push(currentLocalVariableType ?? NullValue.Type); |
| currentLocalVariableType = type; |
| currentLocalVariableModifiers = modifiers; |
| super.push(constantContext); |
| constantContext = ((modifiers & constMask) != 0) |
| ? ConstantContext.inferred |
| : ConstantContext.none; |
| } |
| |
| @override |
| void endVariablesDeclaration(int count, Token endToken) { |
| debugEvent("VariablesDeclaration"); |
| if (count == 1) { |
| Object node = pop(); |
| constantContext = pop(); |
| currentLocalVariableType = pop(); |
| currentLocalVariableModifiers = pop(); |
| List<Expression> annotations = pop(); |
| if (node is ParserRecovery) { |
| push(node); |
| return; |
| } |
| VariableDeclaration variable = node; |
| if (annotations != null) { |
| for (int i = 0; i < annotations.length; i++) { |
| variable.addAnnotation(annotations[i]); |
| } |
| (variablesWithMetadata ??= <VariableDeclaration>[]).add(variable); |
| } |
| push(variable); |
| } else { |
| List<VariableDeclaration> variables = |
| const FixedNullableList<VariableDeclaration>().pop(stack, count); |
| constantContext = pop(); |
| currentLocalVariableType = pop(); |
| currentLocalVariableModifiers = pop(); |
| List<Expression> annotations = pop(); |
| if (variables == null) { |
| push(new ParserRecovery(offsetForToken(endToken))); |
| return; |
| } |
| if (annotations != null) { |
| VariableDeclaration first = variables.first; |
| for (int i = 0; i < annotations.length; i++) { |
| first.addAnnotation(annotations[i]); |
| } |
| (multiVariablesWithMetadata ??= <List<VariableDeclaration>>[]) |
| .add(variables); |
| } |
| push(forest.variablesDeclaration(variables, uri)); |
| } |
| _exitLocalState(); |
| } |
| |
| /// Stack containing assigned variables info for try statements. |
| /// |
| /// These are created in [beginTryStatement] and ended in either [beginBlock] |
| /// when a finally block starts or in [endTryStatement] when the try statement |
| /// ends. Since these need to be associated with the try statement created in |
| /// in [endTryStatement] we store them the stack until the try statement is |
| /// created. |
| Link<AssignedVariablesNodeInfo<VariableDeclaration>> tryStatementInfoStack = |
| const Link<AssignedVariablesNodeInfo<VariableDeclaration>>(); |
| |
| @override |
| void beginBlock(Token token, BlockKind blockKind) { |
| if (blockKind == BlockKind.tryStatement) { |
| // This is matched by the call to [endNode] in [endBlock]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| } else if (blockKind == BlockKind.finallyClause) { |
| // This is matched by the call to [beginNode] in [beginTryStatement]. |
| tryStatementInfoStack = tryStatementInfoStack |
| .prepend(typeInferrer?.assignedVariables?.deferNode()); |
| } |
| super.beginBlock(token, blockKind); |
| } |
| |
| @override |
| void endBlock( |
| int count, Token openBrace, Token closeBrace, BlockKind blockKind) { |
| debugEvent("Block"); |
| Statement block = popBlock(count, openBrace, closeBrace); |
| exitLocalScope(); |
| push(block); |
| if (blockKind == BlockKind.tryStatement) { |
| // This is matched by the call to [beginNode] in [beginBlock]. |
| typeInferrer?.assignedVariables?.endNode(block); |
| } |
| } |
| |
| void handleInvalidTopLevelBlock(Token token) { |
| // TODO(danrubel): Consider improved recovery by adding this block |
| // as part of a synthetic top level function. |
| pop(); // block |
| } |
| |
| @override |
| void handleAssignmentExpression(Token token) { |
| assert(checkState(token, [ |
| unionOfKinds(<ValueKind>[ |
| ValueKinds.Expression, |
| ValueKinds.Generator, |
| // TODO(johnniwinther): Avoid problem builders here. |
| ValueKinds.ProblemBuilder |
| ]), |
| unionOfKinds(<ValueKind>[ |
| ValueKinds.Expression, ValueKinds.Generator, |
| // TODO(johnniwinther): Avoid problem builders here. |
| ValueKinds.ProblemBuilder |
| ]) |
| ])); |
| debugEvent("AssignmentExpression"); |
| Expression value = popForValue(); |
| Object generator = pop(); |
| if (generator is! Generator) { |
| push(buildProblem(fasta.messageNotAnLvalue, offsetForToken(token), |
| lengthForToken(token))); |
| } else { |
| push(new DelayedAssignment( |
| this, token, generator, value, token.stringValue)); |
| } |
| } |
| |
| @override |
| void enterLoop(int charOffset) { |
| if (peek() is LabelTarget) { |
| LabelTarget target = peek(); |
| enterBreakTarget(charOffset, target.breakTarget); |
| enterContinueTarget(charOffset, target.continueTarget); |
| } else { |
| enterBreakTarget(charOffset); |
| enterContinueTarget(charOffset); |
| } |
| } |
| |
| void exitLoopOrSwitch(Statement statement) { |
| if (problemInLoopOrSwitch != null) { |
| push(problemInLoopOrSwitch); |
| problemInLoopOrSwitch = null; |
| } else { |
| push(statement); |
| } |
| } |
| |
| List<VariableDeclaration> _buildForLoopVariableDeclarations( |
| variableOrExpression) { |
| // TODO(ahe): This can be simplified now that we have the events |
| // `handleForInitializer...` events. |
| if (variableOrExpression is Generator) { |
| variableOrExpression = variableOrExpression.buildForEffect(); |
| } |
| if (variableOrExpression is VariableDeclaration) { |
| // Late for loop variables are not supported. An error has already been |
| // reported by the parser. |
| variableOrExpression.isLate = false; |
| return <VariableDeclaration>[variableOrExpression]; |
| } else if (variableOrExpression is Expression) { |
| VariableDeclaration variable = |
| new VariableDeclarationImpl.forEffect(variableOrExpression); |
| return <VariableDeclaration>[variable]; |
| } else if (variableOrExpression is ExpressionStatement) { |
| VariableDeclaration variable = new VariableDeclarationImpl.forEffect( |
| variableOrExpression.expression); |
| return <VariableDeclaration>[variable]; |
| } else if (forest.isVariablesDeclaration(variableOrExpression)) { |
| return forest |
| .variablesDeclarationExtractDeclarations(variableOrExpression); |
| } else if (variableOrExpression is List<Object>) { |
| List<VariableDeclaration> variables = <VariableDeclaration>[]; |
| for (Object v in variableOrExpression) { |
| variables.addAll(_buildForLoopVariableDeclarations(v)); |
| } |
| return variables; |
| } else if (variableOrExpression == null) { |
| return <VariableDeclaration>[]; |
| } |
| return null; |
| } |
| |
| @override |
| void handleForInitializerEmptyStatement(Token token) { |
| debugEvent("ForInitializerEmptyStatement"); |
| push(NullValue.Expression); |
| // This is matched by the call to [deferNode] in [endForStatement] or |
| // [endForControlFlow]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| } |
| |
| @override |
| void handleForInitializerExpressionStatement(Token token, bool forIn) { |
| debugEvent("ForInitializerExpressionStatement"); |
| if (!forIn) { |
| // This is matched by the call to [deferNode] in [endForStatement] or |
| // [endForControlFlow]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| } |
| } |
| |
| @override |
| void handleForInitializerLocalVariableDeclaration(Token token, bool forIn) { |
| debugEvent("ForInitializerLocalVariableDeclaration"); |
| if (forIn) { |
| // If the declaration is of the form `for (final x in ...)`, then we may |
| // have erroneously set the `isStaticLate` flag, so un-set it. |
| Object declaration = peek(); |
| if (declaration is VariableDeclarationImpl) { |
| declaration.isStaticLate = false; |
| } |
| } else { |
| // This is matched by the call to [deferNode] in [endForStatement] or |
| // [endForControlFlow]. |
| typeInferrer?.assignedVariables?.beginNode(); |
| } |
| } |
| |
| @override |
| void handleForLoopParts(Token forKeyword, Token leftParen, |
| Token leftSeparator, int updateExpressionCount) { |
| push(forKeyword); |
| push(leftParen); |
| push(leftSeparator); |
| push(updateExpressionCount); |
| } |
| |
| @override |
| void endForControlFlow(Token token) { |
| debugEvent("ForControlFlow"); |
| Object entry = pop(); |
| int updateExpressionCount = pop(); |
| pop(); // left separator |
| pop(); // left parenthesis |
| Token forToken = pop(); |
| List<Expression> updates = popListForEffect(updateExpressionCount); |
| Statement conditionStatement = popStatement(); // condition |
| |
| if (constantContext != ConstantContext.none) { |
| pop(); // Pop variable or expression. |
| exitLocalScope(); |
| typeInferrer?.assignedVariables?.discardNode(); |
| |
| handleRecoverableError( |
| fasta.templateCantUseControlFlowOrSpreadAsConstant |
| .withArguments(forToken), |
| forToken, |
| forToken); |
|