blob: 9e6978daad6ced22b72d687087b4d5d8f4f15556 [file] [log] [blame]
// Copyright (c) 2017, 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.md file.
import 'package:front_end/src/base/instrumentation.dart';
import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart';
import 'package:front_end/src/fasta/problems.dart';
import 'package:front_end/src/fasta/type_inference/type_inference_engine.dart';
import 'package:front_end/src/fasta/type_inference/type_inferrer.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
/// Type of a closure which applies a covariance annotation to a class member.
///
/// This is necessary since we need to determine which covariance annotations
/// need to be added before creating a forwarding stub, but the covariance
/// annotations themselves need to be applied to the forwarding stub.
typedef void _CovarianceFix(FunctionNode function);
/// A [ForwardingNode] represents a method, getter, or setter within a class's
/// interface that is either implemented in the class directly or inherited from
/// a superclass.
///
/// This class allows us to defer the determination of exactly which member is
/// inherited, as well as the propagation of covariance annotations, and
/// the creation of forwarding stubs, until type inference.
class ForwardingNode extends Procedure {
/// The [InterfaceResolver] that created this [ForwardingNode].
final InterfaceResolver _interfaceResolver;
/// A list containing the directly implemented and directly inherited
/// procedures of the class in question.
///
/// Note that many [ForwardingNode]s share the same [_candidates] list;
/// consult [_start] and [_end] to see which entries in this list are relevant
/// to this [ForwardingNode].
final List<Procedure> _candidates;
/// Indicates whether this forwarding node is for a setter.
final bool _setter;
/// Index of the first entry in [_candidates] relevant to this
/// [ForwardingNode].
final int _start;
/// Index just beyond the last entry in [_candidates] relevant to this
/// [ForwardingNode].
final int _end;
/// The member this node resolves to (if it has been computed); otherwise
/// `null`.
Member _resolution;
ForwardingNode(
this._interfaceResolver,
Class class_,
Name name,
ProcedureKind kind,
this._candidates,
this._setter,
this._start,
this._end)
: super(name, kind, null) {
parent = class_;
}
/// Returns the inherited member, or the forwarding stub, which this node
/// resolves to.
Member resolve() => _resolution ??= _resolve();
/// Determines which covariance fixes need to be applied to the given
/// [interfaceMember].
///
/// [substitution] indicates the necessary substitutions to convert types
/// named in [interfaceMember] to types in the target class.
///
/// The fixes are not applied immediately (since [interfaceMember] might be
/// a member of another class, and a forwarding stub may need to be
/// generated).
void _computeCovarianceFixes(Substitution substitution,
Procedure interfaceMember, List<_CovarianceFix> fixes) {
var class_ = enclosingClass;
var interfaceFunction = interfaceMember.function;
var interfacePositionalParameters = interfaceFunction.positionalParameters;
var interfaceNamedParameters = interfaceFunction.namedParameters;
var interfaceTypeParameters = interfaceFunction.typeParameters;
bool isImplCreated = false;
void createImplIfNeeded() {
if (isImplCreated) return;
fixes.add(_createForwardingImplIfNeeded);
isImplCreated = true;
}
if (class_.typeParameters.isNotEmpty) {
IncludesTypeParametersCovariantly needsCheckVisitor =
ShadowClass.getClassInferenceInfo(class_).needsCheckVisitor ??=
new IncludesTypeParametersCovariantly(class_.typeParameters);
bool needsCheck(DartType type) =>
substitution.substituteType(type).accept(needsCheckVisitor);
for (int i = 0; i < interfacePositionalParameters.length; i++) {
var parameter = interfacePositionalParameters[i];
var isCovariant = needsCheck(parameter.type);
if (isCovariant != parameter.isGenericCovariantInterface) {
fixes.add((FunctionNode function) => function.positionalParameters[i]
.isGenericCovariantInterface = isCovariant);
}
if (isCovariant != parameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) => function
.positionalParameters[i].isGenericCovariantImpl = isCovariant);
}
}
for (int i = 0; i < interfaceNamedParameters.length; i++) {
var parameter = interfaceNamedParameters[i];
var isCovariant = needsCheck(parameter.type);
if (isCovariant != parameter.isGenericCovariantInterface) {
fixes.add((FunctionNode function) => function
.namedParameters[i].isGenericCovariantInterface = isCovariant);
}
if (isCovariant != parameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.namedParameters[i].isGenericCovariantImpl = isCovariant);
}
}
for (int i = 0; i < interfaceTypeParameters.length; i++) {
var typeParameter = interfaceTypeParameters[i];
var isCovariant = needsCheck(typeParameter.bound);
if (isCovariant != typeParameter.isGenericCovariantInterface) {
fixes.add((FunctionNode function) => function
.typeParameters[i].isGenericCovariantInterface = isCovariant);
}
if (isCovariant != typeParameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.typeParameters[i].isGenericCovariantImpl = isCovariant);
}
}
}
for (int i = _start; i < _end; i++) {
var otherMember = _candidates[i];
if (identical(otherMember, interfaceMember)) continue;
var otherFunction = otherMember.function;
var otherPositionalParameters = otherFunction.positionalParameters;
for (int j = 0;
j < interfacePositionalParameters.length &&
j < otherPositionalParameters.length;
j++) {
var parameter = interfacePositionalParameters[j];
var otherParameter = otherPositionalParameters[j];
if (otherParameter.isGenericCovariantImpl &&
!parameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.positionalParameters[j].isGenericCovariantImpl = true);
}
if (otherParameter.isCovariant && !parameter.isCovariant) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.positionalParameters[j].isCovariant = true);
}
}
for (int j = 0; j < interfaceNamedParameters.length; j++) {
var parameter = interfaceNamedParameters[j];
var otherParameter = getNamedFormal(otherFunction, parameter.name);
if (otherParameter != null) {
if (otherParameter.isGenericCovariantImpl &&
!parameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.namedParameters[j].isGenericCovariantImpl = true);
}
if (otherParameter.isCovariant && !parameter.isCovariant) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.namedParameters[j].isCovariant = true);
}
}
}
var otherTypeParameters = otherFunction.typeParameters;
for (int j = 0;
j < interfaceTypeParameters.length && j < otherTypeParameters.length;
j++) {
var typeParameter = interfaceTypeParameters[j];
var otherTypeParameter = otherTypeParameters[j];
if (otherTypeParameter.isGenericCovariantImpl &&
!typeParameter.isGenericCovariantImpl) {
createImplIfNeeded();
fixes.add((FunctionNode function) =>
function.typeParameters[j].isGenericCovariantImpl = true);
}
}
}
}
void _createForwardingImplIfNeeded(FunctionNode function) {
if (function.body != null) {
// There is already an implementation; nothing further needs to be done.
return;
}
// Find the concrete implementation in the superclass; this is what we need
// to forward to. If we can't find one, then the method is fully abstract
// and we don't need to do anything.
var superclass = enclosingClass.superclass;
if (superclass == null) return;
Procedure procedure = function.parent;
var superTarget = _interfaceResolver._typeEnvironment.hierarchy
.getDispatchTarget(superclass, procedure.name,
setter: kind == ProcedureKind.Setter);
if (superTarget == null) return;
procedure.isAbstract = false;
var positionalArguments = function.positionalParameters
.map<Expression>((parameter) => new VariableGet(parameter))
.toList();
var namedArguments = function.namedParameters
.map((parameter) =>
new NamedExpression(parameter.name, new VariableGet(parameter)))
.toList();
var typeArguments = function.typeParameters
.map<DartType>((typeParameter) => new TypeParameterType(typeParameter))
.toList();
var arguments = new Arguments(positionalArguments,
types: typeArguments, named: namedArguments);
Expression superCall;
switch (kind) {
case ProcedureKind.Method:
case ProcedureKind.Operator:
superCall = new SuperMethodInvocation(name, arguments, superTarget);
break;
case ProcedureKind.Getter:
superCall = new SuperPropertyGet(
name,
superTarget is SyntheticAccessor
? superTarget._field
: superTarget);
break;
case ProcedureKind.Setter:
superCall = new SuperPropertySet(
name,
positionalArguments[0],
superTarget is SyntheticAccessor
? superTarget._field
: superTarget);
break;
default:
unhandled('$kind', '_createForwardingImplIfNeeded', -1, null);
break;
}
function.body = new ReturnStatement(superCall);
}
/// Creates a forwarding stub based on the given [target].
Procedure _createForwardingStub(Substitution substitution, Procedure target) {
VariableDeclaration copyParameter(VariableDeclaration parameter) {
return new VariableDeclaration(parameter.name,
type: substitution.substituteType(parameter.type));
}
var targetTypeParameters = target.function.typeParameters;
List<TypeParameter> typeParameters;
if (targetTypeParameters.isNotEmpty) {
typeParameters =
new List<TypeParameter>.filled(targetTypeParameters.length, null);
var additionalSubstitution = <TypeParameter, DartType>{};
for (int i = 0; i < targetTypeParameters.length; i++) {
var targetTypeParameter = targetTypeParameters[i];
var typeParameter = new TypeParameter(targetTypeParameter.name, null);
typeParameters[i] = typeParameter;
additionalSubstitution[targetTypeParameter] =
new TypeParameterType(typeParameter);
}
substitution = Substitution.combine(
substitution, Substitution.fromMap(additionalSubstitution));
for (int i = 0; i < typeParameters.length; i++) {
typeParameters[i].bound =
substitution.substituteType(targetTypeParameters[i].bound);
}
}
var positionalParameters =
target.function.positionalParameters.map(copyParameter).toList();
var namedParameters =
target.function.namedParameters.map(copyParameter).toList();
var function = new FunctionNode(null,
positionalParameters: positionalParameters,
namedParameters: namedParameters,
typeParameters: typeParameters,
requiredParameterCount: target.function.requiredParameterCount,
returnType: substitution.substituteType(target.function.returnType));
return new Procedure(name, kind, function,
isAbstract: true, isForwardingStub: true);
}
/// Determines which inherited member this node resolves to.
Member _resolve() {
var inheritedMember = _candidates[_start];
var inheritedMemberSubstitution = Substitution.empty;
bool isDeclaredInThisClass =
identical(inheritedMember.enclosingClass, enclosingClass);
if (!isDeclaredInThisClass) {
// If there are multiple inheritance candidates, the inherited member is
// the member whose type is a subtype of all the others. We can find it
// by two passes over the list of members. For the first pass, we step
// through the candidates, updating inheritedMember each time we find a
// member whose type is a subtype of the previous inheritedMember. As we
// do this, we also work out the necessary substitution for matching up
// type parameters between this class and the corresponding superclass.
//
// Since the subtyping relation is reflexive, we will favor the most
// recently visited candidate in the case where the types are the same.
// We want to favor earlier candidates, so we visit the candidate list
// backwards.
inheritedMember = _candidates[_end - 1];
inheritedMemberSubstitution = _substitutionFor(inheritedMember);
var inheritedMemberType = inheritedMemberSubstitution.substituteType(
_setter ? inheritedMember.setterType : inheritedMember.getterType);
for (int i = _end - 2; i >= _start; i--) {
var candidate = _candidates[i];
var substitution = _substitutionFor(candidate);
bool isBetter;
DartType type;
if (_setter) {
type = substitution.substituteType(candidate.setterType);
// Setters are contravariant in their setter type, so we have to
// reverse the check.
isBetter = _interfaceResolver._typeEnvironment
.isSubtypeOf(inheritedMemberType, type);
} else {
type = substitution.substituteType(candidate.getterType);
isBetter = _interfaceResolver._typeEnvironment
.isSubtypeOf(type, inheritedMemberType);
}
if (isBetter) {
inheritedMember = candidate;
inheritedMemberSubstitution = substitution;
inheritedMemberType = type;
}
}
// For the second pass, we verify that inheritedMember is a subtype of all
// the other potentially inherited members.
// TODO(paulberry): implement this.
}
// Now decide whether we need a forwarding stub or not, and propagate
// covariance.
var covarianceFixes = <_CovarianceFix>[];
if (_interfaceResolver.strongMode) {
_computeCovarianceFixes(
inheritedMemberSubstitution, inheritedMember, covarianceFixes);
}
if (!isDeclaredInThisClass &&
(!identical(inheritedMember, _candidates[_start]) ||
covarianceFixes.isNotEmpty)) {
var stub =
_createForwardingStub(inheritedMemberSubstitution, inheritedMember);
var function = stub.function;
for (var fix in covarianceFixes) {
fix(function);
}
return stub;
} else {
var function = inheritedMember.function;
for (var fix in covarianceFixes) {
fix(function);
}
if (inheritedMember is SyntheticAccessor) {
var field = inheritedMember._field;
if (inheritedMember.kind == ProcedureKind.Setter) {
// Propagate covariance fixes to the field.
var setterParameter = function.positionalParameters[0];
field.isCovariant = setterParameter.isCovariant;
field.isGenericCovariantInterface =
setterParameter.isGenericCovariantInterface;
field.isGenericCovariantImpl = setterParameter.isGenericCovariantImpl;
}
return field;
} else {
return inheritedMember;
}
}
}
/// Determines the appropriate substitution to translate type parameters
/// mentioned in the given [candidate] to type parameters on the parent class.
Substitution _substitutionFor(Procedure candidate) {
return Substitution.fromInterfaceType(
_interfaceResolver._typeEnvironment.hierarchy.getTypeAsInstanceOf(
enclosingClass.thisType, candidate.enclosingClass));
}
static void createForwardingImplIfNeededForTesting(
ForwardingNode node, FunctionNode function) {
node._createForwardingImplIfNeeded(function);
}
/// Public method allowing tests to access [_createForwardingStub].
///
/// This method is static so that it can be easily eliminated by tree shaking
/// when not needed.
static Procedure createForwardingStubForTesting(
ForwardingNode node, Substitution substitution, Procedure target) {
return node._createForwardingStub(substitution, target);
}
/// For testing: get the list of candidates relevant to a given node.
static List<Procedure> getCandidates(ForwardingNode node) {
return node._candidates.sublist(node._start, node._end);
}
}
/// An [InterfaceResolver] keeps track of the information necessary to resolve
/// method calls, gets, and sets within a chunk of code being compiled, to
/// infer covariance annotations, and to create forwarwding stubs when necessary
/// to meet covariance requirements.
class InterfaceResolver {
final TypeEnvironment _typeEnvironment;
final Instrumentation _instrumentation;
final bool strongMode;
InterfaceResolver(
this._typeEnvironment, this._instrumentation, this.strongMode);
/// Populates [forwardingNodes] with a list of the implemented and inherited
/// members of the given [class_]'s interface.
///
/// Each member of the class's interface is represented by a [ForwardingNode]
/// object.
///
/// If [setters] is `true`, the list will be populated by setters; otherwise
/// it will be populated by getters and methods.
void createForwardingNodes(
Class class_, List<ForwardingNode> forwardingNodes, bool setters) {
// First create a list of candidates for inheritance based on the members
// declared directly in the class.
List<Procedure> candidates = _typeEnvironment.hierarchy
.getDeclaredMembers(class_, setters: setters)
.map((member) => makeCandidate(member, setters))
.toList();
// Merge in candidates from superclasses.
if (class_.superclass != null) {
candidates = _mergeCandidates(candidates, class_.superclass, setters);
}
for (var supertype in class_.implementedTypes) {
candidates = _mergeCandidates(candidates, supertype.classNode, setters);
}
// Now create a forwarding node for each unique name.
forwardingNodes.length = candidates.length;
int storeIndex = 0;
int i = 0;
while (i < candidates.length) {
var name = candidates[i].name;
int j = i + 1;
while (j < candidates.length && candidates[j].name == name) {
j++;
}
forwardingNodes[storeIndex++] = new ForwardingNode(
this, class_, name, candidates[i].kind, candidates, setters, i, j);
i = j;
}
forwardingNodes.length = storeIndex;
}
void finalizeCovariance(Class class_, List<ForwardingNode> forwardingNodes) {
for (var node in forwardingNodes) {
var resolution = node.resolve();
if (resolution is Procedure && resolution.isForwardingStub) {
// TODO(paulberry): store the stub in the class.
_instrumentation?.record(
Uri.parse(class_.location.file),
class_.fileOffset,
'forwardingStub',
new InstrumentationValueForForwardingStub(resolution));
}
}
}
/// If instrumentation is enabled, records the covariance bits for the given
/// [class_] to [_instrumentation].
void recordInstrumentation(Class class_) {
if (_instrumentation != null) {
_recordInstrumentation(class_);
}
}
/// Retrieves a list of the interface members of the given [class_].
///
/// If [setters] is true, setters are retrieved; otherwise getters and methods
/// are retrieved.
List<Member> _getInterfaceMembers(Class class_, bool setters) {
// TODO(paulberry): if class_ is being compiled from source, retrieve its
// forwarding nodes.
return _typeEnvironment.hierarchy
.getInterfaceMembers(class_, setters: setters);
}
/// Merges together the list of interface inheritance candidates in
/// [candidates] with interface inheritance candidates from superclass
/// [class_].
///
/// Any candidates from [class_] are converted into interface inheritance
/// candidates using [_makeCandidate].
List<Procedure> _mergeCandidates(
List<Procedure> candidates, Class class_, bool setters) {
List<Member> members = _getInterfaceMembers(class_, setters);
if (candidates.isEmpty) {
return members.map((member) => makeCandidate(member, setters)).toList();
}
if (members.isEmpty) return candidates;
List<Procedure> result = <Procedure>[]..length =
candidates.length + members.length;
int storeIndex = 0;
int i = 0, j = 0;
while (i < candidates.length && j < members.length) {
Procedure candidate = candidates[i];
Member member = members[j];
int compare = ClassHierarchy.compareMembers(candidate, member);
if (compare <= 0) {
result[storeIndex++] = candidate;
++i;
// If the same member occurs in both lists, skip the duplicate.
if (identical(candidate, member)) ++j;
} else {
result[storeIndex++] = makeCandidate(member, setters);
++j;
}
}
while (i < candidates.length) {
result[storeIndex++] = candidates[i++];
}
while (j < members.length) {
result[storeIndex++] = makeCandidate(members[j++], setters);
}
result.length = storeIndex;
return result;
}
/// Records the covariance bits for the given [class_] to [_instrumentation].
///
/// Caller is responsible for checking whether [_instrumentation] is `null`.
void _recordInstrumentation(Class class_) {
var uri = Uri.parse(class_.fileUri);
void recordCovariance(int fileOffset, bool isExplicitlyCovariant,
bool isGenericCovariantInterface, bool isGenericCovariantImpl) {
var covariance = <String>[];
if (isExplicitlyCovariant) covariance.add('explicit');
if (isGenericCovariantInterface) covariance.add('genericInterface');
if (!isExplicitlyCovariant && isGenericCovariantImpl) {
covariance.add('genericImpl');
}
if (covariance.isNotEmpty) {
_instrumentation.record(uri, fileOffset, 'covariance',
new InstrumentationValueLiteral(covariance.join(', ')));
}
}
for (var procedure in class_.procedures) {
if (procedure.isStatic) continue;
void recordFormalAnnotations(VariableDeclaration formal) {
recordCovariance(formal.fileOffset, formal.isCovariant,
formal.isGenericCovariantInterface, formal.isGenericCovariantImpl);
}
void recordTypeParameterAnnotations(TypeParameter typeParameter) {
recordCovariance(
typeParameter.fileOffset,
false,
typeParameter.isGenericCovariantInterface,
typeParameter.isGenericCovariantImpl);
}
procedure.function.positionalParameters.forEach(recordFormalAnnotations);
procedure.function.namedParameters.forEach(recordFormalAnnotations);
procedure.function.typeParameters.forEach(recordTypeParameterAnnotations);
}
for (var field in class_.fields) {
if (field.isStatic) continue;
recordCovariance(field.fileOffset, field.isCovariant,
field.isGenericCovariantInterface, field.isGenericCovariantImpl);
}
}
/// Transforms [member] into a candidate for interface inheritance.
///
/// Fields are transformed into getters and setters; methods are passed
/// through unchanged.
static Procedure makeCandidate(Member member, bool setter) {
if (member is Procedure) return member;
if (member is Field) {
// TODO(paulberry): don't set the type or covariance annotations here,
// since they might not have been inferred yet. Instead, ensure that this
// information is propagated to the getter/setter during type inference.
var type = member.type;
var isGenericCovariantImpl = member.isGenericCovariantImpl;
var isGenericCovariantInterface = member.isGenericCovariantInterface;
var isCovariant = member.isCovariant;
if (setter) {
var valueParam = new VariableDeclaration('_', type: type)
..isGenericCovariantImpl = isGenericCovariantImpl
..isGenericCovariantInterface = isGenericCovariantInterface
..isCovariant = isCovariant;
var function = new FunctionNode(null,
positionalParameters: [valueParam], returnType: const VoidType());
return new SyntheticAccessor(
member.name, ProcedureKind.Setter, function, member)
..parent = member.enclosingClass;
} else {
var function = new FunctionNode(null, returnType: type);
return new SyntheticAccessor(
member.name, ProcedureKind.Getter, function, member)
..parent = member.enclosingClass;
}
}
return unhandled('${member.runtimeType}', 'makeCandidate', -1, null);
}
}
/// A [SyntheticAccessor] represents the getter or setter implied by a field.
class SyntheticAccessor extends Procedure {
/// The field associated with the synthetic accessor.
final Field _field;
SyntheticAccessor(
Name name, ProcedureKind kind, FunctionNode function, this._field)
: super(name, kind, function);
}