Skip to content

Commit 6598fa0

Browse files
author
John Messerly
committed
implement generic method runtime behavior, fixes #301
[email protected], [email protected] Review URL: https://codereview.chromium.org/1926283002 .
1 parent 7db49e6 commit 6598fa0

File tree

16 files changed

+1177
-863
lines changed

16 files changed

+1177
-863
lines changed

pkg/dev_compiler/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ test/codegen/expect/lib/
2121
test/codegen/expect/sunflower/dev_compiler/
2222
test/codegen/expect/matcher/
2323
test/codegen/language/*_multi.dart
24+
test/codegen/lib/html/*_multi.dart
2425

2526
# Created by ./tool/build_sdk.sh
2627
tool/generated_sdk/

pkg/dev_compiler/lib/runtime/dart_sdk.js

Lines changed: 867 additions & 712 deletions
Large diffs are not rendered by default.

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

Lines changed: 177 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,8 @@ class CodeGenerator extends GeneralizingAstVisitor
11161116
for (ConstructorDeclaration node in ctors) {
11171117
var memberName = _constructorName(node.element);
11181118
var element = node.element;
1119-
var parts = _emitFunctionTypeParts(element.type, node.parameters);
1119+
var parts = _emitFunctionTypeParts(element.type,
1120+
parameters: node.parameters.parameters);
11201121
var property =
11211122
new JS.Property(memberName, new JS.ArrayInitializer(parts));
11221123
tCtors.add(property);
@@ -1630,6 +1631,8 @@ class CodeGenerator extends GeneralizingAstVisitor
16301631
// call sites, but it's cleaner to instead transform the operator method.
16311632
fn = _alwaysReturnLastParameter(fn);
16321633
}
1634+
1635+
fn = _makeGenericFunction(fn);
16331636
}
16341637

16351638
return annotate(
@@ -1780,15 +1783,21 @@ class CodeGenerator extends GeneralizingAstVisitor
17801783
var lazy = topLevel && !_typeIsLoaded(type);
17811784

17821785
if (type is FunctionType && (name == '' || name == null)) {
1783-
if (type.returnType.isDynamic &&
1786+
if (type.typeFormals.isEmpty &&
1787+
type.returnType.isDynamic &&
17841788
type.optionalParameterTypes.isEmpty &&
17851789
type.namedParameterTypes.isEmpty &&
17861790
type.normalParameterTypes.every((t) => t.isDynamic)) {
17871791
return js.call('dart.fn(#)', [fn]);
17881792
}
17891793

1790-
String code = lazy ? '() => dart.definiteFunctionType(#)' : '#';
1791-
return js.call('dart.fn(#, $code)', [fn, _emitFunctionTypeParts(type)]);
1794+
var parts = _emitFunctionTypeParts(type);
1795+
if (lazy) {
1796+
return js.call(
1797+
'dart.lazyFn(#, () => #)', [fn, new JS.ArrayInitializer(parts)]);
1798+
} else {
1799+
return js.call('dart.fn(#, #)', [fn, parts]);
1800+
}
17921801
}
17931802
throw 'Function has non function type: $type';
17941803
}
@@ -1823,10 +1832,29 @@ class CodeGenerator extends GeneralizingAstVisitor
18231832

18241833
// Convert `function(...) { ... }` to `(...) => ...`
18251834
// This is for readability, but it also ensures correct `this` binding.
1826-
return annotate(
1827-
new JS.ArrowFun(f.params, body,
1828-
typeParams: f.typeParams, returnType: f.returnType),
1829-
node);
1835+
var fn = new JS.ArrowFun(f.params, body,
1836+
typeParams: f.typeParams, returnType: f.returnType);
1837+
1838+
return annotate(_makeGenericFunction(fn), node);
1839+
}
1840+
1841+
JS.FunctionExpression/*=T*/ _makeGenericFunction
1842+
/*<T extends JS.FunctionExpression>*/(JS.FunctionExpression/*=T*/ fn) {
1843+
if (fn.typeParams == null || fn.typeParams.isEmpty) return fn;
1844+
1845+
// TODO(jmesserly): we could make these default to `dynamic`.
1846+
var typeParams = fn.typeParams;
1847+
if (fn is JS.ArrowFun) {
1848+
return new JS.ArrowFun(typeParams, fn);
1849+
}
1850+
var f = fn as JS.Fun;
1851+
return new JS.Fun(
1852+
typeParams,
1853+
new JS.Block([
1854+
// Convert the function to an => function, to ensure `this` binding.
1855+
new JS.Return(new JS.ArrowFun(f.params, f.body,
1856+
typeParams: f.typeParams, returnType: f.returnType))
1857+
]));
18301858
}
18311859

18321860
/// Emits a non-arrow FunctionExpression node.
@@ -1838,25 +1866,27 @@ class CodeGenerator extends GeneralizingAstVisitor
18381866
/// Contrast with [visitFunctionExpression].
18391867
JS.Fun _emitFunction(FunctionExpression node) {
18401868
var fn = _emitFunctionBody(node.element, node.parameters, node.body);
1841-
return annotate(fn, node);
1869+
return annotate(_makeGenericFunction(fn), node);
18421870
}
18431871

18441872
JS.Fun _emitFunctionBody(ExecutableElement element,
18451873
FormalParameterList parameters, FunctionBody body) {
1846-
var returnType = emitTypeRef(element.returnType);
1874+
FunctionType type = element.type;
18471875

18481876
// sync*, async, async*
18491877
if (element.isAsynchronous || element.isGenerator) {
18501878
return new JS.Fun(
18511879
visitFormalParameterList(parameters, destructure: false),
1852-
js.statement('{ return #; }',
1853-
[_emitGeneratorFunctionBody(element, parameters, body)]),
1854-
returnType: returnType);
1880+
new JS.Block([
1881+
_emitGeneratorFunctionBody(element, parameters, body).toReturn()
1882+
]),
1883+
returnType: emitTypeRef(type.returnType));
18551884
}
1885+
18561886
// normal function (sync)
18571887
return new JS.Fun(visitFormalParameterList(parameters), _visit(body),
1858-
typeParams: _emitTypeFormals(element.typeParameters),
1859-
returnType: returnType);
1888+
typeParams: _emitTypeFormals(type.typeFormals),
1889+
returnType: emitTypeRef(type.returnType));
18601890
}
18611891

18621892
JS.Expression _emitGeneratorFunctionBody(ExecutableElement element,
@@ -2061,27 +2091,37 @@ class CodeGenerator extends GeneralizingAstVisitor
20612091
/// Emit the pieces of a function type, as an array of return type,
20622092
/// regular args, and optional/named args.
20632093
List<JS.Expression> _emitFunctionTypeParts(FunctionType type,
2064-
[FormalParameterList parameterList]) {
2065-
var parameters = parameterList?.parameters;
2066-
var returnType = type.returnType;
2094+
{List<FormalParameter> parameters, bool lowerTypedef: false}) {
20672095
var parameterTypes = type.normalParameterTypes;
20682096
var optionalTypes = type.optionalParameterTypes;
20692097
var namedTypes = type.namedParameterTypes;
2070-
var rt = _emitTypeName(returnType);
2098+
var rt = _emitTypeName(type.returnType);
20712099
var ra = _emitTypeNames(parameterTypes, parameters);
2072-
if (!namedTypes.isEmpty) {
2100+
2101+
List<JS.Expression> typeParts;
2102+
if (namedTypes.isNotEmpty) {
20732103
assert(optionalTypes.isEmpty);
20742104
// TODO(vsm): Pass in annotations here as well.
20752105
var na = _emitTypeProperties(namedTypes);
2076-
return [rt, ra, na];
2077-
}
2078-
if (!optionalTypes.isEmpty) {
2106+
typeParts = [rt, ra, na];
2107+
} else if (optionalTypes.isNotEmpty) {
20792108
assert(namedTypes.isEmpty);
20802109
var oa = _emitTypeNames(
20812110
optionalTypes, parameters?.sublist(parameterTypes.length));
2082-
return [rt, ra, oa];
2111+
typeParts = [rt, ra, oa];
2112+
} else {
2113+
typeParts = [rt, ra];
2114+
}
2115+
2116+
var typeFormals = type.typeFormals;
2117+
if (typeFormals.isNotEmpty && !lowerTypedef) {
2118+
// TODO(jmesserly): this is a suboptimal representation for universal
2119+
// function types (as callable functions). See discussion at:
2120+
// https://github.com/dart-lang/dev_compiler/issues/526
2121+
var tf = _emitTypeFormals(typeFormals);
2122+
typeParts = [new JS.ArrowFun(tf, new JS.ArrayInitializer(typeParts))];
20832123
}
2084-
return [rt, ra];
2124+
return typeParts;
20852125
}
20862126

20872127
/// Emits a Dart [type] into code.
@@ -2109,17 +2149,13 @@ class CodeGenerator extends GeneralizingAstVisitor
21092149
var name = type.name;
21102150
var element = type.element;
21112151
if (name == '' || name == null || lowerTypedef) {
2112-
var parts = _emitFunctionTypeParts(type as FunctionType);
2152+
// TODO(jmesserly): should we change how typedefs work? They currently
2153+
// go through use similar logic as generic classes. This makes them
2154+
// different from universal function types.
2155+
var ft = type as FunctionType;
2156+
var parts = _emitFunctionTypeParts(ft, lowerTypedef: lowerTypedef);
21132157
return js.call('dart.functionType(#)', [parts]);
21142158
}
2115-
// For now, reify generic method parameters as dynamic
2116-
bool _isGenericTypeParameter(DartType type) =>
2117-
type is TypeParameterType &&
2118-
type.element.enclosingElement is! TypeDefiningElement;
2119-
2120-
if (_isGenericTypeParameter(type)) {
2121-
return js.call('dart.dynamic');
2122-
}
21232159

21242160
if (type is TypeParameterType) {
21252161
return new JS.Identifier(name);
@@ -2128,7 +2164,7 @@ class CodeGenerator extends GeneralizingAstVisitor
21282164
if (type is ParameterizedType) {
21292165
var args = type.typeArguments;
21302166
Iterable jsArgs = null;
2131-
if (args.any((a) => !a.isDynamic && !_isGenericTypeParameter(a))) {
2167+
if (args.any((a) => !a.isDynamic)) {
21322168
jsArgs = args.map(_emitTypeName);
21332169
} else if (lowerGeneric) {
21342170
jsArgs = [];
@@ -2268,51 +2304,130 @@ class CodeGenerator extends GeneralizingAstVisitor
22682304

22692305
@override
22702306
visitMethodInvocation(MethodInvocation node) {
2271-
if (node.operator != null && node.operator.lexeme == '?.') {
2307+
if (node.operator?.lexeme == '?.') {
22722308
return _emitNullSafe(node);
22732309
}
22742310

2275-
var target = _getTarget(node);
22762311
var result = _emitForeignJS(node);
22772312
if (result != null) return result;
22782313

2279-
String code;
2314+
var target = _getTarget(node);
22802315
if (target == null || isLibraryPrefix(target)) {
2281-
if (DynamicInvoke.get(node.methodName)) {
2282-
code = 'dart.dcall(#, #)';
2283-
} else {
2284-
code = '#(#)';
2285-
}
2286-
return js
2287-
.call(code, [_visit(node.methodName), _visit(node.argumentList)]);
2316+
return _emitFunctionCall(node);
22882317
}
22892318

2319+
return _emitMethodCall(target, node);
2320+
}
2321+
2322+
/// Emits a (possibly generic) instance method call.
2323+
JS.Expression _emitMethodCall(Expression target, MethodInvocation node) {
22902324
var type = getStaticType(target);
22912325
var name = node.methodName.name;
22922326
var element = node.methodName.staticElement;
22932327
bool isStatic = element is ExecutableElement && element.isStatic;
22942328
var memberName = _emitMemberName(name, type: type, isStatic: isStatic);
22952329

2330+
JS.Expression jsTarget = _visit(target);
2331+
var typeArgs = _emitInvokeTypeArguments(node);
2332+
List<JS.Expression> args = _visit(node.argumentList);
22962333
if (DynamicInvoke.get(target)) {
2297-
code = 'dart.dsend(#, #, #)';
2298-
} else if (DynamicInvoke.get(node.methodName)) {
2334+
if (typeArgs != null) {
2335+
return js.call('dart.dgsend(#, [#], #, #)',
2336+
[jsTarget, typeArgs, memberName, args]);
2337+
} else {
2338+
return js.call('dart.dsend(#, #, #)', [jsTarget, memberName, args]);
2339+
}
2340+
}
2341+
if (_isObjectMemberCall(target, name)) {
2342+
// Object methods require a helper for null checks & native types.
2343+
assert(typeArgs == null); // Object methods don't take type args.
2344+
return js.call('dart.#(#, #)', [memberName, jsTarget, args]);
2345+
}
2346+
2347+
jsTarget = new JS.PropertyAccess(jsTarget, memberName);
2348+
if (typeArgs != null) jsTarget = new JS.Call(jsTarget, typeArgs);
2349+
2350+
if (DynamicInvoke.get(node.methodName)) {
22992351
// This is a dynamic call to a statically known target. For example:
23002352
// class Foo { Function bar; }
23012353
// new Foo().bar(); // dynamic call
2302-
code = 'dart.dcall(#.#, #)';
2303-
} else if (_isObjectMemberCall(target, name)) {
2304-
// Object methods require a helper for null checks.
2305-
return js.call('dart.#(#, #)',
2306-
[memberName, _visit(target), _visit(node.argumentList)]);
2354+
return js.call('dart.dcall(#, #)', [jsTarget, args]);
2355+
}
2356+
2357+
return new JS.Call(jsTarget, args);
2358+
}
2359+
2360+
/// Emits a function call, to a top-level function, local function, or
2361+
/// an expression.
2362+
JS.Expression _emitFunctionCall(InvocationExpression node) {
2363+
var fn = _visit(node.function);
2364+
var args = _visit(node.argumentList);
2365+
if (DynamicInvoke.get(node.function)) {
2366+
var typeArgs = _emitInvokeTypeArguments(node);
2367+
if (typeArgs != null) {
2368+
return js.call('dart.dgcall(#, [#], #)', [fn, typeArgs, args]);
2369+
} else {
2370+
return js.call('dart.dcall(#, #)', [fn, args]);
2371+
}
23072372
} else {
2308-
code = '#.#(#)';
2373+
return new JS.Call(_applyInvokeTypeArguments(fn, node), args);
2374+
}
2375+
}
2376+
2377+
JS.Expression _applyInvokeTypeArguments(
2378+
JS.Expression target, InvocationExpression node) {
2379+
var typeArgs = _emitInvokeTypeArguments(node);
2380+
if (typeArgs == null) return target;
2381+
return new JS.Call(target, typeArgs);
2382+
}
2383+
2384+
List<JS.Expression> _emitInvokeTypeArguments(InvocationExpression node) {
2385+
return _emitFunctionTypeArguments(
2386+
node.function.staticType, node.staticInvokeType);
2387+
}
2388+
2389+
/// If `g` is a generic function type, and `f` is an instantiation of it,
2390+
/// then this will return the type arguments to apply, otherwise null.
2391+
List<JS.Expression> _emitFunctionTypeArguments(DartType g, DartType f) {
2392+
if (g is FunctionType &&
2393+
g.typeFormals.isNotEmpty &&
2394+
f is FunctionType &&
2395+
f.typeFormals.isEmpty) {
2396+
return _recoverTypeArguments(g, f)
2397+
.map(_emitTypeName)
2398+
.toList(growable: false);
23092399
}
2400+
return null;
2401+
}
23102402

2311-
return js
2312-
.call(code, [_visit(target), memberName, _visit(node.argumentList)]);
2403+
/// Given a generic function type [g] and an instantiated function type [f],
2404+
/// find a list of type arguments TArgs such that `g<TArgs> == f`,
2405+
/// and return TArgs.
2406+
///
2407+
/// This function must be called with type [f] that was instantiated from [g].
2408+
Iterable<DartType> _recoverTypeArguments(FunctionType g, FunctionType f) {
2409+
// TODO(jmesserly): this design is a bit unfortunate. It would be nice if
2410+
// resolution could simply create a synthetic type argument list.
2411+
assert(identical(g.element, f.element));
2412+
assert(g.typeFormals.isNotEmpty && f.typeFormals.isEmpty);
2413+
assert(g.typeFormals.length + g.typeArguments.length ==
2414+
f.typeArguments.length);
2415+
2416+
// Instantiation in Analyzer works like this:
2417+
// Given:
2418+
// {U/T} <S> T -> S
2419+
// Where {U/T} represents the typeArguments (U) and typeParameters (T) list,
2420+
// and <S> represents the typeFormals.
2421+
//
2422+
// Now instantiate([V]), and the result should be:
2423+
// {U/T, V/S} T -> S.
2424+
//
2425+
// Therefore, we can recover the typeArguments from our instantiated
2426+
// function.
2427+
return f.typeArguments.skip(g.typeArguments.length);
23132428
}
23142429

2315-
/// Emits code for the `JS(...)` builtin.
2430+
/// Emits code for the `JS(...)` macro.
23162431
_emitForeignJS(MethodInvocation node) {
23172432
var e = node.methodName.staticElement;
23182433
if (isInlineJS(e)) {
@@ -2351,15 +2466,8 @@ class CodeGenerator extends GeneralizingAstVisitor
23512466

23522467
@override
23532468
JS.Expression visitFunctionExpressionInvocation(
2354-
FunctionExpressionInvocation node) {
2355-
var code;
2356-
if (DynamicInvoke.get(node.function)) {
2357-
code = 'dart.dcall(#, #)';
2358-
} else {
2359-
code = '#(#)';
2360-
}
2361-
return js.call(code, [_visit(node.function), _visit(node.argumentList)]);
2362-
}
2469+
FunctionExpressionInvocation node) =>
2470+
_emitFunctionCall(node);
23632471

23642472
@override
23652473
List<JS.Expression> visitArgumentList(ArgumentList node) {
@@ -3689,9 +3797,7 @@ class CodeGenerator extends GeneralizingAstVisitor
36893797
return result;
36903798
}
36913799

3692-
// TODO(jmesserly): this will need to be a generic method, if we ever want to
3693-
// self-host strong mode.
3694-
List/*<T>*/ _visitList/*<T>*/(Iterable<AstNode> nodes) {
3800+
List/*<T>*/ _visitList/*<T extends AstNode>*/(Iterable/*<T>*/ nodes) {
36953801
if (nodes == null) return null;
36963802
var result = /*<T>*/ [];
36973803
for (var node in nodes) result.add(_visit(node));
@@ -3829,7 +3935,9 @@ class CodeGenerator extends GeneralizingAstVisitor
38293935
() => new JS.TemporaryId(jsLibraryName(_buildRoot, library)));
38303936
}
38313937

3832-
JS.Node annotate(JS.Node node, AstNode original, [Element element]) {
3938+
JS.Node/*=T*/ annotate/*<T extends JS.Node>*/(
3939+
JS.Node/*=T*/ node, AstNode original,
3940+
[Element element]) {
38333941
if (options.closure && element != null) {
38343942
node = node.withClosureAnnotation(closureAnnotationFor(
38353943
node, original, element, namedArgumentTemp.name));

0 commit comments

Comments
 (0)