blob: dcd544e13b239526a4b45fe16495978c03508e90 [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/core_types.dart';
import 'package:kernel/names.dart';
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/transformations/flags.dart' show TransformerFlag;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import "../base/problems.dart" show unhandled;
import '../builder/declaration_builders.dart';
import '../source/source_library_builder.dart';
import 'combined_member_signature.dart';
import 'hierarchy/class_member.dart';
import 'kernel_target.dart';
class ForwardingNode {
/// The source library containing the [declarationBuilder].
final SourceLibraryBuilder libraryBuilder;
/// The class or extension type declaration builder for which the
/// [_combinedMemberSignature] is computed.
final DeclarationBuilder declarationBuilder;
/// The class or extension type declaration node in which a stub is inserted.
final TypeDeclaration typeDeclaration;
/// The [IndexedContainer] for the [declarationBuilder].
final IndexedContainer? indexedContainer;
/// The combined member signature for all interface members implemented
/// by the, possibly synthesized, member for which this [ForwardingNode] was
/// created.
final CombinedMemberSignatureBase _combinedMemberSignature;
final ProcedureKind kind;
/// The concrete member inherited from a superclass, if any.
final ClassMember? _superClassMember;
/// The member inherited from a mixin, if any.
final ClassMember? _mixedInMember;
/// The target `noSuchMethod` implementation for a noSuchMethod.
///
/// If provided, a noSuchMethod forwarder must be created, unless the
/// [_superClassMember] is a valid implementation of the interface.
final ClassMember? _noSuchMethodTarget;
final bool _declarationIsMixinApplication;
ForwardingNode(
this.libraryBuilder,
this.declarationBuilder,
this.typeDeclaration,
this.indexedContainer,
this._combinedMemberSignature,
this.kind,
{ClassMember? superClassMember,
ClassMember? mixedInMember,
ClassMember? noSuchMethodTarget,
required bool declarationIsMixinApplication})
: this._superClassMember = superClassMember,
this._mixedInMember = mixedInMember,
this._noSuchMethodTarget = noSuchMethodTarget,
this._declarationIsMixinApplication = declarationIsMixinApplication;
/// Finishes handling of this node by propagating covariance and creating
/// forwarding stubs, mixin stubs, member signature or noSuchMethod forwarders
/// if necessary.
///
/// If a new member is created, this is returned. Otherwise `null` is
/// returned.
Procedure? finalize() {
ClassMember canonicalMember = _combinedMemberSignature.canonicalMember!;
Member interfaceMember =
canonicalMember.getMember(_combinedMemberSignature.membersBuilder);
// If the class is a mixin application and the member is declared in the
// mixin, we insert a mixin stub for the member:
//
// class Super { void superMethod() {} }
// mixin Mixin { void mixinMethod() {} }
// class NamedMixinApplication = Object with Mixin /*
// mixin-stub mixinMethod(); // mixin in stub
// */;
bool needMixinStub =
_declarationIsMixinApplication && _mixedInMember != null;
// If [_noSuchMethodTarget] is provided, a noSuchMethod forwarder must
// be created, unless the [_superClassTarget] is a valid implementation.
bool hasNoSuchMethodTarget = _noSuchMethodTarget != null;
if (_combinedMemberSignature.members.length == 1 &&
!needMixinStub &&
!hasNoSuchMethodTarget) {
// Coverage-ignore-block(suite): Not run.
// Optimization: Avoid complex computation for simple scenarios.
// Covariance can only come from [interfaceMember] so we never need a
// forwarding stub.
if (_combinedMemberSignature.neededLegacyErasure) {
return _combinedMemberSignature.createMemberFromSignature(
typeDeclaration, indexedContainer,
// TODO(johnniwinther): Change member signatures to use location
// of origin.
copyLocation: false);
} else {
// Nothing to do.
return null;
}
}
// TODO(johnniwinther): Remove this. This relies upon the order of the
// declarations matching the order in which members are returned from the
// [ClassHierarchy].
bool cannotReuseExistingMember =
!(_combinedMemberSignature.isCanonicalMemberFirst ||
_combinedMemberSignature.isCanonicalMemberDeclared);
bool needsTypeOrCovarianceUpdate =
_combinedMemberSignature.neededNnbdTopMerge ||
_combinedMemberSignature.neededLegacyErasure ||
_combinedMemberSignature.needsCovarianceMerging;
bool needsSuperImpl = false;
Member? superTarget;
bool hasValidImplementation = false;
if (_superClassMember != null) {
superTarget =
_superClassMember.getMember(_combinedMemberSignature.membersBuilder);
if (superTarget is Procedure &&
interfaceMember is Procedure &&
(superTarget.function.positionalParameters.length <
interfaceMember.function.positionalParameters.length ||
superTarget.function.namedParameters.length <
interfaceMember.function.namedParameters.length)) {
// [superTarget] is not a valid implementation for [interfaceMember]
// since [interfaceMember] has more parameters than [superTarget].
//
// For instance
//
// class A {
// void method() {}
// }
// abstract class B<T> extends A {
// void method({T? a});
// }
//
// Any concrete implementation of B must provide its own implementation
// of `B.method` and cannot forward to `A.method`.
} else {
if (hasNoSuchMethodTarget) {
// A noSuchMethod forwarder is needed it the type of the super member
// differs from the interface member.
//
// For instance
//
// class Super {
// noSuchMethod(_) => null;
// method1(); // noSuchMethod forwarder created for this.
// method2(int i); // noSuchMethod forwarder created for this.
// }
// class Class extends Super {
// method1(); // noSuchMethod forwarder from Super is valid.
// method2(num i); // A new noSuchMethod forwarder is needed.
// }
//
DartType superTargetType =
_combinedMemberSignature.getMemberTypeForTarget(superTarget);
DartType interfaceMemberType =
_combinedMemberSignature.getMemberTypeForTarget(interfaceMember);
hasValidImplementation = superTargetType == interfaceMemberType;
} else {
// [superTarget] is a valid implementation for [interfaceMember] so
// we need to add concrete forwarding stub of the variances differ.
needsSuperImpl = _superClassMember
.getCovariance(_combinedMemberSignature.membersBuilder) !=
_combinedMemberSignature.combinedMemberSignatureCovariance;
hasValidImplementation = true;
}
}
}
bool needsNoSuchMethodForwarder =
hasNoSuchMethodTarget && !hasValidImplementation;
bool stubNeeded = cannotReuseExistingMember ||
(canonicalMember.declarationBuilder != declarationBuilder &&
(needsTypeOrCovarianceUpdate || needsNoSuchMethodForwarder)) ||
needMixinStub;
if (stubNeeded) {
Procedure stub = _combinedMemberSignature.createMemberFromSignature(
typeDeclaration, indexedContainer,
copyLocation: false)!;
bool needsForwardingStub =
_combinedMemberSignature.needsCovarianceMerging || needsSuperImpl;
if (needsForwardingStub || needMixinStub || needsNoSuchMethodForwarder) {
ProcedureStubKind stubKind;
Member? finalTarget;
if (needsNoSuchMethodForwarder) {
stubKind = ProcedureStubKind.NoSuchMethodForwarder;
finalTarget = null;
} else if (needsForwardingStub) {
stubKind = ProcedureStubKind.AbstractForwardingStub;
if (interfaceMember is Procedure) {
switch (interfaceMember.stubKind) {
case ProcedureStubKind.Regular:
case ProcedureStubKind.NoSuchMethodForwarder:
finalTarget = interfaceMember;
break;
case ProcedureStubKind.AbstractForwardingStub:
case ProcedureStubKind.ConcreteForwardingStub:
case ProcedureStubKind.MemberSignature:
case ProcedureStubKind.AbstractMixinStub:
case ProcedureStubKind.ConcreteMixinStub:
finalTarget = interfaceMember.stubTarget!;
break;
// Coverage-ignore(suite): Not run.
case ProcedureStubKind.RepresentationField:
assert(
false,
"Unexpected representation field as forwarding stub target "
"$interfaceMember.");
finalTarget = interfaceMember;
break;
}
} else {
finalTarget = interfaceMember;
}
} else {
stubKind = ProcedureStubKind.AbstractMixinStub;
finalTarget = _mixedInMember!
.getMember(_combinedMemberSignature.membersBuilder);
}
stub.stubKind = stubKind;
stub.stubTarget = finalTarget;
if (needsNoSuchMethodForwarder) {
_createNoSuchMethodForwarder(
_noSuchMethodTarget.getMember(
_combinedMemberSignature.membersBuilder) as Procedure,
stub);
} else if (needsSuperImpl ||
(needMixinStub && _superClassMember == _mixedInMember)) {
_createForwardingImplIfNeeded(stub.function, stub.name, superTarget,
isForwardingStub: needsForwardingStub);
}
}
return stub;
} else {
if (_combinedMemberSignature.needsCovarianceMerging) {
// Coverage-ignore-block(suite): Not run.
_combinedMemberSignature.combinedMemberSignatureCovariance!
.applyCovariance(interfaceMember);
}
if (needsNoSuchMethodForwarder) {
assert(interfaceMember is Procedure,
"Unexpected abstract member: ${interfaceMember}");
(interfaceMember as Procedure).stubKind =
ProcedureStubKind.NoSuchMethodForwarder;
interfaceMember.stubTarget = null;
_createNoSuchMethodForwarder(
_noSuchMethodTarget.getMember(
_combinedMemberSignature.membersBuilder) as Procedure,
interfaceMember);
} else if (needsSuperImpl) {
_createForwardingImplIfNeeded(
interfaceMember.function!, interfaceMember.name, superTarget,
isForwardingStub: true);
}
return null;
}
}
void _createForwardingImplIfNeeded(
FunctionNode function, Name name, Member? superTarget,
{required bool isForwardingStub}) {
if (function.body != null) {
// There is already an implementation; nothing further needs to be done.
return;
}
// If there is no concrete implementation in the superclass, then the method
// is fully abstract and we don't need to do anything.
if (superTarget == null) {
return;
}
Procedure procedure = function.parent as Procedure;
if (superTarget is Procedure && superTarget.isForwardingStub) {
Procedure superProcedure = superTarget;
superTarget = superProcedure.concreteForwardingStubTarget!;
} else {
superTarget = superTarget.memberSignatureOrigin ?? superTarget;
}
procedure.isAbstract = false;
FunctionType signatureType = procedure.function
.computeFunctionType(procedure.enclosingLibrary.nonNullable);
bool isForwardingSemiStub = isForwardingStub && !procedure.isSynthetic;
bool needsSignatureType = false;
Expression superCall;
assert(
!superTarget.isAbstract,
"Abstract super target $superTarget found for '${name}' in "
"${typeDeclaration}.");
switch (kind) {
case ProcedureKind.Method:
case ProcedureKind.Operator:
FunctionType type = _combinedMemberSignature
.getMemberTypeForTarget(superTarget) as FunctionType;
if (type.typeParameters.isNotEmpty) {
type = FunctionTypeInstantiator.instantiate(
type,
function.typeParameters
.map((TypeParameter parameter) =>
new TypeParameterType.withDefaultNullability(parameter))
.toList());
}
List<Expression> positionalArguments = new List.generate(
function.positionalParameters.length, (int index) {
VariableDeclaration parameter = function.positionalParameters[index];
int fileOffset = parameter.fileOffset;
Expression expression = new VariableGet(parameter)
..fileOffset = fileOffset;
DartType superParameterType = type.positionalParameters[index];
if (isForwardingSemiStub) {
if (parameter.type != superParameterType) {
parameter.type = superParameterType;
needsSignatureType = true;
}
} else {
if (!_combinedMemberSignature.hierarchy.types.isSubtypeOf(
parameter.type,
superParameterType,
SubtypeCheckMode.withNullabilities)) {
expression = new AsExpression(expression, superParameterType)
..fileOffset = fileOffset;
}
}
return expression;
}, growable: true);
List<NamedExpression> namedArguments =
new List.generate(function.namedParameters.length, (int index) {
VariableDeclaration parameter = function.namedParameters[index];
int fileOffset = parameter.fileOffset;
Expression expression = new VariableGet(parameter)
..fileOffset = fileOffset;
DartType superParameterType = type.namedParameters
.singleWhere(
(NamedType namedType) => namedType.name == parameter.name)
.type;
if (isForwardingSemiStub) {
if (parameter.type != superParameterType) {
parameter.type = superParameterType;
needsSignatureType = true;
}
} else {
if (!_combinedMemberSignature.hierarchy.types.isSubtypeOf(
parameter.type,
superParameterType,
SubtypeCheckMode.withNullabilities)) {
expression = new AsExpression(expression, superParameterType)
..fileOffset = fileOffset;
}
}
return new NamedExpression(parameter.name!, expression);
}, growable: true);
List<DartType> typeArguments = function.typeParameters
.map<DartType>((typeParameter) =>
new TypeParameterType.withDefaultNullability(typeParameter))
.toList();
Arguments arguments = new Arguments(positionalArguments,
types: typeArguments, named: namedArguments);
superCall = new SuperMethodInvocation(
name, arguments, superTarget as Procedure);
break;
case ProcedureKind.Getter:
superCall = new SuperPropertyGet(name, superTarget);
break;
case ProcedureKind.Setter:
DartType superParameterType =
_combinedMemberSignature.getMemberTypeForTarget(superTarget);
VariableDeclaration parameter = function.positionalParameters[0];
int fileOffset = parameter.fileOffset;
Expression expression = new VariableGet(parameter)
..fileOffset = fileOffset;
if (isForwardingSemiStub) {
if (parameter.type != superParameterType) {
parameter.type = superParameterType;
needsSignatureType = true;
}
} else {
if (!_combinedMemberSignature.hierarchy.types.isSubtypeOf(
parameter.type,
superParameterType,
SubtypeCheckMode.withNullabilities)) {
expression = new AsExpression(expression, superParameterType)
..fileOffset = fileOffset;
}
}
superCall = new SuperPropertySet(name, expression, superTarget);
break;
// Coverage-ignore(suite): Not run.
default:
unhandled('$kind', '_createForwardingImplIfNeeded', -1, null);
}
function.body = new ReturnStatement(superCall)
..fileOffset = procedure.fileOffset
..parent = function;
procedure.transformerFlags |= TransformerFlag.superCalls;
procedure.stubKind = isForwardingStub
? ProcedureStubKind.ConcreteForwardingStub
: ProcedureStubKind.ConcreteMixinStub;
procedure.stubTarget = superTarget;
if (needsSignatureType) {
procedure.signatureType = signatureType;
}
}
void _createNoSuchMethodForwarder(
Procedure noSuchMethodInterface, Procedure procedure) {
bool shouldThrow = false;
Name procedureName = procedure.name;
if (procedureName.isPrivate) {
Library procedureNameLibrary = procedureName.library!;
// If the name is defined in a different library than the library we're
// synthesizing a forwarder for, then the forwarder must throw. This
// avoids surprising users by ensuring that all non-throwing
// implementations of a private name can be found solely by looking at the
// library in which the name is defined; it also avoids soundness holes in
// field promotion.
if (procedureNameLibrary.compareTo(procedure.enclosingLibrary) != 0) {
shouldThrow = true;
}
}
Expression result;
String prefix = procedure.isGetter
? 'get:'
: procedure.isSetter
? 'set:'
: '';
String invocationName = prefix + procedureName.text;
if (procedure.isSetter) invocationName += '=';
KernelTarget target = libraryBuilder.loader.target;
CoreTypes coreTypes = target.loader.coreTypes;
Expression invocation = target.backendTarget.instantiateInvocation(
coreTypes,
new ThisExpression(),
invocationName,
new Arguments.forwarded(procedure.function, libraryBuilder.library),
procedure.fileOffset,
/*isSuper=*/ false);
if (shouldThrow) {
// Build `throw new NoSuchMethodError(this, invocation)`.
result = new Throw(new StaticInvocation(
coreTypes.noSuchMethodErrorDefaultConstructor,
new Arguments([new ThisExpression(), invocation])))
..fileOffset = procedure.fileOffset
..forErrorHandling = true;
} else {
// Build `this.noSuchMethod(invocation)`.
result = new InstanceInvocation(InstanceAccessKind.Instance,
new ThisExpression(), noSuchMethodName, new Arguments([invocation]),
functionType: noSuchMethodInterface.getterType as FunctionType,
interfaceTarget: noSuchMethodInterface)
..fileOffset = procedure.fileOffset;
if (procedure.function.returnType is! VoidType) {
result = new AsExpression(result, procedure.function.returnType)
..isTypeError = true
..isForDynamic = true
..fileOffset = procedure.fileOffset;
}
}
FunctionType signatureType = procedure.function
.computeFunctionType(procedure.enclosingLibrary.nonNullable);
List<VariableDeclaration> positionalParameters =
procedure.function.positionalParameters;
List<VariableDeclaration> namedParameters =
procedure.function.namedParameters;
int requiredParameterCount = procedure.function.requiredParameterCount;
bool hasUpdate = false;
bool updateNullability(VariableDeclaration parameter,
{required bool isRequired}) {
// Parameters in nnbd libraries that backends might not be able to pass
// a non-null value for must be nullable. This allows backends to do the
// appropriate parameter checks in the forwarder stub for null placeholder
// arguments. Covariance indicates the type must stay the same.
return !(isRequired ||
parameter.hasDeclaredInitializer ||
parameter.isCovariantByDeclaration ||
parameter.isCovariantByClass) &&
parameter.type.nullability != Nullability.nullable;
}
for (int i = 0; i < positionalParameters.length; i++) {
VariableDeclaration parameter = positionalParameters[i];
bool isRequired = i < requiredParameterCount;
if (updateNullability(parameter, isRequired: isRequired)) {
parameter.type =
parameter.type.withDeclaredNullability(Nullability.nullable);
hasUpdate = true;
}
}
for (VariableDeclaration parameter in namedParameters) {
if (updateNullability(parameter, isRequired: parameter.isRequired)) {
parameter.type =
parameter.type.withDeclaredNullability(Nullability.nullable);
hasUpdate = true;
}
}
if (hasUpdate) {
procedure.signatureType = signatureType;
}
procedure.function.body = new ReturnStatement(result)
..fileOffset = procedure.fileOffset
..parent = procedure.function;
procedure.function.asyncMarker = AsyncMarker.Sync;
procedure.function.dartAsyncMarker = AsyncMarker.Sync;
procedure.isAbstract = false;
procedure.stubKind = ProcedureStubKind.NoSuchMethodForwarder;
procedure.stubTarget = null;
}
}