| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library fasta.procedure_builder; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| |
| import '../api_prototype/lowering_predicates.dart'; |
| import '../base/identifiers.dart'; |
| import '../base/local_scope.dart'; |
| import '../base/messages.dart' |
| show |
| messagePatchDeclarationMismatch, |
| messagePatchDeclarationOrigin, |
| messagePatchNonExternal, |
| noLength, |
| templateRequiredNamedParameterHasDefaultValueError; |
| import '../base/modifiers.dart'; |
| import '../base/scope.dart'; |
| import '../builder/builder.dart'; |
| import '../builder/constructor_builder.dart'; |
| import '../builder/declaration_builders.dart'; |
| import '../builder/formal_parameter_builder.dart'; |
| import '../builder/function_builder.dart'; |
| import '../builder/member_builder.dart'; |
| import '../builder/metadata_builder.dart'; |
| import '../builder/omitted_type_builder.dart'; |
| import '../builder/type_builder.dart'; |
| import '../kernel/internal_ast.dart' show VariableDeclarationImpl; |
| import '../kernel/kernel_helper.dart'; |
| import '../type_inference/type_inference_engine.dart' |
| show IncludesTypeParametersNonCovariantly; |
| import 'source_builder_mixins.dart'; |
| import 'source_extension_type_declaration_builder.dart'; |
| import 'source_loader.dart' show SourceLoader; |
| import 'source_member_builder.dart'; |
| |
| abstract class SourceFunctionBuilder |
| implements FunctionBuilder, SourceMemberBuilder { |
| List<MetadataBuilder>? get metadata; |
| |
| TypeBuilder get returnType; |
| |
| List<NominalVariableBuilder>? get typeVariables; |
| |
| List<FormalParameterBuilder>? get formals; |
| |
| @override |
| ProcedureKind? get kind; |
| |
| @override |
| bool get isAbstract; |
| |
| @override |
| bool get isConstructor; |
| |
| @override |
| bool get isRegularMethod; |
| |
| @override |
| bool get isGetter; |
| |
| @override |
| bool get isSetter; |
| |
| @override |
| bool get isOperator; |
| |
| @override |
| bool get isFactory; |
| |
| /// This is the formal parameter scope as specified in the Dart Programming |
| /// Language Specification, 4th ed, section 9.2. |
| LocalScope computeFormalParameterScope(LookupScope parent); |
| |
| LocalScope computeFormalParameterInitializerScope(LocalScope parent); |
| |
| /// This scope doesn't correspond to any scope specified in the Dart |
| /// Programming Language Specification, 4th ed. It's an unspecified extension |
| /// to support generic methods. |
| LookupScope computeTypeParameterScope(LookupScope parent); |
| |
| FormalParameterBuilder? getFormal(Identifier identifier); |
| |
| Statement? get body; |
| |
| void set body(Statement? newBody); |
| |
| bool get isNative; |
| |
| /// Returns the [index]th parameter of this function. |
| /// |
| /// The index is the syntactical index, including both positional and named |
| /// parameter in the order they are declared, and excluding the synthesized |
| /// this parameter on extension instance members. |
| VariableDeclaration getFormalParameter(int index); |
| |
| /// If this is an extension instance method or constructor with lowering |
| /// enabled, the tear off parameter corresponding to the [index]th parameter |
| /// on the instance method or constructor is returned. |
| /// |
| /// This is used to update the default value for the closure parameter when |
| /// it has been computed for the original parameter. |
| VariableDeclaration? getTearOffParameter(int index); |
| |
| /// Returns the parameter for 'this' synthetically added to extension |
| /// instance members. |
| VariableDeclaration? get thisVariable; |
| |
| /// Returns a list of synthetic type parameters added to extension instance |
| /// members. |
| List<TypeParameter>? get thisTypeParameters; |
| |
| void becomeNative(SourceLoader loader); |
| |
| bool checkAugmentation(SourceFunctionBuilder augmentation); |
| |
| void reportAugmentationMismatch(Builder augmentation); |
| } |
| |
| /// Common base class for constructor and procedure builders. |
| abstract class SourceFunctionBuilderImpl extends SourceMemberBuilderImpl |
| implements SourceFunctionBuilder, InferredTypeListener { |
| @override |
| final List<MetadataBuilder>? metadata; |
| |
| @override |
| final Modifiers modifiers; |
| |
| @override |
| final String name; |
| |
| @override |
| final List<NominalVariableBuilder>? typeVariables; |
| |
| @override |
| final List<FormalParameterBuilder>? formals; |
| |
| /// If this procedure is an extension instance member or extension type |
| /// instance member, [_thisVariable] holds the synthetically added `this` |
| /// parameter. |
| VariableDeclaration? _thisVariable; |
| |
| /// If this procedure is an extension instance member or extension type |
| /// instance member, [_thisTypeParameters] holds the type parameters copied |
| /// from the extension/extension type declaration. |
| List<TypeParameter>? _thisTypeParameters; |
| |
| SourceFunctionBuilderImpl( |
| this.metadata, |
| this.modifiers, |
| this.name, |
| this.typeVariables, |
| this.formals, |
| Builder parent, |
| Uri fileUri, |
| int charOffset, |
| this.nativeMethodName) |
| : super(parent, fileUri, charOffset) { |
| returnType.registerInferredTypeListener(this); |
| if (formals != null) { |
| for (int i = 0; i < formals!.length; i++) { |
| formals![i].parent = this; |
| } |
| } |
| } |
| |
| AsyncMarker get asyncModifier; |
| |
| @override |
| bool get isConstructor => false; |
| |
| @override |
| bool get isAbstract => modifiers.isAbstract; |
| |
| @override |
| bool get isRegularMethod => identical(ProcedureKind.Method, kind); |
| |
| @override |
| bool get isGetter => identical(ProcedureKind.Getter, kind); |
| |
| @override |
| bool get isSetter => identical(ProcedureKind.Setter, kind); |
| |
| @override |
| bool get isOperator => identical(ProcedureKind.Operator, kind); |
| |
| @override |
| bool get isFactory => identical(ProcedureKind.Factory, kind); |
| |
| @override |
| bool get isExternal => modifiers.isExternal; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| bool get isAssignable => false; |
| |
| /// Returns `true` if this member is augmented, either by being the origin |
| /// of a augmented member or by not being the last among augmentations. |
| bool get isAugmented; |
| |
| @override |
| LocalScope computeFormalParameterScope(LookupScope parent) { |
| if (formals == null) return new FormalParameterScope(parent: parent); |
| Map<String, Builder> local = <String, Builder>{}; |
| for (FormalParameterBuilder formal in formals!) { |
| if (formal.isWildcard) { |
| continue; |
| } |
| if (!isConstructor || |
| !formal.isInitializingFormal && !formal.isSuperInitializingFormal) { |
| local[formal.name] = formal; |
| } |
| } |
| return new FormalParameterScope(local: local, parent: parent); |
| } |
| |
| @override |
| LocalScope computeFormalParameterInitializerScope(LocalScope parent) { |
| // From |
| // [dartLangSpec.tex](../../../../../../docs/language/dartLangSpec.tex) at |
| // revision 94b23d3b125e9d246e07a2b43b61740759a0dace: |
| // |
| // When the formal parameter list of a non-redirecting generative |
| // constructor contains any initializing formals, a new scope is |
| // introduced, the _formal parameter initializer scope_, which is the |
| // current scope of the initializer list of the constructor, and which is |
| // enclosed in the scope where the constructor is declared. Each |
| // initializing formal in the formal parameter list introduces a final |
| // local variable into the formal parameter initializer scope, but not into |
| // the formal parameter scope; every other formal parameter introduces a |
| // local variable into both the formal parameter scope and the formal |
| // parameter initializer scope. |
| |
| if (formals == null) return parent; |
| Map<String, Builder> local = <String, Builder>{}; |
| for (FormalParameterBuilder formal in formals!) { |
| // Wildcard initializing formal parameters do not introduce a local |
| // variable in the initializer list. |
| if (formal.isWildcard) continue; |
| |
| local[formal.name] = formal.forFormalParameterInitializerScope(); |
| } |
| return parent.createNestedFixedScope( |
| debugName: "formal parameter initializer", |
| kind: ScopeKind.initializers, |
| local: local); |
| } |
| |
| @override |
| LookupScope computeTypeParameterScope(LookupScope parent) { |
| if (typeVariables == null) return parent; |
| Map<String, Builder> local = <String, Builder>{}; |
| for (NominalVariableBuilder variable in typeVariables!) { |
| if (variable.isWildcard) continue; |
| local[variable.name] = variable; |
| } |
| return new TypeParameterScope(parent, local); |
| } |
| |
| @override |
| FormalParameterBuilder? getFormal(Identifier identifier) { |
| if (formals != null) { |
| for (FormalParameterBuilder formal in formals!) { |
| if (formal.isWildcard && |
| identifier.name == '_' && |
| formal.charOffset == identifier.nameOffset) { |
| return formal; |
| } |
| if (formal.name == identifier.name && |
| formal.charOffset == identifier.nameOffset) { |
| return formal; |
| } |
| } |
| // Coverage-ignore(suite): Not run. |
| // If we have any formals we should find the one we're looking for. |
| assert(false, "$identifier not found in $formals"); |
| } |
| return null; |
| } |
| |
| final String? nativeMethodName; |
| |
| Statement? bodyInternal; |
| |
| @override |
| void set body(Statement? newBody) { |
| // if (newBody != null) { |
| // if (isAbstract) { |
| // // TODO(danrubel): Is this check needed? |
| // return internalProblem(messageInternalProblemBodyOnAbstractMethod, |
| // newBody.fileOffset, fileUri); |
| // } |
| // } |
| bodyInternal = newBody; |
| // A forwarding semi-stub is a method that is abstract in the source code, |
| // but which needs to have a forwarding stub body in order to ensure that |
| // covariance checks occur. We don't want to replace the forwarding stub |
| // body with null. |
| TreeNode? parent = function.parent; |
| if (!(newBody == null && |
| // Coverage-ignore(suite): Not run. |
| parent is Procedure && |
| // Coverage-ignore(suite): Not run. |
| parent.isForwardingSemiStub)) { |
| function.body = newBody; |
| newBody?.parent = function; |
| } |
| } |
| |
| @override |
| bool get isNative => nativeMethodName != null; |
| |
| void buildFunction() { |
| function.asyncMarker = asyncModifier; |
| function.body = body; |
| body?.parent = function; |
| IncludesTypeParametersNonCovariantly? needsCheckVisitor; |
| if (!isConstructor && !isFactory && parent is ClassBuilder) { |
| Class enclosingClass = classBuilder!.cls; |
| if (enclosingClass.typeParameters.isNotEmpty) { |
| needsCheckVisitor = new IncludesTypeParametersNonCovariantly( |
| enclosingClass.typeParameters, |
| // We are checking the parameter types which are in a |
| // contravariant position. |
| initialVariance: Variance.contravariant); |
| } |
| } |
| if (typeVariables != null) { |
| for (NominalVariableBuilder t in typeVariables!) { |
| TypeParameter parameter = t.parameter; |
| function.typeParameters.add(parameter); |
| if (needsCheckVisitor != null) { |
| if (parameter.bound.accept(needsCheckVisitor)) { |
| parameter.isCovariantByClass = true; |
| } |
| } |
| } |
| setParents(function.typeParameters, function); |
| } |
| if (formals != null) { |
| for (FormalParameterBuilder formal in formals!) { |
| VariableDeclaration parameter = formal.build(libraryBuilder); |
| if (needsCheckVisitor != null) { |
| if (parameter.type.accept(needsCheckVisitor)) { |
| parameter.isCovariantByClass = true; |
| } |
| } |
| if (formal.isNamed) { |
| function.namedParameters.add(parameter); |
| } else { |
| function.positionalParameters.add(parameter); |
| } |
| parameter.parent = function; |
| if (formal.isRequiredPositional) { |
| function.requiredParameterCount++; |
| } |
| |
| // Required named parameters can't have default values. |
| if (formal.isRequiredNamed && formal.initializerToken != null) { |
| libraryBuilder.addProblem( |
| templateRequiredNamedParameterHasDefaultValueError |
| .withArguments(formal.name), |
| formal.charOffset, |
| formal.name.length, |
| formal.fileUri); |
| } |
| } |
| } |
| if (!(isExtensionInstanceMember || isExtensionTypeInstanceMember) && |
| isSetter && |
| (formals?.length != 1 || formals![0].isOptionalPositional)) { |
| // Replace illegal parameters by single dummy parameter. |
| // Do this after building the parameters, since the diet listener |
| // assumes that parameters are built, even if illegal in number. |
| VariableDeclaration parameter = new VariableDeclarationImpl("#synthetic"); |
| function.positionalParameters.clear(); |
| function.positionalParameters.add(parameter); |
| parameter.parent = function; |
| function.namedParameters.clear(); |
| function.requiredParameterCount = 1; |
| } |
| if (returnType is! InferableTypeBuilder) { |
| function.returnType = |
| returnType.build(libraryBuilder, TypeUse.returnType); |
| } |
| if (isExtensionInstanceMember || isExtensionTypeInstanceMember) { |
| SourceDeclarationBuilderMixin declarationBuilder = |
| parent as SourceDeclarationBuilderMixin; |
| if (declarationBuilder.typeParameters != null) { |
| int count = declarationBuilder.typeParameters!.length; |
| _thisTypeParameters = new List<TypeParameter>.generate( |
| count, (int index) => function.typeParameters[index], |
| growable: false); |
| } |
| if (isExtensionTypeInstanceMember && isConstructor) { |
| SourceExtensionTypeDeclarationBuilder extensionTypeDeclarationBuilder = |
| parent as SourceExtensionTypeDeclarationBuilder; |
| List<DartType> typeArguments; |
| if (_thisTypeParameters != null) { |
| typeArguments = new List<DartType>.generate( |
| _thisTypeParameters!.length, |
| (int index) => new TypeParameterType( |
| _thisTypeParameters![index], |
| TypeParameterType.computeNullabilityFromBound( |
| _thisTypeParameters![index]))); |
| } else { |
| typeArguments = []; |
| } |
| _thisVariable = new VariableDeclarationImpl(syntheticThisName, |
| isFinal: true, |
| type: new ExtensionType( |
| extensionTypeDeclarationBuilder.extensionTypeDeclaration, |
| Nullability.nonNullable, |
| typeArguments)) |
| ..fileOffset = charOffset |
| ..isLowered = true; |
| } else { |
| _thisVariable = function.positionalParameters.first; |
| } |
| } |
| } |
| |
| @override |
| VariableDeclaration getFormalParameter(int index) { |
| if (this is! ConstructorBuilder && |
| (isExtensionInstanceMember || isExtensionTypeInstanceMember)) { |
| return formals![index + 1].variable!; |
| } else { |
| return formals![index].variable!; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| VariableDeclaration? getTearOffParameter(int index) => null; |
| |
| @override |
| VariableDeclaration? get thisVariable { |
| assert( |
| _thisVariable != null || |
| !(isExtensionInstanceMember || isExtensionTypeInstanceMember), |
| "ProcedureBuilder.thisVariable has not been set."); |
| return _thisVariable; |
| } |
| |
| @override |
| List<TypeParameter>? get thisTypeParameters { |
| // Use [_thisVariable] as marker for whether this type parameters have |
| // been computed. |
| assert( |
| _thisVariable != null || |
| !(isExtensionInstanceMember || isExtensionTypeInstanceMember), |
| "ProcedureBuilder.thisTypeParameters has not been set."); |
| return _thisTypeParameters; |
| } |
| |
| @override |
| void onInferredType(DartType type) { |
| function.returnType = type; |
| } |
| |
| bool hasBuiltOutlineExpressions = false; |
| |
| @override |
| void buildOutlineExpressions(ClassHierarchy classHierarchy, |
| List<DelayedDefaultValueCloner> delayedDefaultValueCloners) { |
| if (!hasBuiltOutlineExpressions) { |
| DeclarationBuilder? classOrExtensionBuilder = |
| isClassMember || isExtensionMember || isExtensionTypeMember |
| ? parent as DeclarationBuilder |
| : null; |
| LookupScope parentScope = |
| classOrExtensionBuilder?.scope ?? libraryBuilder.scope; |
| for (Annotatable annotatable in annotatables) { |
| MetadataBuilder.buildAnnotations( |
| annotatable, |
| metadata, |
| createBodyBuilderContext( |
| inOutlineBuildingPhase: true, |
| inMetadata: true, |
| inConstFields: false), |
| libraryBuilder, |
| fileUri, |
| parentScope, |
| createFileUriExpression: isAugmented); |
| } |
| if (typeVariables != null) { |
| for (int i = 0; i < typeVariables!.length; i++) { |
| typeVariables![i].buildOutlineExpressions( |
| libraryBuilder, |
| createBodyBuilderContext( |
| inOutlineBuildingPhase: true, |
| inMetadata: true, |
| inConstFields: false), |
| classHierarchy, |
| computeTypeParameterScope(parentScope)); |
| } |
| } |
| |
| if (formals != null) { |
| // For const constructors we need to include default parameter values |
| // into the outline. For all other formals we need to call |
| // buildOutlineExpressions to clear initializerToken to prevent |
| // consuming too much memory. |
| for (FormalParameterBuilder formal in formals!) { |
| formal.buildOutlineExpressions(libraryBuilder); |
| } |
| } |
| hasBuiltOutlineExpressions = true; |
| } |
| } |
| |
| @override |
| void becomeNative(SourceLoader loader) { |
| MemberBuilder constructor = loader.getNativeAnnotation(); |
| Arguments arguments = |
| new Arguments(<Expression>[new StringLiteral(nativeMethodName!)]); |
| Expression annotation; |
| if (constructor.isConstructor) { |
| annotation = new ConstructorInvocation( |
| constructor.member as Constructor, arguments) |
| ..isConst = true; |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| annotation = |
| new StaticInvocation(constructor.member as Procedure, arguments) |
| ..isConst = true; |
| } |
| member.addAnnotation(annotation); |
| } |
| |
| @override |
| bool checkAugmentation(SourceFunctionBuilder augmentation) { |
| if (!isExternal && !augmentation.libraryBuilder.isAugmentationLibrary) { |
| // Coverage-ignore-block(suite): Not run. |
| augmentation.libraryBuilder.addProblem(messagePatchNonExternal, |
| augmentation.charOffset, noLength, augmentation.fileUri!, context: [ |
| messagePatchDeclarationOrigin.withLocation( |
| fileUri, charOffset, noLength) |
| ]); |
| return false; |
| } |
| return true; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void reportAugmentationMismatch(Builder augmentation) { |
| libraryBuilder.addProblem(messagePatchDeclarationMismatch, |
| augmentation.charOffset, noLength, augmentation.fileUri!, context: [ |
| messagePatchDeclarationOrigin.withLocation(fileUri, charOffset, noLength) |
| ]); |
| } |
| } |