Skip to content

Commit

Permalink
instantiate generic tear-offs, fixes #525
Browse files Browse the repository at this point in the history
  • Loading branch information
John Messerly committed Apr 29, 2016
1 parent 428cb00 commit 7337734
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 41 deletions.
2 changes: 1 addition & 1 deletion pkg/dev_compiler/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ module.exports = function(config) {
},
},

browsers: ['ChromeCanary', 'Electron'],
browsers: ['Chrome'],

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
Expand Down
18 changes: 12 additions & 6 deletions pkg/dev_compiler/lib/runtime/dart_sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ dart_library.library('dart_sdk', null, /* Imports */[
dart.tag(f, sig);
return f;
};
dart.gbind = function(f, ...typeArgs) {
let result = f(...typeArgs);
let sig = dart._getRuntimeType(f)(...typeArgs);
dart.tag(result, sig);
return result;
};
dart._setMethodSignature = function(f, sigF) {
dart.defineMemoizedGetter(f, dart._methodSig, () => {
let sigObj = sigF();
Expand Down Expand Up @@ -462,7 +468,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
}
}
if (ftype === void 0) {
ftype = dart.read(f);
ftype = dart._getRuntimeType(f);
}
if (!ftype) {
if (typeArgs != null) {
Expand Down Expand Up @@ -490,10 +496,10 @@ dart_library.library('dart_sdk', null, /* Imports */[
dart.throwNoSuchMethodFunc(obj, name, args, originalFunction);
};
dart.dcall = function(f, ...args) {
return dart._checkAndCall(f, dart.read(f), void 0, null, args, 'call');
return dart._checkAndCall(f, dart._getRuntimeType(f), void 0, null, args, 'call');
};
dart.dgcall = function(f, typeArgs, ...args) {
return dart._checkAndCall(f, dart.read(f), void 0, typeArgs, args, 'call');
return dart._checkAndCall(f, dart._getRuntimeType(f), void 0, typeArgs, args, 'call');
};
dart._callMethod = function(obj, name, typeArgs, args, displayName) {
let symbol = dart._canonicalFieldName(obj, name, args, displayName);
Expand Down Expand Up @@ -758,7 +764,7 @@ dart_library.library('dart_sdk', null, /* Imports */[
}
return result;
};
dart.read = function(value) {
dart._getRuntimeType = function(value) {
return value[dart._runtimeType];
};
dart.tag = function(value, t) {
Expand Down Expand Up @@ -964,11 +970,11 @@ dart_library.library('dart_sdk', null, /* Imports */[
return new dart.Typedef(name, closure);
};
dart.isDartType = function(type) {
return dart.read(type) === core.Type;
return dart._getRuntimeType(type) === core.Type;
};
dart.typeName = function(type) {
if (type instanceof dart.TypeRep) return type.toString();
let tag = dart.read(type);
let tag = dart._getRuntimeType(type);
if (tag === core.Type) {
let name = type.name;
let args = dart.getGenericArgs(type);
Expand Down
92 changes: 66 additions & 26 deletions pkg/dev_compiler/lib/src/compiler/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,11 @@ class CodeGenerator extends GeneralizingAstVisitor
}

@override
JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type);
JS.Expression visitTypeName(TypeName node) {
// TODO(jmesserly): should only happen for erroneous code.
if (node.type == null) return js.call('dart.dynamic');
return _emitTypeName(node.type);
}

@override
JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
Expand Down Expand Up @@ -1479,7 +1483,7 @@ class CodeGenerator extends GeneralizingAstVisitor
for (var p in ctor.parameters.parameters) {
var element = p.element;
if (element is FieldFormalParameterElement) {
fields[element.field] = visitSimpleIdentifier(p.identifier);
fields[element.field] = _emitSimpleIdentifier(p.identifier);
}
}

Expand Down Expand Up @@ -1542,7 +1546,7 @@ class CodeGenerator extends GeneralizingAstVisitor

var body = <JS.Statement>[];
for (var param in parameters.parameters) {
var jsParam = visitSimpleIdentifier(param.identifier);
var jsParam = _emitSimpleIdentifier(param.identifier);

if (!options.destructureNamedParams) {
if (param.kind == ParameterKind.NAMED) {
Expand Down Expand Up @@ -1978,10 +1982,18 @@ class CodeGenerator extends GeneralizingAstVisitor
]);
}

/// Writes a simple identifier. This can handle implicit `this` as well as
/// going through the qualified library name if necessary.
/// Emits a simple identifier, including handling an inferred generic
/// function instantiation.
@override
JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
return _applyFunctionTypeArguments(
_emitSimpleIdentifier(node), node.staticElement, node.staticType);
}

/// Emits a simple identifier, handling implicit `this` as well as
/// going through the qualified library name if necessary, but *not* handling
/// inferred generic function instantiation.
JS.Expression _emitSimpleIdentifier(SimpleIdentifier node) {
var accessor = node.staticElement;
if (accessor == null) {
return js.commentExpression(
Expand Down Expand Up @@ -2334,8 +2346,8 @@ class CodeGenerator extends GeneralizingAstVisitor
List<JS.Expression> args = _visit(node.argumentList);
if (DynamicInvoke.get(target)) {
if (typeArgs != null) {
return js.call('dart.dgsend(#, [#], #, #)',
[jsTarget, typeArgs, memberName, args]);
return js.call('dart.dgsend(#, #, #, #)',
[jsTarget, new JS.ArrayInitializer(typeArgs), memberName, args]);
} else {
return js.call('dart.dsend(#, #, #)', [jsTarget, memberName, args]);
}
Expand Down Expand Up @@ -2367,7 +2379,8 @@ class CodeGenerator extends GeneralizingAstVisitor
if (DynamicInvoke.get(node.function)) {
var typeArgs = _emitInvokeTypeArguments(node);
if (typeArgs != null) {
return js.call('dart.dgcall(#, [#], #)', [fn, typeArgs, args]);
return js.call('dart.dgcall(#, #, #)',
[fn, new JS.ArrayInitializer(typeArgs), args]);
} else {
return js.call('dart.dcall(#, #)', [fn, args]);
}
Expand All @@ -2385,19 +2398,26 @@ class CodeGenerator extends GeneralizingAstVisitor

List<JS.Expression> _emitInvokeTypeArguments(InvocationExpression node) {
return _emitFunctionTypeArguments(
node.function.staticType, node.staticInvokeType);
node.function.staticType, node.staticInvokeType, node.typeArguments);
}

/// If `g` is a generic function type, and `f` is an instantiation of it,
/// then this will return the type arguments to apply, otherwise null.
List<JS.Expression> _emitFunctionTypeArguments(DartType g, DartType f) {
List<JS.Expression> _emitFunctionTypeArguments(DartType g, DartType f,
[TypeArgumentList typeArgs]) {
if (g is FunctionType &&
g.typeFormals.isNotEmpty &&
f is FunctionType &&
f.typeFormals.isEmpty) {
return _recoverTypeArguments(g, f)
.map(_emitTypeName)
.toList(growable: false);
} else if (typeArgs != null) {
// Dynamic calls may have type arguments, even though the function types
// are not known.
// TODO(jmesserly): seems to be mostly broken in Analyzer at the moment:
// https://github.com/dart-lang/sdk/issues/26368
return typeArgs.arguments.map(visitTypeName).toList(growable: false);
}
return null;
}
Expand Down Expand Up @@ -3365,7 +3385,7 @@ class CodeGenerator extends GeneralizingAstVisitor
if (isLibraryPrefix(node.prefix)) {
return _visit(node.identifier);
} else {
return _emitAccess(node.prefix, node.identifier);
return _emitAccess(node.prefix, node.identifier, node.staticType);
}
}

Expand All @@ -3374,7 +3394,7 @@ class CodeGenerator extends GeneralizingAstVisitor
if (node.operator.lexeme == '?.') {
return _emitNullSafe(node);
}
return _emitAccess(_getTarget(node), node.propertyName);
return _emitAccess(_getTarget(node), node.propertyName, node.staticType);
}

JS.Expression _emitNullSafe(Expression node) {
Expand Down Expand Up @@ -3451,8 +3471,9 @@ class CodeGenerator extends GeneralizingAstVisitor
}

/// Shared code for [PrefixedIdentifier] and [PropertyAccess].
JS.Expression _emitAccess(Expression target, SimpleIdentifier memberId) {
var member = memberId.staticElement;
JS.Expression _emitAccess(
Expression target, SimpleIdentifier memberId, DartType resultType) {
Element member = memberId.staticElement;
if (member is PropertyAccessorElement) {
member = (member as PropertyAccessorElement).variable;
}
Expand All @@ -3463,30 +3484,49 @@ class CodeGenerator extends GeneralizingAstVisitor
return js.call('dart.dload(#, #)', [_visit(target), name]);
}

if (target is SuperExpression &&
member is FieldElement &&
!member.isSynthetic) {
var jsTarget = _visit(target);
bool isSuper = jsTarget is JS.Super;

if (isSuper && member is FieldElement && !member.isSynthetic) {
// If super.x is actually a field, then x is an instance property since
// subclasses cannot override x.
return js.call('this.#', [name]);
jsTarget = new JS.This();
}

String code;
JS.Expression result;
if (member != null && member is MethodElement && !isStatic) {
// Tear-off methods: explicitly bind it.
if (target is SuperExpression) {
return js.call('dart.bind(this, #, #.#)', [name, _visit(target), name]);
if (isSuper) {
result = js.call('dart.bind(this, #, #.#)', [name, jsTarget, name]);
} else if (_isObjectMemberCall(target, memberId.name)) {
return js.call('dart.bind(#, #, dart.#)', [_visit(target), name, name]);
result = js.call('dart.bind(#, #, dart.#)', [jsTarget, name, name]);
} else {
result = js.call('dart.bind(#, #)', [jsTarget, name]);
}
code = 'dart.bind(#, #)';
} else if (_isObjectMemberCall(target, memberId.name)) {
return js.call('dart.#(#)', [name, _visit(target)]);
result = js.call('dart.#(#)', [name, jsTarget]);
} else {
code = '#.#';
result = js.call('#.#', [jsTarget, name]);
}
return _applyFunctionTypeArguments(result, member, resultType);
}

/// If this is an inferred instantiation of a generic function/method, this
/// will add the inferred type arguments.
JS.Expression _applyFunctionTypeArguments(
JS.Expression result, Element member, DartType instantiated) {
DartType type;
if (member is ExecutableElement) {
type = member.type;
} else if (member is VariableElement) {
type = member.type;
}

return js.call(code, [_visit(target), name]);
// TODO(jmesserly): handle explicitly passed type args.
if (type == null) return result;
var typeArgs = _emitFunctionTypeArguments(type, instantiated);
if (typeArgs == null) return result;
return js.call('dart.gbind(#, #)', [result, typeArgs]);
}

/// Emits a generic send, like an operator method.
Expand Down
44 changes: 44 additions & 0 deletions pkg/dev_compiler/test/codegen/language/generic_tearoff_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2016, 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 'dart:math' as math;
import 'dart:math' show min; // <-- generic: <T extends num>(T, T) -> T
import 'package:expect/expect.dart';

class C {
/*=T*/ m/*<T extends num>*/(/*=T*/ x, /*=T*/ y) => min(x, y);
int m2(int x, int y) => min(x, y);
}

typedef int Int2Int2Int(int x, int y);

void _test(Int2Int2Int f) {
int y = f(123, 456);
Expect.equals(y, 123);
// `f` doesn't take type args.
Expect.throws(() => (f as dynamic)/*<int>*/(123, 456));
}

void _testParam(/*=T*/ minFn/*<T extends num>*/(/*=T*/ x, /*=T*/ y)) {
_test(minFn);
}

main() {
// Strong mode infers: `min<int>`
// Test simple/prefixed identifiers and property access
_test(min);
_test(math.min);
_test(new C().m);

// Test local function, variable, and parameter
/*=T*/ m/*<T extends num>*/(/*=T*/ x, /*=T*/ y) => min(x, y);
_test(m);
final f = min;
_test(f);
_testParam(math.min);

// A few misc tests for methods
Expect.equals(123, (new C() as dynamic).m/*<int>*/(123, 456));
Expect.throws(() => (new C() as dynamic).m2/*<int>*/(123, 456));
}
11 changes: 11 additions & 0 deletions pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ bind(obj, name, f) => JS('', '''(() => {
return $f;
})()''');

/// Instantiate a generic method.
///
/// We need to apply the type arguments both to the function, as well as its
/// associated function type.
gbind(f, @rest typeArgs) {
var result = JS('', '#(...#)', f, typeArgs);
var sig = JS('', '#(...#)', _getRuntimeType(f), typeArgs);
tag(result, sig);
return result;
}

// Set up the method signature field on the constructor
_setMethodSignature(f, sigF) => JS('', '''(() => {
$defineMemoizedGetter($f, $_methodSig, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ _checkAndCall(f, ftype, obj, typeArgs, args, name) => JS('', '''(() => {
// then it should have been a function valued field, so
// get the type from the function.
if ($ftype === void 0) {
$ftype = $read($f);
$ftype = $_getRuntimeType($f);
}
if (!$ftype) {
Expand Down Expand Up @@ -150,12 +150,12 @@ _checkAndCall(f, ftype, obj, typeArgs, args, name) => JS('', '''(() => {
$throwNoSuchMethodFunc($obj, $name, $args, originalFunction);
})()''');

dcall(f, @rest args) =>
_checkAndCall(f, read(f), JS('', 'void 0'), null, args, 'call');
dcall(f, @rest args) => _checkAndCall(
f, _getRuntimeType(f), JS('', 'void 0'), null, args, 'call');


dgcall(f, typeArgs, @rest args) =>
_checkAndCall(f, read(f), JS('', 'void 0'), typeArgs, args, 'call');
dgcall(f, typeArgs, @rest args) => _checkAndCall(
f, _getRuntimeType(f), JS('', 'void 0'), typeArgs, args, 'call');


/// Shared code for dsend, dindex, and dsetindex.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ _nonPrimitiveRuntimeType(obj) => JS('', '''(() => {
return result;
})()''');

read(value) => JS('', '#[#]', value, _runtimeType);
_getRuntimeType(value) => JS('', '#[#]', value, _runtimeType);

/// Tag the runtime type of [value] to be type [t].
void tag(value, t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,13 @@ definiteFunctionType(returnType, args, extra) =>

typedef(name, closure) => JS('', 'new #(#, #)', Typedef, name, closure);

bool isDartType(type) => JS('bool', '#(#) === #', read, type, Type);
bool isDartType(type) => JS('bool', '#(#) === #', _getRuntimeType, type, Type);

typeName(type) => JS('', '''(() => {
// Non-instance types
if ($type instanceof $TypeRep) return $type.toString();
// Instance types
let tag = $read($type);
let tag = $_getRuntimeType($type);
if (tag === $Type) {
let name = $type.name;
let args = $getGenericArgs($type);
Expand Down

0 comments on commit 7337734

Please sign in to comment.