blob: 0cbb45a1f97f41f848fa84abd9260db2e59eee0f [file] [log] [blame]
// Copyright (c) 2021, 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:kernel/ast.dart';
import 'package:kernel/type_algebra.dart';
import '../source/source_library_builder.dart';
import '../source/source_member_builder.dart';
import 'kernel_helper.dart';
const String _tearOffNamePrefix = '_#';
const String _tearOffNameSuffix = '#tearOff';
/// Creates the synthesized name to use for the lowering of the tear off of a
/// constructor or factory by the given [name] in [library].
Name constructorTearOffName(String name, Library library) {
return new Name(
'$_tearOffNamePrefix'
'${name.isEmpty ? 'new' : name}'
'$_tearOffNameSuffix',
library);
}
/// Creates the synthesized name to use for the lowering of the tear off of a
/// constructor or factory by the given [constructorName] in [library].
Name typedefTearOffName(
String typedefName, String constructorName, Library library) {
return new Name(
'$_tearOffNamePrefix'
'$typedefName#'
'${constructorName.isEmpty ? 'new' : constructorName}'
'$_tearOffNameSuffix',
library);
}
/// Returns the name of the corresponding constructor or factory if [name] is
/// the synthesized name of a lowering of the tear off of a constructor or
/// factory. Returns `null` otherwise.
String? extractConstructorNameFromTearOff(Name name) {
if (name.text.startsWith(_tearOffNamePrefix) &&
name.text.endsWith(_tearOffNameSuffix) &&
name.text.length >
_tearOffNamePrefix.length + _tearOffNameSuffix.length) {
String text =
name.text.substring(0, name.text.length - _tearOffNameSuffix.length);
text = text.substring(_tearOffNamePrefix.length);
if (text.contains('#')) {
return null;
}
return text == 'new' ? '' : text;
}
return null;
}
/// If [name] is the synthesized name of a lowering of a typedef tear off, a
/// list containing the [String] name of the typedef and the [Name] name of the
/// corresponding constructor or factory is returned. Returns `null` otherwise.
List<Object>? extractTypedefNameFromTearOff(Name name) {
if (name.text.startsWith(_tearOffNamePrefix) &&
name.text.endsWith(_tearOffNameSuffix) &&
name.text.length >
_tearOffNamePrefix.length + _tearOffNameSuffix.length) {
String text =
name.text.substring(0, name.text.length - _tearOffNameSuffix.length);
text = text.substring(_tearOffNamePrefix.length);
int hashIndex = text.indexOf('#');
if (hashIndex == -1) {
return null;
}
String typedefName = text.substring(0, hashIndex);
String constructorName = text.substring(hashIndex + 1);
constructorName = constructorName == 'new' ? '' : constructorName;
return [typedefName, new Name(constructorName, name.library)];
}
return null;
}
/// Returns `true` if [member] is a lowered constructor, factory or typedef tear
/// off.
bool isTearOffLowering(Member member) {
return member is Procedure &&
(isConstructorTearOffLowering(member) ||
isTypedefTearOffLowering(member));
}
/// Returns `true` if [procedure] is a lowered constructor or factory tear off.
bool isConstructorTearOffLowering(Procedure procedure) {
return extractConstructorNameFromTearOff(procedure.name) != null;
}
/// Returns `true` if [procedure] is a lowered typedef tear off.
bool isTypedefTearOffLowering(Procedure procedure) {
return extractTypedefNameFromTearOff(procedure.name) != null;
}
/// Creates the [Procedure] for the lowering of a generative constructor of
/// the given [name] in [compilationUnit].
///
/// If constructor tear off lowering is not enabled, `null` is returned.
Procedure? createConstructorTearOffProcedure(
String name,
SourceLibraryBuilder compilationUnit,
Uri fileUri,
int fileOffset,
Reference? reference,
{required bool forAbstractClassOrEnum}) {
if (!forAbstractClassOrEnum &&
compilationUnit
.loader.target.backendTarget.isConstructorTearOffLoweringEnabled) {
return _createTearOffProcedure(
compilationUnit,
constructorTearOffName(name, compilationUnit.library),
fileUri,
fileOffset,
reference);
}
return null;
}
/// Creates the [Procedure] for the lowering of a non-redirecting factory of
/// the given [name] in [compilationUnit].
///
/// If constructor tear off lowering is not enabled, `null` is returned.
Procedure? createFactoryTearOffProcedure(
String name,
SourceLibraryBuilder compilationUnit,
Uri fileUri,
int fileOffset,
Reference? reference) {
if (compilationUnit
.loader.target.backendTarget.isFactoryTearOffLoweringEnabled) {
return _createTearOffProcedure(
compilationUnit,
constructorTearOffName(name, compilationUnit.library),
fileUri,
fileOffset,
reference);
}
return null;
}
/// Creates the [Procedure] for the lowering of a typedef tearoff of a
/// constructor of the given [name] in with the typedef defined in
/// [libraryBuilder].
Procedure createTypedefTearOffProcedure(
String typedefName,
String name,
SourceLibraryBuilder libraryBuilder,
Uri fileUri,
int fileOffset,
Reference? reference) {
return _createTearOffProcedure(
libraryBuilder,
typedefTearOffName(typedefName, name, libraryBuilder.library),
fileUri,
fileOffset,
reference);
}
/// Creates the parameters and body for [tearOff] based on [constructor] in
/// [enclosingClass].
void buildConstructorTearOffProcedure(Procedure tearOff, Member constructor,
Class enclosingClass, SourceLibraryBuilder libraryBuilder) {
assert(
constructor is Constructor ||
(constructor is Procedure && constructor.isFactory) ||
(constructor is Procedure && constructor.isStatic),
"Unexpected constructor tear off target $constructor "
"(${constructor.runtimeType}).");
int fileOffset = tearOff.fileOffset;
FunctionNode function = constructor.function!;
List<TypeParameter> classTypeParameters;
if (constructor is Constructor) {
// Generative constructors implicitly have the type parameters of the
// enclosing class.
classTypeParameters = enclosingClass.typeParameters;
} else {
// Factory constructors explicitly copy over the type parameters of the
// enclosing class.
classTypeParameters = function.typeParameters;
}
FreshTypeParameters freshTypeParameters =
_createFreshTypeParameters(classTypeParameters, tearOff.function);
List<DartType> typeArguments = freshTypeParameters.freshTypeArguments;
Substitution substitution = freshTypeParameters.substitution;
_createParameters(tearOff, constructor, substitution, libraryBuilder);
Arguments arguments = _createArguments(tearOff, typeArguments, fileOffset);
_createTearOffBody(tearOff, constructor, arguments);
tearOff.function.fileOffset = tearOff.fileOffset;
tearOff.function.fileEndOffset = tearOff.fileOffset;
updatePrivateMemberName(tearOff, libraryBuilder);
}
/// Creates the parameters and body for [tearOff] for a typedef tearoff of
/// [constructor] in [enclosingClass] with [typeParameters] as the typedef
/// parameters and [typeArguments] as the arguments passed to the
/// [enclosingClass].
void buildTypedefTearOffProcedure(
Procedure tearOff,
Member constructor,
Class enclosingClass,
List<TypeParameter> typeParameters,
List<DartType> typeArguments,
SourceLibraryBuilder libraryBuilder) {
assert(
constructor is Constructor ||
(constructor is Procedure && constructor.isFactory) ||
(constructor is Procedure && constructor.isStatic),
"Unexpected constructor tear off target $constructor "
"(${constructor.runtimeType}).");
int fileOffset = tearOff.fileOffset;
FunctionNode function = constructor.function!;
List<TypeParameter> classTypeParameters;
if (constructor is Constructor) {
// Generative constructors implicitly have the type parameters of the
// enclosing class.
classTypeParameters = enclosingClass.typeParameters;
} else {
// Factory constructors explicitly copy over the type parameters of the
// enclosing class.
classTypeParameters = function.typeParameters;
}
FreshTypeParameters freshTypeParameters =
_createFreshTypeParameters(typeParameters, tearOff.function);
Substitution substitution = freshTypeParameters.substitution;
if (!substitution.isEmpty) {
if (typeArguments.isNotEmpty) {
// Translate [typeArgument] into the context of the synthesized procedure.
typeArguments = new List<DartType>.generate(typeArguments.length,
(int index) => substitution.substituteType(typeArguments[index]));
}
}
_createParameters(
tearOff,
constructor,
Substitution.fromPairs(classTypeParameters, typeArguments),
libraryBuilder);
Arguments arguments = _createArguments(tearOff, typeArguments, fileOffset);
_createTearOffBody(tearOff, constructor, arguments);
tearOff.function.fileOffset = tearOff.fileOffset;
tearOff.function.fileEndOffset = tearOff.fileOffset;
updatePrivateMemberName(tearOff, libraryBuilder);
}
/// Creates the parameters for the redirecting factory [tearOff] based on the
/// [redirectingConstructor] declaration.
FreshTypeParameters buildRedirectingFactoryTearOffProcedureParameters(
Procedure tearOff,
Procedure redirectingConstructor,
SourceLibraryBuilder libraryBuilder) {
assert(redirectingConstructor.isRedirectingFactory);
FunctionNode function = redirectingConstructor.function;
FreshTypeParameters freshTypeParameters =
_createFreshTypeParameters(function.typeParameters, tearOff.function);
Substitution substitution = freshTypeParameters.substitution;
_createParameters(
tearOff, redirectingConstructor, substitution, libraryBuilder);
tearOff.function.fileOffset = tearOff.fileOffset;
tearOff.function.fileEndOffset = tearOff.fileOffset;
updatePrivateMemberName(tearOff, libraryBuilder);
return freshTypeParameters;
}
/// Creates the body for the redirecting factory [tearOff] with the target
/// [constructor] and [typeArguments].
///
/// Returns the [SynthesizedFunctionNode] object need to perform default value
/// computation.
SynthesizedFunctionNode buildRedirectingFactoryTearOffBody(
Procedure tearOff,
Member target,
List<DartType> typeArguments,
FreshTypeParameters freshTypeParameters) {
int fileOffset = tearOff.fileOffset;
List<TypeParameter> typeParameters;
if (target is Constructor) {
typeParameters = target.enclosingClass.typeParameters;
} else {
typeParameters = target.function!.typeParameters;
}
if (!freshTypeParameters.substitution.isEmpty) {
if (typeArguments.length != typeParameters.length) {
// Error case: Use default types as type arguments.
typeArguments = new List<DartType>.generate(typeParameters.length,
(int index) => typeParameters[index].defaultType);
}
if (typeArguments.isNotEmpty) {
// Translate [typeArgument] into the context of the synthesized procedure.
typeArguments = new List<DartType>.generate(
typeArguments.length,
(int index) => freshTypeParameters.substitution
.substituteType(typeArguments[index]));
}
}
Map<TypeParameter, DartType> substitutionMap;
if (typeParameters.length == typeArguments.length) {
substitutionMap = new Map<TypeParameter, DartType>.fromIterables(
typeParameters, typeArguments);
} else {
// Error case: Substitute type parameters with `dynamic`.
substitutionMap = new Map<TypeParameter, DartType>.fromIterables(
typeParameters,
new List<DartType>.generate(
typeParameters.length, (int index) => const DynamicType()));
}
Arguments arguments = _createArguments(tearOff, typeArguments, fileOffset);
_createTearOffBody(tearOff, target, arguments);
return new SynthesizedFunctionNode(
substitutionMap, target.function!, tearOff.function,
identicalSignatures: false);
}
/// Creates the synthesized [Procedure] node for a tear off lowering by the
/// given [name].
Procedure _createTearOffProcedure(SourceLibraryBuilder libraryBuilder,
Name name, Uri fileUri, int fileOffset, Reference? reference) {
return new Procedure(name, ProcedureKind.Method, new FunctionNode(null),
fileUri: fileUri, isStatic: true, reference: reference)
..startFileOffset = fileOffset
..fileOffset = fileOffset
..fileEndOffset = fileOffset
..isNonNullableByDefault = libraryBuilder.isNonNullableByDefault;
}
/// Creates the synthesized type parameters for a tear off lowering. The type
/// parameters are based [originalTypeParameters] and are inserted into
/// [newFunctionNode]. The created [FreshTypeParameters] is returned.
FreshTypeParameters _createFreshTypeParameters(
List<TypeParameter> originalTypeParameters, FunctionNode newFunctionNode) {
FreshTypeParameters freshTypeParameters;
if (originalTypeParameters.isNotEmpty) {
freshTypeParameters = getFreshTypeParameters(originalTypeParameters);
List<TypeParameter> typeParameters =
freshTypeParameters.freshTypeParameters;
newFunctionNode.typeParameters.addAll(typeParameters);
setParents(typeParameters, newFunctionNode);
} else {
freshTypeParameters = new FreshTypeParameters([], [], Substitution.empty);
}
return freshTypeParameters;
}
/// Creates the parameters for the [tearOff] lowering based of the parameters
/// in [constructor] and using the [substitution] to compute the parameter and
/// return types.
void _createParameters(Procedure tearOff, Member constructor,
Substitution substitution, SourceLibraryBuilder libraryBuilder) {
FunctionNode function = constructor.function!;
for (VariableDeclaration constructorParameter
in function.positionalParameters) {
VariableDeclaration tearOffParameter = new VariableDeclaration(
constructorParameter.name,
type: substitution.substituteType(constructorParameter.type))
..fileOffset = constructorParameter.fileOffset;
tearOff.function.positionalParameters.add(tearOffParameter);
tearOffParameter.parent = tearOff.function;
}
for (VariableDeclaration constructorParameter in function.namedParameters) {
VariableDeclaration tearOffParameter = new VariableDeclaration(
constructorParameter.name,
type: substitution.substituteType(constructorParameter.type),
isRequired: constructorParameter.isRequired)
..fileOffset = constructorParameter.fileOffset;
tearOff.function.namedParameters.add(tearOffParameter);
tearOffParameter.parent = tearOff.function;
}
tearOff.function.returnType =
substitution.substituteType(function.returnType);
tearOff.function.requiredParameterCount = function.requiredParameterCount;
libraryBuilder.loader.registerTypeDependency(
tearOff,
new TypeDependency(tearOff, constructor, substitution,
copyReturnType: true));
}
/// Creates the [Arguments] for passing the parameters from [tearOff] to its
/// target, using [typeArguments] as the passed type arguments.
Arguments _createArguments(
Procedure tearOff, List<DartType> typeArguments, int fileOffset) {
List<Expression> positionalArguments = [];
for (VariableDeclaration tearOffParameter
in tearOff.function.positionalParameters) {
positionalArguments
.add(new VariableGet(tearOffParameter)..fileOffset = fileOffset);
}
List<NamedExpression> namedArguments = [];
for (VariableDeclaration tearOffParameter
in tearOff.function.namedParameters) {
namedArguments.add(new NamedExpression(tearOffParameter.name!,
new VariableGet(tearOffParameter)..fileOffset = fileOffset)
..fileOffset = fileOffset);
}
Arguments arguments = new Arguments(positionalArguments,
named: namedArguments, types: typeArguments)
..fileOffset = tearOff.fileOffset;
return arguments;
}
/// Creates the tear of body for [tearOff] which calls [target] with
/// [arguments].
void _createTearOffBody(Procedure tearOff, Member target, Arguments arguments) {
assert(target is Constructor ||
(target is Procedure && target.isFactory) ||
(target is Procedure && target.isStatic));
Expression constructorInvocation;
if (target is Constructor) {
constructorInvocation = new ConstructorInvocation(target, arguments)
..fileOffset = tearOff.fileOffset;
} else {
constructorInvocation = new StaticInvocation(target as Procedure, arguments)
..fileOffset = tearOff.fileOffset;
}
tearOff.function.body = new ReturnStatement(constructorInvocation)
..fileOffset = tearOff.fileOffset
..parent = tearOff.function;
}
/// Reverse engineered typedef tear off information.
class LoweredTypedefTearOff {
Procedure typedefTearOff;
Expression targetTearOff;
List<DartType> typeArguments;
LoweredTypedefTearOff(
this.typedefTearOff, this.targetTearOff, this.typeArguments);
/// Reverse engineers [expression] to a [LoweredTypedefTearOff] if
/// [expression] is the encoding of a lowered typedef tear off.
static LoweredTypedefTearOff? fromExpression(Expression expression) {
if (expression is StaticTearOff &&
isTypedefTearOffLowering(expression.target)) {
Procedure typedefTearOff = expression.target;
Statement? body = typedefTearOff.function.body;
if (body is ReturnStatement) {
Expression? constructorInvocation = body.expression;
Member? target;
List<DartType>? typeArguments;
if (constructorInvocation is ConstructorInvocation) {
target = constructorInvocation.target;
typeArguments = constructorInvocation.arguments.types;
} else if (constructorInvocation is StaticInvocation) {
target = constructorInvocation.target;
typeArguments = constructorInvocation.arguments.types;
}
if (target != null) {
Class cls = target.enclosingClass!;
Name tearOffName =
constructorTearOffName(target.name.text, cls.enclosingLibrary);
for (Procedure procedure in cls.procedures) {
if (procedure.name == tearOffName) {
target = procedure;
break;
}
}
Expression targetTearOff;
if (target is Constructor ||
target is Procedure && target.isFactory) {
targetTearOff = new ConstructorTearOff(target!);
} else {
targetTearOff = new StaticTearOff(target as Procedure);
}
return new LoweredTypedefTearOff(
typedefTearOff, targetTearOff, typeArguments!);
}
}
}
return null;
}
}