| // Copyright (c) 2025, 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. |
| |
| import 'package:cfg/ir/constant_value.dart'; |
| import 'package:cfg/ir/source_position.dart'; |
| import 'package:cfg/ir/types.dart'; |
| import 'package:cfg/utils/misc.dart'; |
| import 'package:kernel/ast.dart' as ast; |
| |
| /// Base class representing a function (getter, setter, regular method, |
| /// constructor, field initializer etc) in CFG IR. |
| /// |
| /// A function can be called via one of the call instructions. |
| /// A non-abstract function can have CFG IR implementing this function. |
| sealed class CFunction { |
| /// Corresponding or enclosing AST member. |
| /// |
| /// Multiple distinct functions could have the same [member], |
| /// e.g. both [GetterFunction] and [SetterFunction] can have |
| /// the same [ast.Field] member. |
| final ast.Member member; |
| |
| CFunction._(this.member); |
| |
| ast.FunctionNode? get functionNode => member.function; |
| |
| /// Whether this function has a receiver parameter. |
| bool get hasReceiverParameter => |
| member.isInstanceMember || member is ast.Constructor; |
| |
| /// Whether this function has a closure parameter. |
| bool get hasClosureParameter => false; |
| |
| /// Whether this function has class type parameters in scope. |
| bool get hasClassTypeParameters => |
| (member.isInstanceMember || member is ast.Constructor) && |
| member.enclosingClass!.typeParameters.isNotEmpty; |
| |
| /// Number of function type parameters declared by this function. |
| int get numberOfFunctionTypeParameters => |
| functionNode?.typeParameters.length ?? 0; |
| |
| /// Whether this function has function type parameters. |
| bool get hasFunctionTypeParameters => numberOfFunctionTypeParameters > 0; |
| |
| /// Number of function type parameters declared by the enclosing functions. |
| int get numberOfEnclosingFunctionTypeParameters => 0; |
| |
| /// Whether enclosing functions have function type parameters. |
| bool get hasEnclosingFunctionTypeParameters => |
| numberOfEnclosingFunctionTypeParameters > 0; |
| |
| /// Number of implicit parameters of this function: |
| /// - function type parameters (represented with a single parameter), |
| /// - receiver, |
| /// - closure. |
| int get numberOfImplicitParameters => |
| (hasFunctionTypeParameters ? 1 : 0) + |
| (hasReceiverParameter ? 1 : 0) + |
| (hasClosureParameter ? 1 : 0); |
| |
| /// Number of required positional parameters including implicit parameters. |
| int get numberOfRequiredPositionalParameters => |
| numberOfImplicitParameters + (functionNode?.requiredParameterCount ?? 0); |
| |
| /// Total number of parameters including implicit parameters and |
| /// optional parameters. |
| int get numberOfParameters => |
| numberOfImplicitParameters + |
| (functionNode?.positionalParameters.length ?? 0) + |
| (functionNode?.namedParameters.length ?? 0); |
| |
| /// Whether this function has optional positional parameters. |
| bool get hasOptionalPositionalParameters => |
| functionNode != null && |
| (functionNode!.requiredParameterCount < |
| functionNode!.positionalParameters.length); |
| |
| /// Whether this function has named parameters. |
| bool get hasNamedParameters => |
| functionNode?.namedParameters.isNotEmpty ?? false; |
| |
| ast.FunctionParameter _getOptionalOrNamedParameter(int index) => |
| hasOptionalPositionalParameters |
| ? functionNode!.positionalParameters[index - numberOfImplicitParameters] |
| : functionNode!.namedParameters[index - |
| numberOfRequiredPositionalParameters]; |
| |
| /// Default value of the [index]-th optional or named parameter. |
| ConstantValue getParameterDefaultValue(int index) => ConstantValue( |
| (_getOptionalOrNamedParameter(index).defaultValue as ast.ConstantExpression) |
| .constant, |
| ); |
| |
| /// Name of the [index]-th named parameter. |
| /// Named parameters are sorted by name and follow required positional parameters. |
| String getParameterName(int index) { |
| assert(hasNamedParameters); |
| return _getOptionalOrNamedParameter(index).cosmeticName!; |
| } |
| |
| /// Whether the [index]-th named parameter is required. |
| bool isRequiredParameter(int index) { |
| assert(hasNamedParameters); |
| return _getOptionalOrNamedParameter(index).isRequired; |
| } |
| |
| /// Return type of this function. |
| CType get returnType; |
| |
| /// Async marker of this function. |
| ast.AsyncMarker get asyncMarker => |
| functionNode?.asyncMarker ?? ast.AsyncMarker.Sync; |
| |
| /// Whether this function is suspendable (i.e. async, async* or sync*). |
| bool get isSuspendable => asyncMarker != ast.AsyncMarker.Sync; |
| |
| /// Source position of the beginning of this function. |
| SourcePosition get sourcePosition => SourcePosition(member.fileOffset); |
| } |
| |
| /// Function representing a getter. |
| final class GetterFunction extends CFunction { |
| GetterFunction._(super.member) : assert(member.hasGetter), super._(); |
| |
| @override |
| int get numberOfRequiredPositionalParameters => numberOfImplicitParameters; |
| |
| @override |
| int get numberOfParameters => numberOfImplicitParameters; |
| |
| @override |
| late final CType returnType = CType.fromStaticType(member.getterType); |
| |
| @override |
| String toString() => 'getter $member'; |
| } |
| |
| /// Function representing an implicit getter of a field. |
| final class ImplicitFieldGetter extends GetterFunction { |
| ImplicitFieldGetter._(ast.Field super.member) : super._(); |
| } |
| |
| /// Function representing closurization of an instance method. |
| final class MethodExtractor extends GetterFunction { |
| MethodExtractor._(ast.Procedure super.member) |
| : assert(member.isInstanceMember), |
| super._(); |
| |
| @override |
| String toString() => 'method-extractor $member'; |
| } |
| |
| /// Function representing a setter. |
| final class SetterFunction extends CFunction { |
| SetterFunction._(super.member) : assert(member.hasSetter), super._(); |
| |
| @override |
| int get numberOfRequiredPositionalParameters => |
| numberOfImplicitParameters + 1 /* value */; |
| |
| @override |
| int get numberOfParameters => numberOfImplicitParameters + 1 /* value */; |
| |
| @override |
| CType get returnType => const TopType(const ast.VoidType()); |
| |
| /// Type of the value parameter. |
| late final CType valueType = CType.fromStaticType(member.setterType); |
| |
| @override |
| String toString() => 'setter $member'; |
| } |
| |
| /// Function representing an implicit setter of a field. |
| final class ImplicitFieldSetter extends SetterFunction { |
| ImplicitFieldSetter._(ast.Field super.member) : super._(); |
| } |
| |
| /// Function representing a field initializer. |
| final class FieldInitializerFunction extends CFunction { |
| FieldInitializerFunction._(super.member) |
| : assert(member is ast.Field && member.initializer != null), |
| super._(); |
| |
| @override |
| ast.FunctionNode? get functionNode => null; |
| |
| @override |
| int get numberOfRequiredPositionalParameters => numberOfImplicitParameters; |
| |
| @override |
| int get numberOfParameters => numberOfImplicitParameters; |
| |
| @override |
| CType get returnType => CType.fromStaticType(member.getterType); |
| |
| @override |
| String toString() => 'field-init $member'; |
| } |
| |
| /// Regular instance or static method, or a top-level function. |
| /// Also used for factory constructors. |
| final class RegularFunction extends CFunction { |
| RegularFunction._(ast.Procedure super.member) |
| : assert(!member.isGetter && !member.isSetter), |
| super._(); |
| |
| @override |
| late final CType returnType = CType.fromStaticType(functionNode!.returnType); |
| |
| @override |
| String toString() => member.toString(); |
| } |
| |
| /// Generative constructor. |
| final class GenerativeConstructor extends CFunction { |
| GenerativeConstructor._(ast.Constructor super.member) : super._(); |
| |
| @override |
| CType get returnType => const TopType(const ast.VoidType()); |
| |
| @override |
| String toString() => member.toString(); |
| } |
| |
| /// Closure function. |
| sealed class ClosureFunction extends CFunction { |
| ClosureFunction._(super.member) : super._(); |
| |
| @override |
| bool get hasReceiverParameter => false; |
| |
| @override |
| bool get hasClosureParameter => true; |
| } |
| |
| /// Anonymous closure or a local function. |
| final class LocalFunction extends ClosureFunction { |
| final ast.LocalFunction localFunction; |
| final CFunction enclosingFunction; |
| |
| LocalFunction._(super.member, this.enclosingFunction, this.localFunction) |
| : assert(enclosingFunction.member == member), |
| super._(); |
| |
| @override |
| ast.FunctionNode? get functionNode => localFunction.function; |
| |
| @override |
| String toString() => 'closure $localFunction at $member'; |
| |
| @override |
| late final CType returnType = CType.fromStaticType(functionNode!.returnType); |
| |
| @override |
| SourcePosition get sourcePosition => SourcePosition(localFunction.fileOffset); |
| |
| @override |
| late final int numberOfEnclosingFunctionTypeParameters = |
| enclosingFunction.numberOfEnclosingFunctionTypeParameters + |
| enclosingFunction.numberOfFunctionTypeParameters; |
| } |
| |
| /// Tear-off (result of function closurization). |
| final class TearOffFunction extends ClosureFunction { |
| TearOffFunction._(super.member) : super._(); |
| |
| @override |
| String toString() => 'tear-off $member'; |
| |
| @override |
| bool get hasClassTypeParameters => false; |
| |
| @override |
| bool get hasFunctionTypeParameters => member is ast.Constructor |
| ? member.enclosingClass!.typeParameters.isNotEmpty |
| : member.function!.typeParameters.isNotEmpty; |
| |
| @override |
| late final CType returnType = CType.fromStaticType( |
| member is ast.Constructor |
| ? ast.InterfaceType( |
| member.enclosingClass!, |
| ast.Nullability.nonNullable, |
| member.enclosingClass!.typeParameters |
| .map((tp) => tp.defaultType) |
| .toList(), |
| ) |
| : member.function!.returnType, |
| ); |
| |
| @override |
| ast.AsyncMarker get asyncMarker => .Sync; |
| } |
| |
| class ArgumentsShape { |
| final int types; |
| final int positional; |
| final List<String> named; |
| |
| const ArgumentsShape(this.types, this.positional, this.named); |
| |
| @override |
| String toString() => |
| 'Args[$positional${types > 0 ? ', types: $types' : ''}${named.isNotEmpty ? ', named: $named' : ''}]'; |
| |
| @override |
| bool operator ==(Object other) => |
| other is ArgumentsShape && |
| this.types == other.types && |
| this.positional == other.positional && |
| listEquals(this.named, other.named); |
| |
| @override |
| int get hashCode => finalizeHash( |
| combineHash( |
| combineHash(types.hashCode, positional.hashCode), |
| listHashCode(named), |
| ), |
| ); |
| } |
| |
| /// Mapping between AST nodes and functions. |
| /// |
| /// Ensures that unique [CFunction] is used to represent |
| /// each function in the program. |
| class FunctionRegistry { |
| final Map<ast.Member, CFunction> _getters = {}; |
| final Map<ast.Member, CFunction> _setters = {}; |
| final Map<ast.LocalFunction, CFunction> _closures = {}; |
| final Map<ast.Member, CFunction> _tearOffs = {}; |
| final Map<ast.Member, CFunction> _methodExtractors = {}; |
| final Map<ast.Member, CFunction> _fieldInitializers = {}; |
| final Map<ast.Member, CFunction> _other = {}; |
| final List<ArgumentsShape> _positionalArgShapes = []; |
| |
| /// Returns [CFunction] corresponding to a [member] or [localFunction] with given properties. |
| /// |
| /// [enclosingFunction] should be specified for the local function |
| /// when querying a function for the first time. |
| CFunction getFunction( |
| ast.Member member, { |
| bool isGetter = false, |
| bool isSetter = false, |
| bool isInitializer = false, |
| bool isTearOff = false, |
| bool isMethodExtractor = false, |
| CFunction? enclosingFunction, |
| ast.LocalFunction? localFunction, |
| }) { |
| if (localFunction != null) { |
| assert( |
| !isGetter && |
| !isSetter && |
| !isInitializer && |
| !isTearOff && |
| !isMethodExtractor, |
| ); |
| return _closures[localFunction] ??= LocalFunction._( |
| member, |
| enclosingFunction!, |
| localFunction, |
| ); |
| } |
| if (isTearOff) { |
| assert(!isGetter && !isSetter && !isInitializer && !isMethodExtractor); |
| assert(member is ast.Procedure || member is ast.Constructor); |
| return _tearOffs[member] ??= TearOffFunction._(member); |
| } |
| if (isMethodExtractor) { |
| assert(!isGetter && !isSetter && !isInitializer); |
| return _methodExtractors[member] ??= MethodExtractor._( |
| member as ast.Procedure, |
| ); |
| } |
| if (isInitializer) { |
| assert(!isGetter && !isSetter); |
| return _fieldInitializers[member] ??= FieldInitializerFunction._(member); |
| } |
| if (isGetter) { |
| assert(!isSetter); |
| return _getters[member] ??= (member is ast.Field) |
| ? ImplicitFieldGetter._(member) |
| : GetterFunction._(member); |
| } |
| if (isSetter) { |
| return _setters[member] ??= (member is ast.Field) |
| ? ImplicitFieldSetter._(member) |
| : SetterFunction._(member); |
| } |
| return _other[member] ??= switch (member) { |
| ast.Constructor() => GenerativeConstructor._(member), |
| ast.Procedure() => RegularFunction._(member), |
| _ => throw 'Unexpected member ${member.runtimeType} $member', |
| }; |
| } |
| |
| ArgumentsShape getArgumentsShape( |
| int positional, { |
| int types = 0, |
| List<String> named = const [], |
| }) { |
| if (types == 0 && named.isEmpty) { |
| for (int i = _positionalArgShapes.length, n = positional; i <= n; ++i) { |
| _positionalArgShapes.add(ArgumentsShape(0, i, const <String>[])); |
| } |
| return _positionalArgShapes[positional]; |
| } |
| return ArgumentsShape(types, positional, named); |
| } |
| } |