Skip to content

Commit

Permalink
allow 'super' in async and finally blocks
Browse files Browse the repository at this point in the history
Fixes #499
Fixes #528

R=jmesserly@google.com

Review URL: https://codereview.chromium.org/1948563002 .
  • Loading branch information
harryterkelsen committed May 6, 2016
1 parent 7d32105 commit d9e0533
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 30 deletions.
2 changes: 1 addition & 1 deletion pkg/dev_compiler/lib/runtime/dart_sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -25019,8 +25019,8 @@ dart_library.library('dart_sdk', null, /* Imports */[
return _ConverterStreamEventSink;
});
convert._ConverterStreamEventSink = convert._ConverterStreamEventSink$();
const _first$ = Symbol('_first');
const _second = Symbol('_second');
const _first$ = Symbol('_first');
convert._FusedCodec$ = dart.generic((S, M, T) => {
class _FusedCodec extends convert.Codec$(S, T) {
get encoder() {
Expand Down
157 changes: 129 additions & 28 deletions pkg/dev_compiler/lib/src/compiler/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ class CodeGenerator extends GeneralizingAstVisitor

String _buildRoot;

bool _superAllowed = true;

List<JS.TemporaryId> _superHelperSymbols = <JS.TemporaryId>[];
List<JS.Method> _superHelpers = <JS.Method>[];

/// Whether we are currently generating code for the body of a `JS()` call.
bool _isInForeignJS = false;

Expand Down Expand Up @@ -595,6 +600,7 @@ class CodeGenerator extends GeneralizingAstVisitor
var body = <JS.Statement>[];
var extensions = _extensionsToImplement(classElem);
_initExtensionSymbols(classElem, methods, fields, body);
_emitSuperHelperSymbols(_superHelperSymbols, body);

// Emit the class, e.g. `core.Object = class Object { ... }`
_defineClass(classElem, className, classExpr, body);
Expand All @@ -619,6 +625,14 @@ class CodeGenerator extends GeneralizingAstVisitor
return _statement(body);
}

void _emitSuperHelperSymbols(
List<JS.TemporaryId> superHelperSymbols, List<JS.Statement> body) {
for (var id in superHelperSymbols) {
body.add(js.statement('const # = Symbol(#)', [id, js.string(id.name)]));
}
superHelperSymbols.clear();
}

void _registerPropertyOverrides(
ClassElement classElem,
JS.Expression className,
Expand Down Expand Up @@ -897,6 +911,10 @@ class CodeGenerator extends GeneralizingAstVisitor
jsMethods.add(_emitIterable(type));
}

// Add all of the super helper methods
jsMethods.addAll(_superHelpers);
_superHelpers.clear();

return jsMethods.where((m) => m != null).toList(growable: false);
}

Expand Down Expand Up @@ -1630,7 +1648,7 @@ class CodeGenerator extends GeneralizingAstVisitor
}
}

JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
JS.Method _emitMethodDeclaration(InterfaceType type, MethodDeclaration node) {
if (node.isAbstract) {
return null;
}
Expand Down Expand Up @@ -1956,8 +1974,11 @@ class CodeGenerator extends GeneralizingAstVisitor
} else {
_asyncStarController = null;
}
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
// Visit the body with our async* controller set.
var jsBody = _visit(body);
_superAllowed = savedSuperAllowed;
_asyncStarController = savedController;

DartType returnType = _getExpectedReturnType(element);
Expand Down Expand Up @@ -2003,8 +2024,12 @@ class CodeGenerator extends GeneralizingAstVisitor
/// function instantiation.
@override
JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
return _applyFunctionTypeArguments(
_emitSimpleIdentifier(node), node.staticElement, node.staticType);
var typeArgs = _getTypeArgs(node.staticElement, node.staticType);
var simpleId = _emitSimpleIdentifier(node);
if (typeArgs == null) {
return simpleId;
}
return js.call('dart.gbind(#, #)', [simpleId, typeArgs]);
}

/// Emits a simple identifier, handling implicit `this` as well as
Expand Down Expand Up @@ -2384,17 +2409,66 @@ class CodeGenerator extends GeneralizingAstVisitor
return _emitMethodCall(target, node);
}

/// Emits a (possibly generic) instance method call.
JS.Expression _emitMethodCall(Expression target, MethodInvocation node) {
List<JS.Expression> args = _visit(node.argumentList);
var typeArgs = _emitInvokeTypeArguments(node);

if (target is SuperExpression && !_superAllowed) {
return _emitSuperHelperCall(typeArgs, args, target, node);
}

return _emitMethodCallInternal(target, node, args, typeArgs);
}

JS.Expression _emitSuperHelperCall(List<JS.Expression> typeArgs,
List<JS.Expression> args, SuperExpression target, MethodInvocation node) {
var fakeTypeArgs =
typeArgs?.map((_) => new JS.TemporaryId('a'))?.toList(growable: false);
var fakeArgs =
args.map((_) => new JS.TemporaryId('a')).toList(growable: false);
var combinedFakeArgs = <JS.TemporaryId>[];
if (fakeTypeArgs != null) {
combinedFakeArgs.addAll(fakeTypeArgs);
}
combinedFakeArgs.addAll(fakeArgs);

var forwardedCall =
_emitMethodCallInternal(target, node, fakeArgs, fakeTypeArgs);
var superForwarder = _getSuperHelperFor(
node.methodName.name, forwardedCall, combinedFakeArgs);

var combinedRealArgs = <JS.Expression>[];
if (typeArgs != null) {
combinedRealArgs.addAll(typeArgs);
}
combinedRealArgs.addAll(args);

return js.call('this.#(#)', [superForwarder, combinedRealArgs]);
}

JS.Expression _getSuperHelperFor(String name, JS.Expression forwardedCall,
List<JS.Expression> helperArgs) {
var helperMethod =
new JS.Fun(helperArgs, new JS.Block([new JS.Return(forwardedCall)]));
var helperMethodName = new JS.TemporaryId('super\$$name');
_superHelperSymbols.add(helperMethodName);
_superHelpers.add(new JS.Method(helperMethodName, helperMethod));
return helperMethodName;
}

/// Emits a (possibly generic) instance method call.
JS.Expression _emitMethodCallInternal(
Expression target,
MethodInvocation node,
List<JS.Expression> args,
List<JS.Expression> typeArgs) {
var type = getStaticType(target);
var name = node.methodName.name;
var element = node.methodName.staticElement;
bool isStatic = element is ExecutableElement && element.isStatic;
var memberName = _emitMemberName(name, type: type, isStatic: isStatic);

JS.Expression jsTarget = _visit(target);
var typeArgs = _emitInvokeTypeArguments(node);
List<JS.Expression> args = _visit(node.argumentList);
if (DynamicInvoke.get(target)) {
if (typeArgs != null) {
return js.call('dart.dgsend(#, #, #, #)',
Expand All @@ -2410,6 +2484,7 @@ class CodeGenerator extends GeneralizingAstVisitor
}

jsTarget = new JS.PropertyAccess(jsTarget, memberName);

if (typeArgs != null) jsTarget = new JS.Call(jsTarget, typeArgs);

if (DynamicInvoke.get(node.methodName)) {
Expand Down Expand Up @@ -3544,8 +3619,45 @@ class CodeGenerator extends GeneralizingAstVisitor
if (member is PropertyAccessorElement) {
member = (member as PropertyAccessorElement).variable;
}
String memberName = memberId.name;
var typeArgs = _getTypeArgs(member, resultType);

if (target is SuperExpression && !_superAllowed) {
return _emitSuperHelperAccess(target, member, memberName, typeArgs);
}
return _emitAccessInternal(target, member, memberName, typeArgs);
}

JS.Expression _emitSuperHelperAccess(SuperExpression target, Element member,
String memberName, List<JS.Expression> typeArgs) {
var fakeTypeArgs =
typeArgs?.map((_) => new JS.TemporaryId('a'))?.toList(growable: false);

var forwardedAccess =
_emitAccessInternal(target, member, memberName, fakeTypeArgs);
var superForwarder = _getSuperHelperFor(
memberName, forwardedAccess, fakeTypeArgs ?? const []);

return js.call('this.#(#)', [superForwarder, typeArgs ?? const []]);
}

List<JS.Expression> _getTypeArgs(Element member, DartType instantiated) {
DartType type;
if (member is ExecutableElement) {
type = member.type;
} else if (member is VariableElement) {
type = member.type;
}

// TODO(jmesserly): handle explicitly passed type args.
if (type == null) return null;
return _emitFunctionTypeArguments(type, instantiated);
}

JS.Expression _emitAccessInternal(Expression target, Element member,
String memberName, List<JS.Expression> typeArgs) {
bool isStatic = member is ClassMemberElement && member.isStatic;
var name = _emitMemberName(memberId.name,
var name = _emitMemberName(memberName,
type: getStaticType(target), isStatic: isStatic);
if (DynamicInvoke.get(target)) {
return js.call('dart.dload(#, #)', [_visit(target), name]);
Expand All @@ -3565,34 +3677,19 @@ class CodeGenerator extends GeneralizingAstVisitor
// Tear-off methods: explicitly bind it.
if (isSuper) {
result = js.call('dart.bind(this, #, #.#)', [name, jsTarget, name]);
} else if (_isObjectMemberCall(target, memberId.name)) {
} else if (_isObjectMemberCall(target, memberName)) {
result = js.call('dart.bind(#, #, dart.#)', [jsTarget, name, name]);
} else {
result = js.call('dart.bind(#, #)', [jsTarget, name]);
}
} else if (_isObjectMemberCall(target, memberId.name)) {
} else if (_isObjectMemberCall(target, memberName)) {
result = js.call('dart.#(#)', [name, jsTarget]);
} else {
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;
if (typeArgs == null) {
return result;
}

// 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]);
}

Expand Down Expand Up @@ -3790,8 +3887,12 @@ class CodeGenerator extends GeneralizingAstVisitor

@override
visitTryStatement(TryStatement node) {
return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses),
_visit(node.finallyBlock));
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
var finallyBlock = _visit(node.finallyBlock);
_superAllowed = savedSuperAllowed;
return new JS.Try(
_visit(node.body), _visitCatch(node.catchClauses), finallyBlock);
}

_visitCatch(NodeList<CatchClause> clauses) {
Expand Down
1 change: 0 additions & 1 deletion pkg/dev_compiler/lib/src/compiler/source_map_printer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright (c) 2015, 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.

Expand Down
22 changes: 22 additions & 0 deletions pkg/dev_compiler/test/codegen/language/super_in_async1_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 "package:expect/expect.dart";

import 'dart:async';

class A {
Future<int> foo() async => 42;
}

class B extends A {
Future<int> foo() async {
var x = await super.foo();
return x + 1;
}
}

main() async {
Expect.equals(43, await new B().foo());
}
23 changes: 23 additions & 0 deletions pkg/dev_compiler/test/codegen/language/super_in_async2_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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 "package:expect/expect.dart";

import 'dart:async';

class A {
Future<int> foo() async => 42;
}

class B extends A {
Future<int> foo() async {
var x = await super.foo();
var y = await super.foo();
return x + y;
}
}

main() async {
Expect.equals(84, await new B().foo());
}
22 changes: 22 additions & 0 deletions pkg/dev_compiler/test/codegen/language/super_in_async3_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 "package:expect/expect.dart";

import 'dart:async';

class A {
Future/*<T>*/ foo/*<T>*/(/*=T*/ x) async => x;
}

class B extends A {
Future<int> bar() async {
var x = await super.foo(41);
return x + 1;
}
}

main() async {
Expect.equals(42, await new B().bar());
}
22 changes: 22 additions & 0 deletions pkg/dev_compiler/test/codegen/language/super_in_async4_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 "package:expect/expect.dart";

import 'dart:async';

class A {
Future/*<T>*/ foo/*<T>*/({/*=T*/ x}) async => x;
}

class B extends A {
Future<int> bar() async {
var x = await super.foo(x: 41);
return x + 1;
}
}

main() async {
Expect.equals(42, await new B().bar());
}
22 changes: 22 additions & 0 deletions pkg/dev_compiler/test/codegen/language/super_in_async5_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 "package:expect/expect.dart";

import 'dart:async';

class A {
Future<int> get foo async => 42;
}

class B extends A {
Future<int> bar() async {
var x = await super.foo;
return x + 1;
}
}

main() async {
Expect.equals(43, await new B().bar());
}
Loading

0 comments on commit d9e0533

Please sign in to comment.