| // Copyright (c) 2024, 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. |
| |
| part of '../../ast.dart'; |
| |
| // ------------------------------------------------------------------------ |
| // FUNCTIONS |
| // ------------------------------------------------------------------------ |
| |
| /// A function declares parameters and has a body. |
| /// |
| /// This may occur in a procedure, constructor, function expression, or local |
| /// function declaration. |
| class FunctionNode extends TreeNode { |
| /// End offset in the source file it comes from. Valid values are from 0 and |
| /// up, or -1 ([TreeNode.noOffset]) if the file end offset is not available |
| /// (this is the default if none is specifically set). |
| int fileEndOffset = TreeNode.noOffset; |
| |
| @override |
| List<int>? get fileOffsetsIfMultiple => [fileOffset, fileEndOffset]; |
| |
| /// Kernel async marker for the function. |
| /// |
| /// See also [dartAsyncMarker]. |
| AsyncMarker asyncMarker; |
| |
| /// Dart async marker for the function. |
| /// |
| /// See also [asyncMarker]. |
| /// |
| /// A Kernel function can represent a Dart function with a different async |
| /// marker. |
| /// |
| /// For example, when async/await is translated away, |
| /// a Dart async function might be represented by a Kernel sync function. |
| AsyncMarker dartAsyncMarker; |
| |
| List<TypeParameter> typeParameters; |
| int requiredParameterCount; |
| List<VariableDeclaration> positionalParameters; |
| List<VariableDeclaration> namedParameters; |
| DartType returnType; // Not null. |
| Statement? _body; |
| |
| /// The emitted value of non-sync functions |
| /// |
| /// For `async` functions [emittedValueType] is the future value type, that |
| /// is, the returned element type. For instance |
| /// |
| /// Future<Foo> method1() async => new Foo(); |
| /// FutureOr<Foo> method2() async => new Foo(); |
| /// |
| /// here the return types are `Future<Foo>` and `FutureOr<Foo>` for `method1` |
| /// and `method2`, respectively, but the future value type is in both cases |
| /// `Foo`. |
| /// |
| /// For pre-nnbd libraries, this is set to `flatten(T)` of the return type |
| /// `T`, which can be seen as the pre-nnbd equivalent of the future value |
| /// type. |
| /// |
| /// For `sync*` functions [emittedValueType] is the type of the element of the |
| /// iterable returned by the function. |
| /// |
| /// For `async*` functions [emittedValueType] is the type of the element of |
| /// the stream returned by the function. |
| /// |
| /// For sync functions (those not marked with one of `async`, `sync*`, or |
| /// `async*`) the value of [emittedValueType] is null. |
| DartType? emittedValueType; |
| |
| /// If the function is a redirecting factory constructor, this holds |
| /// the target and type arguments of the redirection. |
| RedirectingFactoryTarget? redirectingFactoryTarget; |
| |
| void Function()? lazyBuilder; |
| |
| void _buildLazy() { |
| void Function()? lazyBuilderLocal = lazyBuilder; |
| if (lazyBuilderLocal != null) { |
| lazyBuilder = null; |
| lazyBuilderLocal(); |
| } |
| } |
| |
| Statement? get body { |
| _buildLazy(); |
| return _body; |
| } |
| |
| void set body(Statement? body) { |
| _buildLazy(); |
| _body = body; |
| } |
| |
| FunctionNode(this._body, |
| {List<TypeParameter>? typeParameters, |
| List<VariableDeclaration>? positionalParameters, |
| List<VariableDeclaration>? namedParameters, |
| int? requiredParameterCount, |
| this.returnType = const DynamicType(), |
| this.asyncMarker = AsyncMarker.Sync, |
| AsyncMarker? dartAsyncMarker, |
| this.emittedValueType}) |
| : this.positionalParameters = |
| positionalParameters ?? <VariableDeclaration>[], |
| this.requiredParameterCount = |
| requiredParameterCount ?? positionalParameters?.length ?? 0, |
| this.namedParameters = namedParameters ?? <VariableDeclaration>[], |
| this.typeParameters = typeParameters ?? <TypeParameter>[], |
| this.dartAsyncMarker = dartAsyncMarker ?? asyncMarker { |
| setParents(this.typeParameters, this); |
| setParents(this.positionalParameters, this); |
| setParents(this.namedParameters, this); |
| _body?.parent = this; |
| } |
| |
| static DartType _getTypeOfVariable(VariableDeclaration node) => node.type; |
| |
| static NamedType _getNamedTypeOfVariable(VariableDeclaration node, |
| [Substitution? substitution]) { |
| return new NamedType( |
| node.name!, |
| substitution != null |
| ? substitution.substituteType(node.type) |
| : node.type, |
| isRequired: node.isRequired); |
| } |
| |
| /// Returns the function type of the node reusing its type parameters. |
| /// |
| /// This getter works similarly to [functionType], but reuses type parameters |
| /// of the function node (or the class enclosing it -- see the comment on |
| /// [functionType] about constructors of generic classes) in the result. It |
| /// is useful in some contexts, especially when reasoning about the function |
| /// type of the enclosing generic function and in combination with |
| /// [FunctionType.withoutTypeParameters]. |
| FunctionType computeThisFunctionType(Nullability nullability, |
| {bool reuseTypeParameters = false}) { |
| TreeNode? parent = this.parent; |
| |
| List<StructuralParameter> structuralParameters; |
| List<TypeParameter> typeParametersToCopy = parent is Constructor |
| ? parent.enclosingClass.typeParameters |
| : typeParameters; |
| DartType returnType; |
| List<DartType> positionalParameters; |
| List<NamedType> namedParameters; |
| if (typeParametersToCopy.isEmpty || reuseTypeParameters) { |
| structuralParameters = const <StructuralParameter>[]; |
| returnType = this.returnType; |
| List<VariableDeclaration> thisPositionals = this.positionalParameters; |
| positionalParameters = List.generate(thisPositionals.length, |
| (index) => _getTypeOfVariable(thisPositionals[index]), |
| growable: false); |
| |
| List<VariableDeclaration> thisNamed = this.namedParameters; |
| if (thisNamed.isEmpty) { |
| namedParameters = const <NamedType>[]; |
| } else { |
| namedParameters = List.generate(thisNamed.length, |
| (index) => _getNamedTypeOfVariable(thisNamed[index]), |
| growable: false); |
| namedParameters.sort(); |
| } |
| } else { |
| // We need create a copy of the list of type parameters, otherwise |
| // transformations like erasure don't work. |
| FreshStructuralParametersFromTypeParameters freshStructuralParameters = |
| getFreshStructuralParametersFromTypeParameters(typeParametersToCopy); |
| structuralParameters = freshStructuralParameters.freshTypeParameters; |
| Substitution substitution = freshStructuralParameters.substitution; |
| returnType = substitution.substituteType(this.returnType); |
| |
| List<VariableDeclaration> thisPositionals = this.positionalParameters; |
| positionalParameters = List.generate( |
| thisPositionals.length, |
| (index) => substitution |
| .substituteType(_getTypeOfVariable(thisPositionals[index])), |
| growable: false); |
| List<VariableDeclaration> thisNamed = this.namedParameters; |
| if (thisNamed.isEmpty) { |
| namedParameters = const <NamedType>[]; |
| } else { |
| namedParameters = List.generate(thisNamed.length, |
| (index) => _getNamedTypeOfVariable(thisNamed[index], substitution), |
| growable: false); |
| namedParameters.sort(); |
| } |
| } |
| // TODO(johnniwinther,cstefantsova): Cache the function type here and use |
| // [DartType.withDeclaredNullability] to handle the variants. |
| return new FunctionType(positionalParameters, returnType, nullability, |
| namedParameters: namedParameters, |
| typeParameters: structuralParameters, |
| requiredParameterCount: requiredParameterCount); |
| } |
| |
| /// Returns the function type of the function node. |
| /// |
| /// If the function node describes a generic function, the resulting function |
| /// type will be generic. If the function node describes a constructor of a |
| /// generic class, the resulting function type will be generic with its type |
| /// parameters constructed after those of the class. In both cases, if the |
| /// resulting function type is generic, a fresh set of type parameters is used |
| /// in it. |
| // TODO(johnniwinther,cstefantsova): Merge it with [computeThisFunctionType]. |
| FunctionType computeFunctionType(Nullability nullability) { |
| return computeThisFunctionType(nullability); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.visitFunctionNode(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => |
| v.visitFunctionNode(this, arg); |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(typeParameters, v); |
| visitList(positionalParameters, v); |
| visitList(namedParameters, v); |
| returnType.accept(v); |
| emittedValueType?.accept(v); |
| redirectingFactoryTarget?.target?.acceptReference(v); |
| if (redirectingFactoryTarget?.typeArguments != null) { |
| visitList(redirectingFactoryTarget!.typeArguments!, v); |
| } |
| body?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(typeParameters, this); |
| v.transformList(positionalParameters, this); |
| v.transformList(namedParameters, this); |
| returnType = v.visitDartType(returnType); |
| if (emittedValueType != null) { |
| emittedValueType = v.visitDartType(emittedValueType!); |
| } |
| if (redirectingFactoryTarget?.typeArguments != null) { |
| v.transformDartTypeList(redirectingFactoryTarget!.typeArguments!); |
| } |
| if (body != null) { |
| body = v.transform(body!); |
| body?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformTypeParameterList(typeParameters, this); |
| v.transformVariableDeclarationList(positionalParameters, this); |
| v.transformVariableDeclarationList(namedParameters, this); |
| returnType = v.visitDartType(returnType, cannotRemoveSentinel); |
| if (emittedValueType != null) { |
| emittedValueType = |
| v.visitDartType(emittedValueType!, cannotRemoveSentinel); |
| } |
| if (redirectingFactoryTarget?.typeArguments != null) { |
| v.transformDartTypeList(redirectingFactoryTarget!.typeArguments!); |
| } |
| if (body != null) { |
| body = v.transformOrRemoveStatement(body!); |
| body?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "FunctionNode(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| enum AsyncMarker { |
| // Do not change the order of these, the frontends depend on it. |
| Sync, |
| SyncStar, |
| Async, |
| AsyncStar, |
| } |