blob: 9588563f112347f48a2079c183ce0d602822d1ba [file] [log] [blame]
// Copyright (c) 2019, 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 'package:kernel/type_environment.dart';
import '../builder/builder.dart';
import '../builder/extension_builder.dart';
import '../builder/formal_parameter_builder.dart';
import '../builder/member_builder.dart';
import '../builder/metadata_builder.dart';
import '../builder/procedure_builder.dart';
import '../builder/type_builder.dart';
import '../builder/type_variable_builder.dart';
import '../kernel/hierarchy/class_member.dart';
import '../kernel/hierarchy/members_builder.dart';
import '../kernel/kernel_helper.dart';
import '../kernel/member_covariance.dart';
import '../source/name_scheme.dart';
import '../source/source_library_builder.dart' show SourceLibraryBuilder;
import '../source/source_loader.dart' show SourceLoader;
import 'source_class_builder.dart';
import 'source_function_builder.dart';
import 'source_member_builder.dart';
class SourceProcedureBuilder extends SourceFunctionBuilderImpl
implements ProcedureBuilder {
final int charOpenParenOffset;
AsyncMarker actualAsyncModifier = AsyncMarker.Sync;
@override
final bool isExtensionInstanceMember;
@override
final TypeBuilder returnType;
late Procedure _procedure;
final Reference? _tearOffReference;
/// If this is an extension instance method then [_extensionTearOff] holds
/// the synthetically created tear off function.
Procedure? _extensionTearOff;
/// If this is an extension instance method then
/// [_extensionTearOffParameterMap] holds a map from the parameters of
/// the methods to the parameter of the closure returned in the tear-off.
///
/// This map is used to set the default values on the closure parameters when
/// these have been built.
Map<VariableDeclaration, VariableDeclaration>? _extensionTearOffParameterMap;
@override
final ProcedureKind kind;
SourceProcedureBuilder? actualOrigin;
List<SourceProcedureBuilder>? _patches;
SourceProcedureBuilder(
List<MetadataBuilder>? metadata,
int modifiers,
this.returnType,
String name,
List<TypeVariableBuilder>? typeVariables,
List<FormalParameterBuilder>? formals,
this.kind,
SourceLibraryBuilder libraryBuilder,
int startCharOffset,
int charOffset,
this.charOpenParenOffset,
int charEndOffset,
Reference? procedureReference,
this._tearOffReference,
AsyncMarker asyncModifier,
NameScheme nameScheme,
{required bool isExtensionMember,
required bool isInstanceMember,
String? nativeMethodName,
bool isSynthetic: false})
// ignore: unnecessary_null_comparison
: assert(isExtensionMember != null),
// ignore: unnecessary_null_comparison
assert(isInstanceMember != null),
assert(kind != ProcedureKind.Factory),
this.isExtensionInstanceMember = isInstanceMember && isExtensionMember,
super(metadata, modifiers, name, typeVariables, formals, libraryBuilder,
charOffset, nativeMethodName) {
_procedure = new Procedure(
nameScheme.getProcedureName(kind, name),
isExtensionInstanceMember ? ProcedureKind.Method : kind,
new FunctionNode(null),
fileUri: libraryBuilder.fileUri,
reference: procedureReference,
isSynthetic: isSynthetic)
..startFileOffset = startCharOffset
..fileOffset = charOffset
..fileEndOffset = charEndOffset
..isNonNullableByDefault = libraryBuilder.isNonNullableByDefault;
this.asyncModifier = asyncModifier;
if (isExtensionMember && isInstanceMember && kind == ProcedureKind.Method) {
_extensionTearOff ??= new Procedure(
nameScheme.getProcedureName(ProcedureKind.Getter, name),
ProcedureKind.Method,
new FunctionNode(null),
isStatic: true,
isExtensionMember: true,
reference: _tearOffReference,
fileUri: fileUri)
..isNonNullableByDefault = libraryBuilder.isNonNullableByDefault;
}
}
List<SourceProcedureBuilder>? get patchesForTesting => _patches;
@override
AsyncMarker get asyncModifier => actualAsyncModifier;
@override
Statement? get body {
if (bodyInternal == null && !isAbstract && !isExternal) {
bodyInternal = new EmptyStatement();
}
return bodyInternal;
}
void set asyncModifier(AsyncMarker newModifier) {
actualAsyncModifier = newModifier;
function.asyncMarker = actualAsyncModifier;
function.dartAsyncMarker = actualAsyncModifier;
}
bool get isExtensionMethod {
return parent is ExtensionBuilder;
}
@override
Member get member => procedure;
@override
SourceProcedureBuilder get origin => actualOrigin ?? this;
@override
Procedure get procedure => isPatch ? origin.procedure : _procedure;
Procedure get actualProcedure => _procedure;
@override
FunctionNode get function => _procedure.function;
bool _typeEnsured = false;
Set<ClassMember>? _overrideDependencies;
void registerOverrideDependency(Set<ClassMember> overriddenMembers) {
assert(
overriddenMembers.every((overriddenMember) =>
overriddenMember.classBuilder != classBuilder),
"Unexpected override dependencies for $this: $overriddenMembers");
_overrideDependencies ??= {};
_overrideDependencies!.addAll(overriddenMembers);
}
void _ensureTypes(ClassMembersBuilder membersBuilder) {
if (_typeEnsured) return;
if (_overrideDependencies != null) {
if (isGetter) {
membersBuilder.inferGetterType(this, _overrideDependencies!);
} else if (isSetter) {
membersBuilder.inferSetterType(this, _overrideDependencies!);
} else {
membersBuilder.inferMethodType(this, _overrideDependencies!);
}
_overrideDependencies = null;
}
returnType.build(libraryBuilder, TypeUse.fieldType,
hierarchy: membersBuilder.hierarchyBuilder);
if (formals != null) {
for (FormalParameterBuilder formal in formals!) {
formal.type.build(libraryBuilder, TypeUse.parameterType,
hierarchy: membersBuilder.hierarchyBuilder);
}
}
_typeEnsured = true;
}
@override
Member? get readTarget {
switch (kind) {
case ProcedureKind.Method:
return extensionTearOff ?? procedure;
case ProcedureKind.Getter:
return procedure;
case ProcedureKind.Factory:
return procedure;
case ProcedureKind.Operator:
case ProcedureKind.Setter:
return null;
}
}
@override
Member? get writeTarget {
switch (kind) {
case ProcedureKind.Setter:
return procedure;
case ProcedureKind.Method:
case ProcedureKind.Getter:
case ProcedureKind.Operator:
case ProcedureKind.Factory:
return null;
}
}
@override
Member? get invokeTarget {
switch (kind) {
case ProcedureKind.Method:
case ProcedureKind.Getter:
case ProcedureKind.Operator:
case ProcedureKind.Factory:
return procedure;
case ProcedureKind.Setter:
return null;
}
}
@override
Iterable<Member> get exportedMembers => [procedure];
@override
void buildMembers(void Function(Member, BuiltMemberKind) f) {
_build();
if (isExtensionMethod) {
switch (kind) {
case ProcedureKind.Method:
f(_procedure, BuiltMemberKind.ExtensionMethod);
break;
case ProcedureKind.Getter:
f(_procedure, BuiltMemberKind.ExtensionGetter);
break;
case ProcedureKind.Setter:
f(_procedure, BuiltMemberKind.ExtensionSetter);
break;
case ProcedureKind.Operator:
f(_procedure, BuiltMemberKind.ExtensionOperator);
break;
case ProcedureKind.Factory:
throw new UnsupportedError(
'Unexpected extension method kind ${kind}');
}
if (extensionTearOff != null) {
f(extensionTearOff!, BuiltMemberKind.ExtensionTearOff);
}
} else {
f(member, BuiltMemberKind.Method);
}
}
void _build() {
buildFunction();
_procedure.function.fileOffset = charOpenParenOffset;
_procedure.function.fileEndOffset = _procedure.fileEndOffset;
_procedure.isAbstract = isAbstract;
_procedure.isExternal = isExternal;
_procedure.isConst = isConst;
updatePrivateMemberName(_procedure, libraryBuilder);
if (isExtensionMethod) {
_procedure.isExtensionMember = true;
_procedure.isStatic = true;
if (isExtensionInstanceMember) {
assert(_procedure.kind == ProcedureKind.Method);
}
} else {
_procedure.isStatic = isStatic;
}
if (extensionTearOff != null) {
_buildExtensionTearOff(libraryBuilder, parent as ExtensionBuilder);
updatePrivateMemberName(extensionTearOff!, libraryBuilder);
}
}
/// Creates a top level function that creates a tear off of an extension
/// instance method.
///
/// For this declaration
///
/// extension E<T> on A<T> {
/// X method<S>(S s, Y y) {}
/// }
///
/// we create the top level function
///
/// X E|method<T, S>(A<T> #this, S s, Y y) {}
///
/// and the tear off function
///
/// X Function<S>(S, Y) E|get#method<T>(A<T> #this) {
/// return (S s, Y y) => E|method<T, S>(#this, s, y);
/// }
///
void _buildExtensionTearOff(SourceLibraryBuilder sourceLibraryBuilder,
ExtensionBuilder extensionBuilder) {
assert(
_extensionTearOff != null, "No extension tear off created for $this.");
_extensionTearOffParameterMap = {};
int fileOffset = _procedure.fileOffset;
int fileEndOffset = _procedure.fileEndOffset;
int extensionTypeParameterCount =
extensionBuilder.typeParameters?.length ?? 0;
List<TypeParameter> typeParameters = <TypeParameter>[];
Map<TypeParameter, DartType> substitutionMap = {};
List<DartType> typeArguments = <DartType>[];
for (TypeParameter typeParameter in function.typeParameters) {
TypeParameter newTypeParameter = new TypeParameter(typeParameter.name);
typeParameters.add(newTypeParameter);
typeArguments.add(substitutionMap[typeParameter] =
new TypeParameterType.forAlphaRenaming(
typeParameter, newTypeParameter));
}
List<TypeParameter> tearOffTypeParameters = <TypeParameter>[];
List<TypeParameter> closureTypeParameters = <TypeParameter>[];
Substitution substitution = Substitution.fromMap(substitutionMap);
for (int index = 0; index < typeParameters.length; index++) {
TypeParameter newTypeParameter = typeParameters[index];
newTypeParameter.bound =
substitution.substituteType(function.typeParameters[index].bound);
newTypeParameter.defaultType = function.typeParameters[index].defaultType;
if (index < extensionTypeParameterCount) {
tearOffTypeParameters.add(newTypeParameter);
} else {
closureTypeParameters.add(newTypeParameter);
}
}
VariableDeclaration copyParameter(
VariableDeclaration parameter, DartType type,
{required bool isOptional}) {
VariableDeclaration newParameter = new VariableDeclaration(parameter.name,
type: type,
isFinal: parameter.isFinal,
isLowered: parameter.isLowered)
..fileOffset = parameter.fileOffset;
_extensionTearOffParameterMap![parameter] = newParameter;
return newParameter;
}
VariableDeclaration extensionThis = copyParameter(
function.positionalParameters.first,
substitution.substituteType(function.positionalParameters.first.type),
isOptional: false);
DartType closureReturnType =
substitution.substituteType(function.returnType);
List<VariableDeclaration> closurePositionalParameters = [];
List<Expression> closurePositionalArguments = [];
for (int position = 0;
position < function.positionalParameters.length;
position++) {
VariableDeclaration parameter = function.positionalParameters[position];
if (position == 0) {
/// Pass `this` as a captured variable.
closurePositionalArguments
.add(new VariableGet(extensionThis)..fileOffset = fileOffset);
} else {
DartType type = substitution.substituteType(parameter.type);
VariableDeclaration newParameter = copyParameter(parameter, type,
isOptional: position >= function.requiredParameterCount);
closurePositionalParameters.add(newParameter);
closurePositionalArguments
.add(new VariableGet(newParameter)..fileOffset = fileOffset);
}
}
List<VariableDeclaration> closureNamedParameters = [];
List<NamedExpression> closureNamedArguments = [];
for (VariableDeclaration parameter in function.namedParameters) {
DartType type = substitution.substituteType(parameter.type);
VariableDeclaration newParameter =
copyParameter(parameter, type, isOptional: true);
closureNamedParameters.add(newParameter);
closureNamedArguments.add(new NamedExpression(parameter.name!,
new VariableGet(newParameter)..fileOffset = fileOffset));
}
Statement closureBody = new ReturnStatement(
new StaticInvocation(
_procedure,
new Arguments(closurePositionalArguments,
types: typeArguments, named: closureNamedArguments))
..fileOffset = fileOffset)
..fileOffset = fileOffset;
FunctionExpression closure = new FunctionExpression(
new FunctionNode(closureBody,
typeParameters: closureTypeParameters,
positionalParameters: closurePositionalParameters,
namedParameters: closureNamedParameters,
requiredParameterCount:
_procedure.function.requiredParameterCount - 1,
returnType: closureReturnType)
..fileOffset = fileOffset
..fileEndOffset = fileEndOffset)
..fileOffset = fileOffset;
_extensionTearOff!
..function = (new FunctionNode(
new ReturnStatement(closure)..fileOffset = fileOffset,
typeParameters: tearOffTypeParameters,
positionalParameters: [extensionThis],
requiredParameterCount: 1,
returnType:
closure.function.computeFunctionType(libraryBuilder.nonNullable))
..fileOffset = fileOffset
..fileEndOffset = fileEndOffset)
..fileUri = fileUri
..fileOffset = fileOffset
..fileEndOffset = fileEndOffset;
_extensionTearOff!.function.parent = _extensionTearOff;
}
Procedure? get extensionTearOff => _extensionTearOff;
@override
VariableDeclaration? getTearOffParameter(int index) {
return _extensionTearOffParameterMap?[getFormalParameter(index)];
}
List<ClassMember>? _localMembers;
List<ClassMember>? _localSetters;
@override
List<ClassMember> get localMembers => _localMembers ??= isSetter
? const <ClassMember>[]
: <ClassMember>[new SourceProcedureMember(this)];
@override
List<ClassMember> get localSetters =>
_localSetters ??= isSetter && !isConflictingSetter
? <ClassMember>[new SourceProcedureMember(this)]
: const <ClassMember>[];
@override
void becomeNative(SourceLoader loader) {
_procedure.isExternal = true;
super.becomeNative(loader);
}
@override
void applyPatch(Builder patch) {
if (patch is SourceProcedureBuilder) {
if (checkPatch(patch)) {
patch.actualOrigin = this;
(_patches ??= []).add(patch);
}
} else {
reportPatchMismatch(patch);
}
}
@override
int finishPatch() {
if (!isPatch) return 0;
finishProcedurePatch(origin.procedure, _procedure);
return 1;
}
@override
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {
sourceClassBuilder.checkVarianceInFunction(
procedure, typeEnvironment, sourceClassBuilder.cls.typeParameters);
List<SourceProcedureBuilder>? patches = _patches;
if (patches != null) {
for (SourceProcedureBuilder patch in patches) {
patch.checkVariance(sourceClassBuilder, typeEnvironment);
}
}
}
@override
void checkTypes(
SourceLibraryBuilder library, TypeEnvironment typeEnvironment) {
library.checkTypesInFunctionBuilder(this, typeEnvironment);
List<SourceProcedureBuilder>? patches = _patches;
if (patches != null) {
for (SourceProcedureBuilder patch in patches) {
patch.checkTypes(library, typeEnvironment);
}
}
}
}
class SourceProcedureMember extends BuilderClassMember {
@override
final SourceProcedureBuilder memberBuilder;
Covariance? _covariance;
SourceProcedureMember(this.memberBuilder);
@override
bool get isSourceDeclaration => true;
@override
void inferType(ClassMembersBuilder membersBuilder) {
memberBuilder._ensureTypes(membersBuilder);
}
@override
void registerOverrideDependency(Set<ClassMember> overriddenMembers) {
memberBuilder.registerOverrideDependency(overriddenMembers);
}
@override
Member getMember(ClassMembersBuilder membersBuilder) {
memberBuilder._ensureTypes(membersBuilder);
return memberBuilder.member;
}
@override
Covariance getCovariance(ClassMembersBuilder membersBuilder) {
return _covariance ??= new Covariance.fromMember(getMember(membersBuilder),
forSetter: forSetter);
}
@override
bool get forSetter => isSetter;
@override
bool get isProperty =>
memberBuilder.kind == ProcedureKind.Getter ||
memberBuilder.kind == ProcedureKind.Setter;
@override
bool isSameDeclaration(ClassMember other) {
return other is SourceProcedureMember &&
memberBuilder == other.memberBuilder;
}
}