Skip to content

Commit dd6257a

Browse files
nshahanCommit Queue
authored and
Commit Queue
committed
[ddc] Support dynamic calls of generic functions
Adds the ability to call functions/methods/tearoffs with generic type arguments when running with the new type system. Issue: #48585 Change-Id: I28b8cdad56d614a6b9904995634c0cef67d88ebc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/296960 Reviewed-by: Mayank Patke <[email protected]> Commit-Queue: Nicholas Shahan <[email protected]> Reviewed-by: Anna Gringauze <[email protected]>
1 parent df61eeb commit dd6257a

File tree

7 files changed

+221
-36
lines changed

7 files changed

+221
-36
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
15801580
var extAccessors = _classProperties!.extensionAccessors;
15811581
var staticMethods = <js_ast.Property>[];
15821582
var instanceMethods = <js_ast.Property>[];
1583+
var instanceMethodsDefaultTypeArgs = <js_ast.Property>[];
15831584
var staticGetters = <js_ast.Property>[];
15841585
var instanceGetters = <js_ast.Property>[];
15851586
var staticSetters = <js_ast.Property>[];
@@ -1635,14 +1636,28 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
16351636

16361637
if (needsSignature) {
16371638
js_ast.Expression type;
1639+
var memberName = _declareMemberName(member);
16381640
if (member.isAccessor) {
16391641
type = _emitType(member.isGetter
16401642
? reifiedType.returnType
16411643
: reifiedType.positionalParameters[0]);
16421644
} else {
16431645
type = visitFunctionType(reifiedType);
1646+
if (_options.newRuntimeTypes &&
1647+
reifiedType.typeParameters.isNotEmpty) {
1648+
// Instance methods with generic type parameters require extra
1649+
// information to support dynamic calls. The default values for the
1650+
// type parameters are encoded into a separate storage object for
1651+
// use at runtime.
1652+
var defaultTypeArgs = js_ast.ArrayInitializer([
1653+
for (var parameter in reifiedType.typeParameters)
1654+
_emitType(parameter.defaultType)
1655+
]);
1656+
var property = js_ast.Property(memberName, defaultTypeArgs);
1657+
instanceMethodsDefaultTypeArgs.add(property);
1658+
}
16441659
}
1645-
var property = js_ast.Property(_declareMemberName(member), type);
1660+
var property = js_ast.Property(memberName, type);
16461661
var signatures = getSignatureList(member);
16471662
signatures.add(property);
16481663
if (!member.isStatic &&
@@ -1654,6 +1669,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
16541669
}
16551670

16561671
emitSignature('Method', instanceMethods);
1672+
emitSignature('MethodsDefaultTypeArg', instanceMethodsDefaultTypeArgs);
16571673
// TODO(40273) Skip for all statics when the debugger consumes signature
16581674
// information from symbol files.
16591675
emitSignature('StaticMethod', staticMethods);
@@ -3034,7 +3050,22 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
30343050
// Avoid tagging a closure as Function? or Function*
30353051
type.withDeclaredNullability(Nullability.nonNullable),
30363052
lazy: lazy);
3037-
return runtimeCall(lazy ? 'lazyFn(#, #)' : 'fn(#, #)', [fn, typeRep]);
3053+
if (_options.newRuntimeTypes) {
3054+
if (type.typeParameters.isEmpty) {
3055+
return runtimeCall(lazy ? 'lazyFn(#, #)' : 'fn(#, #)', [fn, typeRep]);
3056+
} else {
3057+
var typeParameterDefaults = [
3058+
for (var parameter in type.typeParameters)
3059+
_emitType(parameter.defaultType)
3060+
];
3061+
var defaultInstantiatedBounds =
3062+
_emitConstList(const DynamicType(), typeParameterDefaults);
3063+
return runtimeCall(
3064+
'gFn(#, #, #)', [fn, typeRep, defaultInstantiatedBounds]);
3065+
}
3066+
} else {
3067+
return runtimeCall(lazy ? 'lazyFn(#, #)' : 'fn(#, #)', [fn, typeRep]);
3068+
}
30383069
}
30393070

30403071
/// Whether the expression for [type] can be evaluated at this point in the JS

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ Object instantiateClass(Object genericClass, List<Object> typeArgs) {
290290

291291
final _constructorSig = JS('', 'Symbol("sigCtor")');
292292
final _methodSig = JS('', 'Symbol("sigMethod")');
293+
final _methodsDefaultTypeArgSig = JS('', 'Symbol("sigMethodDefaultTypeArgs")');
293294
final _fieldSig = JS('', 'Symbol("sigField")');
294295
final _getterSig = JS('', 'Symbol("sigGetter")');
295296
final _setterSig = JS('', 'Symbol("sigSetter")');
@@ -302,6 +303,8 @@ final _libraryUri = JS('', 'Symbol("libraryUri")');
302303

303304
getConstructors(value) => _getMembers(value, _constructorSig);
304305
getMethods(value) => _getMembers(value, _methodSig);
306+
getMethodsDefaultTypeArgs(value) =>
307+
_getMembers(value, _methodsDefaultTypeArgSig);
305308
getFields(value) => _getMembers(value, _fieldSig);
306309
getGetters(value) => _getMembers(value, _getterSig);
307310
getSetters(value) => _getMembers(value, _setterSig);
@@ -351,6 +354,11 @@ getMethodType(type, name) {
351354
return m != null ? JS('', '#[#]', m, name) : null;
352355
}
353356

357+
/// Returns the default type argument values for the instance method [name] on
358+
/// the class [type].
359+
JSArray<Object> getMethodDefaultTypeArgs(type, name) =>
360+
JS('!', '#[#]', getMethodsDefaultTypeArgs(type), name);
361+
354362
/// Gets the type of the corresponding setter (this includes writable fields).
355363
getSetterType(type, name) {
356364
var setters = getSetters(type);
@@ -394,6 +402,8 @@ classGetConstructorType(cls, name) {
394402
}
395403

396404
void setMethodSignature(f, sigF) => JS('', '#[#] = #', f, _methodSig, sigF);
405+
void setMethodsDefaultTypeArgSignature(f, sigF) =>
406+
JS('', '#[#] = #', f, _methodsDefaultTypeArgSig, sigF);
397407
void setFieldSignature(f, sigF) => JS('', '#[#] = #', f, _fieldSig, sigF);
398408
void setGetterSignature(f, sigF) => JS('', '#[#] = #', f, _getterSig, sigF);
399409
void setSetterSignature(f, sigF) => JS('', '#[#] = #', f, _setterSig, sigF);

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ argumentError(value) {
1717
/// that should never be executed.
1818
// TODO(48585): Remove after switching to the new runtime type system.
1919
Never throwUnimplementedInOldRti() => throw UnimplementedError(
20-
'This code path is not support with the old runtime type system.');
20+
'This code path is not supported with the old runtime type system.');
2121

2222
throwUnimplementedError(String message) {
2323
throw UnimplementedError(message);

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart

Lines changed: 143 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,20 @@ bind(obj, name, method) {
5555
// TODO(jmesserly): canonicalize tearoffs.
5656
JS('', '#._boundObject = #', f, obj);
5757
JS('', '#._boundMethod = #', f, method);
58-
JS(
59-
'',
60-
'#[#] = #',
61-
f,
62-
JS_GET_FLAG('NEW_RUNTIME_TYPES')
63-
? JS_GET_NAME(JsGetName.SIGNATURE_NAME)
64-
: _runtimeType,
65-
getMethodType(getType(obj), name));
58+
var objType = getType(obj);
59+
var methodType = getMethodType(objType, name);
60+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
61+
if (rti.isGenericFunctionType(methodType)) {
62+
// Attach the default type argument values to the new function in case
63+
// they are needed for a dynamic call.
64+
var defaultTypeArgs = getMethodDefaultTypeArgs(objType, name);
65+
JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
66+
}
67+
JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), methodType);
68+
} else {
69+
JS('', '#[#] = #', f, _runtimeType, methodType);
70+
}
71+
6672
return f;
6773
}
6874

@@ -77,21 +83,25 @@ bind(obj, name, method) {
7783
/// a native type/interface with `call`.
7884
bindCall(obj, name) {
7985
if (obj == null) return null;
80-
var ftype = getMethodType(getType(obj), name);
86+
var objType = getType(obj);
87+
var ftype = getMethodType(objType, name);
8188
if (ftype == null) return null;
8289
var method = JS('', '#[#]', obj, name);
8390
var f = JS('', '#.bind(#)', method, obj);
8491
// TODO(jmesserly): canonicalize tearoffs.
8592
JS('', '#._boundObject = #', f, obj);
8693
JS('', '#._boundMethod = #', f, method);
87-
JS(
88-
'',
89-
'#[#] = #',
90-
f,
91-
JS_GET_FLAG('NEW_RUNTIME_TYPES')
92-
? JS_GET_NAME(JsGetName.SIGNATURE_NAME)
93-
: _runtimeType,
94-
ftype);
94+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
95+
JS('', '#[#] = #', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME), ftype);
96+
if (rti.isGenericFunctionType(ftype)) {
97+
// Attach the default type argument values to the new function in case
98+
// they are needed for a dynamic call.
99+
var defaultTypeArgs = getMethodDefaultTypeArgs(objType, name);
100+
JS('', '#._defaultTypeArgs = #', f, defaultTypeArgs);
101+
}
102+
} else {
103+
JS('', '#[#] = #', f, _runtimeType, ftype);
104+
}
95105
return f;
96106
}
97107

@@ -100,22 +110,31 @@ bindCall(obj, name) {
100110
/// We need to apply the type arguments both to the function, as well as its
101111
/// associated function type.
102112
gbind(f, @rest List<Object> typeArgs) {
113+
var instantiatedType;
103114
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
104-
throw 'TODO: Support tearing off generic methods';
115+
Object fnType = JS('!', '#[#]', f, JS_GET_NAME(JsGetName.SIGNATURE_NAME));
116+
// TODO(nshahan): The old type system checks type arguments against the
117+
// bounds here but why? Is it possible to reach this code without knowing
118+
// if the provided type args are valid or not?
119+
var instantiationBinding =
120+
rti.bindingRtiFromList(JS<JSArray>('!', '#', typeArgs));
121+
instantiatedType = rti.instantiatedGenericFunctionType(
122+
JS<rti.Rti>('!', '#', fnType), instantiationBinding);
105123
} else {
106124
GenericFunctionType type = JS('!', '#[#]', f, _runtimeType);
107125
type.checkBounds(typeArgs);
108-
// Create a JS wrapper function that will also pass the type arguments.
109-
var result =
110-
JS('', '(...args) => #.apply(null, #.concat(args))', f, typeArgs);
111-
// Tag the wrapper with the original function to be used for equality
112-
// checks.
113-
JS('', '#["_originalFn"] = #', result, f);
114-
JS('', '#["_typeArgs"] = #', result, constList(typeArgs, Object));
115-
116-
// Tag the wrapper with the instantiated function type.
117-
return fn(result, type.instantiate(typeArgs));
126+
instantiatedType = type.instantiate(typeArgs);
118127
}
128+
// Create a JS wrapper function that will also pass the type arguments.
129+
var result =
130+
JS('', '(...args) => #.apply(null, #.concat(args))', f, typeArgs);
131+
// Tag the wrapper with the original function to be used for equality
132+
// checks.
133+
JS('', '#["_originalFn"] = #', result, f);
134+
JS('', '#["_typeArgs"] = #', result, constList(typeArgs, Object));
135+
136+
// Tag the wrapper with the instantiated function type.
137+
return fn(result, instantiatedType);
119138
}
120139

121140
dloadRepl(obj, field) => dload(obj, replNameLookup(obj, field));
@@ -463,16 +482,63 @@ _checkAndCall(f, ftype, obj, typeArgs, args, named, displayName) {
463482
return JS('', '#.apply(#, #)', f, obj, args);
464483
}
465484

466-
// Apply type arguments
485+
// Apply type arguments if needed.
467486
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
468487
if (rti.isGenericFunctionType(ftype)) {
469-
throw 'TODO: Support dynamic calls of functions with generic type '
470-
'arguments.';
488+
var typeParameterBounds = rti.getGenericFunctionBounds(ftype);
489+
var typeParameterCount = JS<int>('!', '#.length', typeParameterBounds);
490+
if (typeArgs == null) {
491+
// No type arguments were provided so they will take on their default
492+
// values that are attached to generic function tearoffs for this
493+
// purpose.
494+
//
495+
// Note the default value is not always equivalent to the bound for a
496+
// given type parameter. The bound can reference other type parameters
497+
// and contain infinite cycles where the default value is determined
498+
// with an algorithm that will terminate. This means that the default
499+
// values will need to be checked against the instantiated bounds just
500+
// like any other type arguments.
501+
typeArgs = JS('!', '#._defaultTypeArgs', f);
502+
}
503+
var typeArgCount = JS<int>('!', '#.length', typeArgs);
504+
if (typeArgCount != typeParameterCount) {
505+
return callNSM('Dynamic call with incorrect number of type arguments. '
506+
'Expected: $typeParameterCount Actual: $typeArgCount');
507+
} else {
508+
// Check the provided type arguments against the instantiated bounds.
509+
for (var i = 0; i < typeParameterCount; i++) {
510+
var bound = JS<rti.Rti>('!', '#[#]', typeParameterBounds, i);
511+
var typeArg = JS<rti.Rti>('!', '#[#]', typeArgs, i);
512+
// TODO(nshahan): Skip type checks when the bound is a top type once
513+
// there is no longer any warnings/errors in weak null safety mode.
514+
if (bound != typeArg) {
515+
var instantiatedBound = rti.substitute(bound, typeArgs);
516+
var validSubtype = compileTimeFlag('soundNullSafety')
517+
? rti.isSubtype(JS_EMBEDDED_GLOBAL('', RTI_UNIVERSE), typeArg,
518+
instantiatedBound)
519+
: _isSubtypeWithWarning(typeArg, instantiatedBound);
520+
if (!validSubtype) {
521+
throwTypeError("The type '${rti.rtiToString(typeArg)}' "
522+
"is not a subtype of the type variable bound "
523+
"'${rti.rtiToString(instantiatedBound)}' "
524+
"of type variable 'T${i + 1}' "
525+
"in '${rti.rtiToString(ftype)}'.");
526+
}
527+
}
528+
}
529+
}
530+
var instantiationBinding =
531+
rti.bindingRtiFromList(JS<JSArray>('!', '#', typeArgs));
532+
ftype = rti.instantiatedGenericFunctionType(
533+
JS<rti.Rti>('!', '#', ftype), instantiationBinding);
534+
} else if (typeArgs != null) {
535+
return callNSM('Dynamic call with unexpected type arguments. '
536+
'Expected: 0 Actual: ${JS<int>('!', '#.length', typeArgs)}');
471537
}
472538
} else {
539+
// Old runtime types.
473540
if (_jsInstanceOf(ftype, GenericFunctionType)) {
474541
var formalCount = JS<int>('!', '#.formalCount', ftype);
475-
476542
if (typeArgs == null) {
477543
typeArgs = JS<List>('!', '#.instantiateDefaultBounds()', ftype);
478544
} else if (JS<bool>('!', '#.length != #', typeArgs, formalCount)) {
@@ -564,6 +630,12 @@ callMethod(obj, name, typeArgs, args, named, displayName) {
564630
var f = obj != null ? JS('', '#[#]', obj, symbol) : null;
565631
var type = getType(obj);
566632
var ftype = getMethodType(type, symbol);
633+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
634+
if (ftype != null && rti.isGenericFunctionType(ftype) && typeArgs == null) {
635+
// No type arguments were provided, use the default values in this call.
636+
typeArgs = getMethodDefaultTypeArgs(type, name);
637+
}
638+
}
567639
// No such method if dart object and ftype is missing.
568640
return _checkAndCall(f, ftype, obj, typeArgs, args, named, displayName);
569641
}
@@ -667,6 +739,45 @@ cast(obj, type) {
667739
}
668740
}
669741

742+
/// Returns `true` if [t1] is a subtype of [t2].
743+
///
744+
/// This method should only be called when running with weak null safety.
745+
///
746+
/// Will produce a warning/error (if enabled) when the subtype passes but would
747+
/// fail in sound null safety.
748+
///
749+
/// Currently only called from _checkAndCall to test type arguments applied to
750+
/// dynamic method calls.
751+
// TODO(48585) Revise argument types after removing old type representation.
752+
@notNull
753+
bool _isSubtypeWithWarning(@notNull t1, @notNull t2) {
754+
// Avoid classes from the rti library appearing unless they are used.
755+
if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) {
756+
var t1Rti = JS<rti.Rti>('!', '#', t1);
757+
var t2Rti = JS<rti.Rti>('!', '#', t2);
758+
var legacyErasedRecipe = rti.Rti.getLegacyErasedRecipe(t2Rti);
759+
var legacyErasedType = rti.findType(legacyErasedRecipe);
760+
legacyTypeChecks = false;
761+
var validSubtype = rti.isSubtype(
762+
JS_EMBEDDED_GLOBAL('', RTI_UNIVERSE), t1Rti, legacyErasedType);
763+
legacyTypeChecks = true;
764+
if (validSubtype) return true;
765+
validSubtype =
766+
rti.isSubtype(JS_EMBEDDED_GLOBAL('', RTI_UNIVERSE), t1Rti, t2Rti);
767+
if (validSubtype) {
768+
// Subtype check passes put would fail with sound null safety.
769+
_nullWarn('${rti.createRuntimeType(t1Rti)} '
770+
'is not a subtype of '
771+
'${rti.createRuntimeType(t2Rti)}.');
772+
}
773+
return validSubtype;
774+
} else {
775+
// Should never be reached because this method isn't called when using old
776+
// runtime type representation.
777+
throwUnimplementedInOldRti();
778+
}
779+
}
780+
670781
bool test(bool? obj) {
671782
if (obj == null) throw BooleanConversionAssertionError();
672783
return obj;

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ fn(closure, type) {
5656
return closure;
5757
}
5858

59+
/// Tag a generic [closure] with a [type] and the [defaultTypeArgs] values.
60+
///
61+
/// Only called from generated code when running with the new type system.
62+
gFn(Object closure, Object type, JSArray<Object> defaultTypeArgs) {
63+
JS('', '#[#] = #', closure, JS_GET_NAME(JsGetName.SIGNATURE_NAME), type);
64+
JS('', '#._defaultTypeArgs = #', closure, defaultTypeArgs);
65+
return closure;
66+
}
67+
5968
/// Tag a closure with a type that's computed lazily.
6069
///
6170
/// `dart.fn(closure, type)` marks [closure] with a getter that uses

sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,19 @@ import 'dart:_js_helper'
5151
import 'dart:_js_shared_embedded_names';
5252
import 'dart:_rti' as rti
5353
show
54+
bindingRtiFromList,
5455
createRuntimeType,
5556
constructorRtiCachePropertyName,
5657
findType,
5758
getFunctionParametersForDynamicChecks,
59+
getGenericFunctionBounds,
5860
instanceType,
61+
instantiatedGenericFunctionType,
5962
interfaceTypeRecipePropertyName,
6063
isGenericFunctionType,
6164
isSubtype,
6265
Rti,
66+
substitute,
6367
rtiToString;
6468

6569
export 'dart:_debugger' show getDynamicStats, clearDynamicStats, trackCall;

0 commit comments

Comments
 (0)