[dart2js] Ensure type arguments are passed when invoking a generic
function property. Change-Id: I387977e2f1fb7732d94331b7a97cceeec767aaae Bug: https://github.com/dart-lang/sdk/issues/41449 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151301 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Mayank Patke <fishythefish@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
e3fb81af38
commit
cf62339751
@@ -2263,4 +2263,14 @@ abstract class DartTypes {
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
bool canAssignGenericFunctionTo(DartType type) {
|
||||
type = type.withoutNullability;
|
||||
return type is FunctionType && type.typeVariables.isNotEmpty ||
|
||||
isSubtype(commonElements.functionType, type) ||
|
||||
type is FutureOrType && canAssignGenericFunctionTo(type.typeArgument) ||
|
||||
type is TypeVariableType &&
|
||||
canAssignGenericFunctionTo(getTypeVariableBound(type.element)) ||
|
||||
type is FunctionTypeVariable && canAssignGenericFunctionTo(type.bound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,11 @@ class ClassNode extends RtiNode {
|
||||
String get kind => 'class';
|
||||
}
|
||||
|
||||
class MethodNode extends RtiNode {
|
||||
abstract class CallableNode extends RtiNode {
|
||||
bool selectorApplies(Selector selector, BuiltWorld world);
|
||||
}
|
||||
|
||||
class MethodNode extends CallableNode {
|
||||
final Entity function;
|
||||
final ParameterStructure parameterStructure;
|
||||
final bool isCallTarget;
|
||||
@@ -139,7 +143,8 @@ class MethodNode extends RtiNode {
|
||||
@override
|
||||
Entity get entity => function;
|
||||
|
||||
bool selectorApplies(Selector selector) {
|
||||
@override
|
||||
bool selectorApplies(Selector selector, BuiltWorld world) {
|
||||
if (isNoSuchMethod) return true;
|
||||
return (isCallTarget && selector.isClosureCall ||
|
||||
instanceName == selector.memberName) &&
|
||||
@@ -162,10 +167,37 @@ class MethodNode extends RtiNode {
|
||||
}
|
||||
}
|
||||
|
||||
class CallablePropertyNode extends CallableNode {
|
||||
final MemberEntity property;
|
||||
final DartType type;
|
||||
|
||||
CallablePropertyNode(this.property, this.type);
|
||||
|
||||
@override
|
||||
Entity get entity => property;
|
||||
|
||||
@override
|
||||
String get kind => 'callable-property';
|
||||
|
||||
@override
|
||||
bool selectorApplies(Selector selector, BuiltWorld world) {
|
||||
if (world.annotationsData.getParameterCheckPolicy(property).isTrusted)
|
||||
return false;
|
||||
if (property.memberName != selector.memberName) return false;
|
||||
if (type is FunctionType &&
|
||||
!selector.callStructure
|
||||
.signatureApplies(ParameterStructure.fromType(type))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'CallablePropertyNode(property=$property)';
|
||||
}
|
||||
|
||||
class TypeVariableTests {
|
||||
List<RtiNode> _nodes = <RtiNode>[];
|
||||
Map<ClassEntity, ClassNode> _classes = <ClassEntity, ClassNode>{};
|
||||
Map<Entity, MethodNode> _methods = <Entity, MethodNode>{};
|
||||
Map<ClassEntity, ClassNode> _classes = {};
|
||||
Map<Entity, MethodNode> _methods = {};
|
||||
Map<MemberEntity, CallablePropertyNode> _callableProperties = {};
|
||||
Map<Selector, Set<Entity>> _appliedSelectorMap;
|
||||
Map<GenericInstantiation, Set<Entity>> _instantiationMap;
|
||||
|
||||
@@ -293,11 +325,7 @@ class TypeVariableTests {
|
||||
}
|
||||
|
||||
ClassNode _getClassNode(ClassEntity cls) {
|
||||
return _classes.putIfAbsent(cls, () {
|
||||
ClassNode node = new ClassNode(cls);
|
||||
_nodes.add(node);
|
||||
return node;
|
||||
});
|
||||
return _classes.putIfAbsent(cls, () => ClassNode(cls));
|
||||
}
|
||||
|
||||
MethodNode _getMethodNode(ElementEnvironment elementEnvironment,
|
||||
@@ -325,11 +353,15 @@ class TypeVariableTests {
|
||||
elementEnvironment.getLocalFunctionType(function));
|
||||
node = new MethodNode(function, parameterStructure, isCallTarget: true);
|
||||
}
|
||||
_nodes.add(node);
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
CallablePropertyNode _getCallablePropertyNode(
|
||||
MemberEntity property, DartType type) =>
|
||||
_callableProperties.putIfAbsent(
|
||||
property, () => CallablePropertyNode(property, type));
|
||||
|
||||
void _setupDependencies(
|
||||
ElementEnvironment elementEnvironment,
|
||||
CommonElements commonElements,
|
||||
@@ -431,9 +463,8 @@ class TypeVariableTests {
|
||||
|
||||
world.forEachDynamicTypeArgument(
|
||||
(Selector selector, Iterable<DartType> typeArguments) {
|
||||
void processEntity(Entity entity) {
|
||||
MethodNode node = _getMethodNode(elementEnvironment, world, entity);
|
||||
if (node.selectorApplies(selector)) {
|
||||
void processCallableNode(CallableNode node) {
|
||||
if (node.selectorApplies(selector, world)) {
|
||||
for (DartType type in typeArguments) {
|
||||
// Register that if `node.entity` needs type arguments then so do
|
||||
// the entities that declare type variables occurring in [type].
|
||||
@@ -442,10 +473,21 @@ class TypeVariableTests {
|
||||
}
|
||||
}
|
||||
|
||||
world.forEachGenericInstanceMethod(processEntity);
|
||||
world.genericLocalFunctions.forEach(processEntity);
|
||||
world.closurizedStatics.forEach(processEntity);
|
||||
world.userNoSuchMethods.forEach(processEntity);
|
||||
void processMethod(Entity entity) {
|
||||
MethodNode node = _getMethodNode(elementEnvironment, world, entity);
|
||||
processCallableNode(node);
|
||||
}
|
||||
|
||||
void processCallableProperty(MemberEntity entity, DartType type) {
|
||||
CallablePropertyNode node = _getCallablePropertyNode(entity, type);
|
||||
processCallableNode(node);
|
||||
}
|
||||
|
||||
world.forEachGenericInstanceMethod(processMethod);
|
||||
world.genericLocalFunctions.forEach(processMethod);
|
||||
world.closurizedStatics.forEach(processMethod);
|
||||
world.userNoSuchMethods.forEach(processMethod);
|
||||
world.genericCallableProperties.forEach(processCallableProperty);
|
||||
});
|
||||
|
||||
for (GenericInstantiation instantiation in genericInstantiations) {
|
||||
@@ -627,11 +669,14 @@ class TypeVariableTests {
|
||||
|
||||
world.forEachDynamicTypeArgument(
|
||||
(Selector selector, Iterable<DartType> typeArguments) {
|
||||
for (MethodNode node in _methods.values) {
|
||||
if (node.selectorApplies(selector)) {
|
||||
for (CallableNode node in [
|
||||
..._methods.values,
|
||||
..._callableProperties.values
|
||||
]) {
|
||||
if (node.selectorApplies(selector, world)) {
|
||||
if (forRtiNeeds) {
|
||||
_appliedSelectorMap
|
||||
.putIfAbsent(selector, () => new Set<Entity>())
|
||||
.putIfAbsent(selector, () => {})
|
||||
.add(node.entity);
|
||||
}
|
||||
if (node.hasTest) {
|
||||
@@ -824,18 +869,14 @@ class RuntimeTypesNeedImpl implements RuntimeTypesNeed {
|
||||
sink.end(tag);
|
||||
}
|
||||
|
||||
bool checkClass(covariant ClassEntity cls) => true;
|
||||
|
||||
@override
|
||||
bool classNeedsTypeArguments(ClassEntity cls) {
|
||||
assert(checkClass(cls));
|
||||
if (!_elementEnvironment.isGenericClass(cls)) return false;
|
||||
return classesNeedingTypeArguments.contains(cls);
|
||||
}
|
||||
|
||||
@override
|
||||
bool classHasErasedTypeArguments(ClassEntity cls) {
|
||||
assert(checkClass(cls));
|
||||
if (!_elementEnvironment.isGenericClass(cls)) return false;
|
||||
return !classesNeedingTypeArguments.contains(cls);
|
||||
}
|
||||
@@ -1273,7 +1314,8 @@ class RuntimeTypesNeedBuilderImpl implements RuntimeTypesNeedBuilder {
|
||||
typeVariableTests
|
||||
.forEachAppliedSelector((Selector selector, Set<Entity> targets) {
|
||||
for (Entity target in targets) {
|
||||
if (methodsNeedingTypeArguments.contains(target) ||
|
||||
if (target is MemberEntity && (target.isField || target.isGetter) ||
|
||||
methodsNeedingTypeArguments.contains(target) ||
|
||||
localFunctionsNeedingTypeArguments.contains(target)) {
|
||||
selectorsNeedingTypeArguments.add(selector);
|
||||
if (retainDataForTesting) {
|
||||
|
||||
@@ -233,4 +233,28 @@ class KClosedWorldImpl implements KClosedWorld {
|
||||
}
|
||||
return _closurizedStaticsCache;
|
||||
}
|
||||
|
||||
Map<MemberEntity, DartType> _genericCallablePropertiesCache;
|
||||
|
||||
@override
|
||||
Map<MemberEntity, DartType> get genericCallableProperties {
|
||||
if (_genericCallablePropertiesCache == null) {
|
||||
_genericCallablePropertiesCache = {};
|
||||
liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
|
||||
if (usage.hasRead) {
|
||||
DartType type;
|
||||
if (member.isField) {
|
||||
type = elementEnvironment.getFieldType(member);
|
||||
} else if (member.isGetter) {
|
||||
type = elementEnvironment.getFunctionType(member).returnType;
|
||||
}
|
||||
if (type == null) return;
|
||||
if (dartTypes.canAssignGenericFunctionTo(type)) {
|
||||
_genericCallablePropertiesCache[member] = type;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return _genericCallablePropertiesCache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../common_elements.dart';
|
||||
import '../constants/values.dart';
|
||||
import '../elements/entities.dart';
|
||||
import '../elements/types.dart';
|
||||
import '../js_backend/annotations.dart' show AnnotationsData;
|
||||
import '../js_backend/interceptor_data.dart' show OneShotInterceptorData;
|
||||
import '../js_backend/native_data.dart' show NativeBasicData;
|
||||
import '../js_model/elements.dart';
|
||||
@@ -668,6 +669,9 @@ class CodegenWorldImpl implements CodegenWorld {
|
||||
_staticTypeArgumentDependencies = staticTypeArgumentDependencies,
|
||||
_dynamicTypeArgumentDependencies = dynamicTypeArgumentDependencies;
|
||||
|
||||
@override
|
||||
AnnotationsData get annotationsData => _closedWorld.annotationsData;
|
||||
|
||||
@override
|
||||
void forEachStaticField(void Function(FieldEntity) f) {
|
||||
bool failure = false;
|
||||
@@ -778,6 +782,32 @@ class CodegenWorldImpl implements CodegenWorld {
|
||||
return _closurizedStaticsCache;
|
||||
}
|
||||
|
||||
Map<MemberEntity, DartType> _genericCallablePropertiesCache;
|
||||
|
||||
@override
|
||||
Map<MemberEntity, DartType> get genericCallableProperties {
|
||||
if (_genericCallablePropertiesCache == null) {
|
||||
_genericCallablePropertiesCache = {};
|
||||
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
|
||||
if (usage.hasRead) {
|
||||
DartType type;
|
||||
if (member.isField) {
|
||||
type = _closedWorld.elementEnvironment.getFieldType(member);
|
||||
} else if (member.isGetter) {
|
||||
type = _closedWorld.elementEnvironment
|
||||
.getFunctionType(member)
|
||||
.returnType;
|
||||
}
|
||||
if (type == null) return;
|
||||
if (_closedWorld.dartTypes.canAssignGenericFunctionTo(type)) {
|
||||
_genericCallablePropertiesCache[member] = type;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return _genericCallablePropertiesCache;
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachStaticTypeArgument(
|
||||
void f(Entity function, Set<DartType> typeArguments)) {
|
||||
|
||||
@@ -256,12 +256,17 @@ abstract class BuiltWorld {
|
||||
/// Static or top level methods that are closurized.
|
||||
Iterable<FunctionEntity> get closurizedStatics;
|
||||
|
||||
/// Properties (fields and getters) which can be called as generic functions.
|
||||
Map<MemberEntity, DartType> get genericCallableProperties;
|
||||
|
||||
/// Type variables used as type literals.
|
||||
Iterable<TypeVariableType> get typeVariableTypeLiterals;
|
||||
|
||||
/// Live user-defined 'noSuchMethod' implementations.
|
||||
Iterable<FunctionEntity> get userNoSuchMethods;
|
||||
|
||||
AnnotationsData get annotationsData;
|
||||
|
||||
/// Calls [f] for each live generic instance methods.
|
||||
void forEachGenericInstanceMethod(void Function(FunctionEntity) f);
|
||||
|
||||
@@ -306,6 +311,7 @@ abstract class KClosedWorld implements BuiltWorld {
|
||||
RuntimeTypesNeed get rtiNeed;
|
||||
NoSuchMethodData get noSuchMethodData;
|
||||
|
||||
@override
|
||||
AnnotationsData get annotationsData;
|
||||
|
||||
/// Set of live closurized members whose signatures reference type variables.
|
||||
|
||||
@@ -29,6 +29,13 @@ class B2 implements AAA {
|
||||
dynamic get foo => _arr.first;
|
||||
}
|
||||
|
||||
class B3 implements AAA {
|
||||
final dynamic __foo;
|
||||
B3(this.__foo);
|
||||
dynamic get _foo => __foo;
|
||||
dynamic get foo => _foo;
|
||||
}
|
||||
|
||||
@pragma('dart2js:noInline')
|
||||
test1<T>(AAA a, String expected) {
|
||||
// call-through getter 'foo' with one type argument.
|
||||
@@ -44,7 +51,9 @@ test2<U, V>(AAA a, String expected) {
|
||||
main() {
|
||||
test1<int>(B1(<P>() => '$P'), 'int');
|
||||
test1<num>(B2(<Q>() => '$Q'), 'num');
|
||||
test1<double>(B3(<R>() => '$R'), 'double');
|
||||
|
||||
test2<int, num>(B1(<A, B>() => '$A $B'), 'int num');
|
||||
test2<num, int>(B2(<X, Y>() => '$X $Y'), 'num int');
|
||||
test2<double, String>(B3(<C, D>() => '$C $D'), 'double String');
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ class B2 implements AAA {
|
||||
dynamic get foo => _arr.first;
|
||||
}
|
||||
|
||||
class B3 implements AAA {
|
||||
final dynamic __foo;
|
||||
B3(this.__foo);
|
||||
dynamic get _foo => __foo;
|
||||
dynamic get foo => _foo;
|
||||
}
|
||||
|
||||
@pragma('dart2js:noInline')
|
||||
test1<T>(AAA a, String expected) {
|
||||
// call-through getter 'foo' with one type argument.
|
||||
@@ -44,7 +51,9 @@ test2<U, V>(AAA a, String expected) {
|
||||
main() {
|
||||
test1<int>(B1(<P>() => '$P'), 'int');
|
||||
test1<num>(B2(<Q>() => '$Q'), 'num');
|
||||
test1<double>(B3(<R>() => '$R'), 'double');
|
||||
|
||||
test2<int, num>(B1(<A, B>() => '$A $B'), 'int num');
|
||||
test2<num, int>(B2(<X, Y>() => '$X $Y'), 'num int');
|
||||
test2<double, String>(B3(<C, D>() => '$C $D'), 'double String');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user