Add tests for handling closures in LocalInitializers

BUG=https://github.com/dart-lang/sdk/issues/29887
R=sjindel@google.com

Review-Url: https://codereview.chromium.org/2944433002 .
This commit is contained in:
Dmitry Stefantsov 2017-07-06 16:39:15 +02:00
parent 42fd6cfd77
commit b1e629f078
8 changed files with 393 additions and 13 deletions

View file

@ -0,0 +1,66 @@
// Copyright (c) 2017, 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.
library kernel.transformations.argument_extraction_for_redirecting;
import '../ast.dart'
show
Program,
Constructor,
RedirectingInitializer,
Library,
LocalInitializer,
VariableDeclaration,
VariableGet,
Expression,
NamedExpression;
import '../core_types.dart' show CoreTypes;
import '../visitor.dart' show Transformer;
Program transformProgram(CoreTypes coreTypes, Program program) {
new ArgumentExtractionForRedirecting().visitProgram(program);
return program;
}
void transformLibraries(CoreTypes coreTypes, List<Library> libraries) {
var transformer = new ArgumentExtractionForRedirecting();
for (var library in libraries) {
transformer.visitLibrary(library);
}
}
class ArgumentExtractionForRedirecting extends Transformer {
visitConstructor(Constructor node) {
if (node.initializers.length == 1 &&
node.initializers[0] is RedirectingInitializer) {
int index = 0;
RedirectingInitializer redirectingInitializer = node.initializers[0];
List<Expression> positionalArguments =
redirectingInitializer.arguments.positional;
List<NamedExpression> namedArguments =
redirectingInitializer.arguments.named;
for (int i = 0; i < positionalArguments.length; i++) {
Expression argument = positionalArguments[i];
VariableDeclaration extractedArgument =
new VariableDeclaration("extracted#$index", initializer: argument);
LocalInitializer initializer = new LocalInitializer(extractedArgument)
..parent = node;
node.initializers.insert(index++, initializer);
positionalArguments[i] = new VariableGet(extractedArgument)
..parent = redirectingInitializer.arguments;
}
for (int i = 0; i < namedArguments.length; i++) {
Expression argument = namedArguments[i].value;
VariableDeclaration extractedArgument =
new VariableDeclaration("extracted#$index", initializer: argument);
LocalInitializer initializer = new LocalInitializer(extractedArgument)
..parent = node;
node.initializers.insert(index++, initializer);
namedArguments[i].value = new VariableGet(extractedArgument)
..parent = redirectingInitializer.arguments;
}
}
return super.visitConstructor(node);
}
}

View file

@ -70,7 +70,13 @@ import 'context.dart' show Context, NoContext;
import 'info.dart' show ClosureInfo;
import 'rewriter.dart' show AstRewriter, BlockRewriter, InitializerRewriter;
import 'rewriter.dart'
show
AstRewriter,
BlockRewriter,
InitializerRewriter,
FieldInitializerRewriter,
LocalInitializerRewriter;
class ClosureConverter extends Transformer {
final CoreTypes coreTypes;
@ -195,18 +201,47 @@ class ClosureConverter extends Transformer {
context.extend(parameter, new VariableGet(parameter));
}
static InitializerRewriter getRewriterForInitializer(
Initializer initializer) {
if (initializer is FieldInitializer) {
return new FieldInitializerRewriter(initializer.value);
}
if (initializer is LocalInitializer) {
return new LocalInitializerRewriter(initializer.variable.initializer);
}
throw "Trying to extract an initializer expression from "
"${initializer.runtimeType}, but only FieldInitializer and "
"LocalInitializer are supported.";
}
static Expression getInitializerExpression(Initializer initializer) {
if (initializer is FieldInitializer) {
return initializer.value;
}
if (initializer is LocalInitializer) {
return initializer.variable.initializer;
}
throw "Trying to get initializing expressino from "
"${initializer.runtimeType}, but only Field Initializer and "
"LocalInitializer are supported.";
}
TreeNode visitConstructor(Constructor node) {
assert(isEmptyContext);
currentMember = node;
// Transform initializers.
for (Initializer initializer in node.initializers) {
if (initializer is FieldInitializer) {
if (initializer is FieldInitializer || initializer is LocalInitializer) {
// Create a rewriter and a context for the initializer expression.
rewriter = new InitializerRewriter(initializer.value);
InitializerRewriter initializerRewriter =
getRewriterForInitializer(initializer);
rewriter = initializerRewriter;
context = new NoContext(this);
// Save the expression to visit it in the extended context, since the
// rewriter will modify `initializer.value`.
Expression initializerExpression = initializer.value;
// rewriter will modify `initializer.value` (for [FieldInitializer]) or
// `initializer.variable.initializer` (for [LocalInitializer]).
Expression initializerExpression =
getInitializerExpression(initializer);
// Extend the context with all captured parameters of the constructor.
// TODO(karlklose): add a fine-grained analysis of captured parameters.
node.function.positionalParameters
@ -223,9 +258,11 @@ class ClosureConverter extends Transformer {
parent.body = initializerExpression;
} else if (parent is FieldInitializer) {
parent.value = initializerExpression;
} else if (parent is LocalInitializer) {
parent.variable.initializer = initializerExpression;
} else {
throw "Found unexpected node '${node.runtimeType}, expected a 'Let' "
"or a 'FieldInitializer'.";
",a 'FieldInitializer', or a 'LocalInitializer'.";
}
}
}

View file

@ -87,12 +87,10 @@ class BlockRewriter extends AstRewriter {
/// Creates and updates the context as [Let] bindings around the initializer
/// expression.
class InitializerRewriter extends AstRewriter {
abstract class InitializerRewriter extends AstRewriter {
final Expression initializingExpression;
InitializerRewriter(this.initializingExpression) {
assert(initializingExpression.parent is FieldInitializer);
}
InitializerRewriter(this.initializingExpression);
@override
BlockRewriter forNestedBlock(Block block) {
@ -102,11 +100,9 @@ class InitializerRewriter extends AstRewriter {
@override
void insertContextDeclaration(Expression accessParent) {
_createDeclaration();
FieldInitializer parent = initializingExpression.parent;
Let binding = new Let(contextDeclaration, initializingExpression);
setInitializerExpression(binding);
initializingExpression.parent = binding;
parent.value = binding;
binding.parent = parent;
}
@override
@ -117,4 +113,34 @@ class InitializerRewriter extends AstRewriter {
parent.body = binding;
binding.parent = parent;
}
void setInitializerExpression(Expression expression);
}
class FieldInitializerRewriter extends InitializerRewriter {
FieldInitializerRewriter(Expression initializingExpression)
: super(initializingExpression) {
assert(initializingExpression.parent is FieldInitializer);
}
void setInitializerExpression(Expression expression) {
assert(initializingExpression.parent is FieldInitializer);
FieldInitializer parent = initializingExpression.parent;
parent.value = expression;
expression.parent = parent;
}
}
class LocalInitializerRewriter extends InitializerRewriter {
LocalInitializerRewriter(Expression initializingExpression)
: super(initializingExpression) {
assert(initializingExpression.parent is LocalInitializer);
}
void setInitializerExpression(Expression expression) {
assert(initializingExpression.parent is LocalInitializer);
LocalInitializer parent = initializingExpression.parent;
parent.variable.initializer = expression;
expression.parent = parent;
}
}

View file

@ -0,0 +1,5 @@
# Copyright (c) 2017, 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.md file.
local_initializers: Crash

View file

@ -0,0 +1,172 @@
// Copyright (c) 2017, 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.md file.
library test.kernel.closures.suite;
import 'dart:async' show Future;
import 'package:front_end/physical_file_system.dart' show PhysicalFileSystem;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:testing/testing.dart'
show Chain, ChainContext, Result, Step, TestDescription, runMe;
import 'package:front_end/src/fasta/testing/patched_sdk_location.dart'
show computePatchedSdk;
import 'package:kernel/ast.dart' show Program, Library;
import 'package:kernel/transformations/argument_extraction_for_redirecting.dart'
as argument_extraction;
import 'package:kernel/transformations/closure_conversion.dart'
as closure_conversion;
import 'package:front_end/src/fasta/testing/kernel_chain.dart'
show Print, MatchExpectation, WriteDill, ReadDill, Verify;
import 'package:front_end/src/fasta/ticker.dart' show Ticker;
import 'package:front_end/src/fasta/dill/dill_target.dart' show DillTarget;
import 'package:front_end/src/fasta/kernel/kernel_target.dart'
show KernelTarget;
import 'package:front_end/src/fasta/translate_uri.dart' show TranslateUri;
import 'package:front_end/src/fasta/errors.dart' show InputError;
import 'package:front_end/src/fasta/testing/patched_sdk_location.dart';
import 'package:kernel/kernel.dart' show loadProgramFromBinary;
import 'package:kernel/target/targets.dart' show TargetFlags;
import 'package:kernel/target/vm_fasta.dart' show VmFastaTarget;
const String STRONG_MODE = " strong mode ";
class ClosureConversionContext extends ChainContext {
final bool strongMode;
final TranslateUri uriTranslator;
final List<Step> steps;
ClosureConversionContext(
this.strongMode, bool updateExpectations, this.uriTranslator)
: steps = <Step>[
const FastaCompile(),
const Print(),
const Verify(true),
const ArgumentExtraction(),
const Print(),
const Verify(true),
const ClosureConversion(),
const Print(),
const Verify(true),
new MatchExpectation(".expect",
updateExpectations: updateExpectations),
const WriteDill(),
const ReadDill(),
// TODO(29143): add `Run` step when Vectors are added to VM.
];
Future<Program> loadPlatform() async {
Uri sdk = await computePatchedSdk();
return loadProgramFromBinary(sdk.resolve('platform.dill').toFilePath());
}
static Future<ClosureConversionContext> create(
Chain suite, Map<String, String> environment) async {
Uri sdk = await computePatchedSdk();
Uri packages = Uri.base.resolve(".packages");
bool strongMode = environment.containsKey(STRONG_MODE);
bool updateExpectations = environment["updateExpectations"] == "true";
TranslateUri uriTranslator = await TranslateUri
.parse(PhysicalFileSystem.instance, sdk, packages: packages);
return new ClosureConversionContext(
strongMode, updateExpectations, uriTranslator);
}
}
Future<ClosureConversionContext> createContext(
Chain suite, Map<String, String> environment) async {
environment["updateExpectations"] =
const String.fromEnvironment("updateExpectations");
return ClosureConversionContext.create(suite, environment);
}
class FastaCompile
extends Step<TestDescription, Program, ClosureConversionContext> {
const FastaCompile();
String get name => "fasta compilation";
Future<Result<Program>> run(
TestDescription description, ClosureConversionContext context) async {
Program platform = await context.loadPlatform();
Ticker ticker = new Ticker();
DillTarget dillTarget = new DillTarget(ticker, context.uriTranslator,
new VmFastaTarget(new TargetFlags(strongMode: context.strongMode)));
platform.unbindCanonicalNames();
dillTarget.loader.appendLibraries(platform);
KernelTarget sourceTarget = new KernelTarget(
PhysicalFileSystem.instance, dillTarget, context.uriTranslator);
Program p;
try {
sourceTarget.read(description.uri);
await dillTarget.buildOutlines();
await sourceTarget.buildOutlines();
p = await sourceTarget.buildProgram();
} on InputError catch (e, s) {
return fail(null, e.error, s);
}
return pass(p);
}
}
class ArgumentExtraction
extends Step<Program, Program, ClosureConversionContext> {
const ArgumentExtraction();
String get name => "argument extraction";
Future<Result<Program>> run(
Program program, ClosureConversionContext context) async {
try {
CoreTypes coreTypes = new CoreTypes(program);
Library library = program.libraries
.firstWhere((Library library) => library.importUri.scheme != "dart");
argument_extraction.transformLibraries(coreTypes, <Library>[library]);
return pass(program);
} catch (e, s) {
return crash(e, s);
}
}
}
class ClosureConversion
extends Step<Program, Program, ClosureConversionContext> {
const ClosureConversion();
String get name => "closure conversion";
Future<Result<Program>> run(
Program program, ClosureConversionContext testContext) async {
try {
CoreTypes coreTypes = new CoreTypes(program);
Library library = program.libraries
.firstWhere((Library library) => library.importUri.scheme != "dart");
closure_conversion.transformLibraries(coreTypes, <Library>[library]);
return pass(program);
} catch (e, s) {
return crash(e, s);
}
}
}
main(List<String> arguments) => runMe(arguments, createContext, "testing.json");

View file

@ -0,0 +1,28 @@
{
"":"Copyright (c) 2017, 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.md file.",
"packages": "../../../../.packages",
"suites": [
{
"name": "closures_initializers",
"kind": "Chain",
"source": "suite.dart",
"path": "../../testcases/closures_initializers/",
"status": "closures_initializers.status",
"pattern": [
"\\.dart$"
],
"exclude": [
"/test/closures_initializers/suite\\.dart$"
]
}
],
"analyze": {
"uris": [
"suite.dart"
],
"exclude": [
]
}
}

View file

@ -0,0 +1,23 @@
// Copyright (c) 2017, 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.
// The purpose of this test is to detect that closures in [LocalInitializer]s
// are properly converted. This test assumes that
// [ArgumentExtractionForRedirecting] transformer was run before closure
// conversion. It should introduce one [LocalInitializer] for each argument
// passed to the redirecting constructor. If such argument contains a closure,
// it would appear in a [LocalInitializer].
class X {}
class A {
X foo;
A.named(X foo) {}
A(X foo) : this.named((() => foo)());
}
main() {
A a = new A(new X());
a.foo; // To prevent dartanalyzer from marking [a] as unused.
}

View file

@ -0,0 +1,23 @@
library;
import self as self;
import "dart:core" as core;
class X extends core::Object {
constructor •() → void
: super core::Object::•()
;
}
class A extends core::Object {
field self::X foo = null;
constructor named(self::X foo) → void
: super core::Object::•() {}
constructor •(self::X foo) → void
: dynamic extracted#0 = () → self::X
throw "Calling unconverted closure at pkg/kernel/testcases/closures_initializers/local_initializers.dart:17:26";
.call(), this self::A::named(extracted#0)
;
}
static method main() → dynamic {
self::A a = new self::A::•(new self::X::•());
a.foo;
}