Emit declarations for typedefs that are needed by reflection.

Typedefs are encoded like closure classes, but without a call-method and a superclass.

For example, the typedef `typedef int Foo(String s);` is emitted as:
  Foo: {'^': ':15'},
where `15` is the index into `init.metadata` at which the function type is emitted.

BUG= http://dartbug.com/16939
R=johnniwinther@google.com, floitsch@google.com, herhut@google.com

Committed: https://code.google.com/p/dart/source/detail?r=37814

Reverted in https://code.google.com/p/dart/source/detail?r=37848

Committed: https://code.google.com/p/dart/source/detail?r=37939

Reverted in https://code.google.com/p/dart/source/detail?r=37940

Committed: https://code.google.com/p/dart/source/detail?r=37945

Review URL: https://codereview.chromium.org//360493002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@38891 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
karlklose@google.com 2014-08-05 07:18:32 +00:00
parent ec6f1dfb64
commit b203f95355
15 changed files with 194 additions and 106 deletions

View file

@ -314,11 +314,13 @@ abstract class Enqueuer {
/// needed for reflection.
void enqueueReflectiveMember(Element element, bool enclosingWasIncluded) {
if (shouldIncludeElementDueToMirrors(element,
includedEnclosing: enclosingWasIncluded)
// Do not enqueue typedefs.
&& !element.impliesType) {
includedEnclosing: enclosingWasIncluded)) {
logEnqueueReflectiveAction(element);
if (Elements.isStaticOrTopLevel(element)) {
if (element.isTypedef) {
TypedefElement typedef = element;
typedef.ensureResolved(compiler);
compiler.world.allTypedefs.add(element);
} else if (Elements.isStaticOrTopLevel(element)) {
registerStaticUse(element.declaration);
} else if (element.isInstanceMember) {
// We need to enqueue all members matching this one in subclasses, as

View file

@ -340,9 +340,6 @@ class JavaScriptBackend extends Backend {
/// List of elements that the backend may use.
final Set<Element> helpersUsed = new Set<Element>();
/// Set of typedefs that are used as type literals.
final Set<TypedefElement> typedefTypeLiterals = new Set<TypedefElement>();
/// All the checked mode helpers.
static const checkedModeHelpers = CheckedModeHelper.helpers;
@ -1937,11 +1934,13 @@ class JavaScriptBackend extends Backend {
if (foundClosure) {
reflectableMembers.add(closureClass);
}
// It would be nice to have a better means to select
Set<Element> closurizedMembers = compiler.resolverWorld.closurizedMembers;
if (closurizedMembers.any(reflectableMembers.contains)) {
reflectableMembers.add(boundClosureClass);
}
// Add typedefs.
reflectableMembers
.addAll(compiler.world.allTypedefs.where(referencedFromMirrorSystem));
// Register all symbols of reflectable elements
for (Element element in reflectableMembers) {
symbolsUsed.add(element.name);
@ -2170,7 +2169,7 @@ class JavaScriptionResolutionCallbacks extends ResolutionCallbacks {
// when reflection is used. However, as long as we disable tree-shaking
// eagerly it doesn't matter.
if (type.isTypedef) {
backend.typedefTypeLiterals.add(type.element);
backend.compiler.world.allTypedefs.add(type.element);
}
backend.customElementsAnalysis.registerTypeLiteral(type, registry);
}

View file

@ -244,7 +244,7 @@ class NativeEmitter {
emitter.classEmitter.emitClassBuilderWithReflectionData(
backend.namer.getNameOfClass(classElement),
classElement, builders[classElement],
emitter.getElementDecriptor(classElement));
emitter.getElementDescriptor(classElement));
emitter.needsDefineClass = true;
}
}

View file

@ -80,32 +80,10 @@ class ClassEmitter extends CodeEmitterHelper {
// [ constructorName, fields,
// fields.map(
// (name) => js('this.# = #', [name, name]))]));
task.precompiledFunction.add(
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration(constructorName),
js('function(#) { #; }',
[fields,
fields.map((name) => js('this.# = #', [name, name]))])));
// TODO(floitsch): do we actually need the name field?
// TODO(floitsch): these should all go through the namer.
task.precompiledFunction.add(
js.statement(r'''{
#.builtin$cls = #;
if (!"name" in #)
#.name = #;
$desc=$collectedClasses.#;
if ($desc instanceof Array) $desc = $desc[1];
#.prototype = $desc;
}''',
[ constructorName, js.string(constructorName),
constructorName,
constructorName, js.string(constructorName),
constructorName,
constructorName
]));
task.precompiledConstructorNames.add(js('#', constructorName));
jsAst.Expression constructorAst = js('function(#) { #; }',
[fields,
fields.map((name) => js('this.# = #', [name, name]))]);
task.emitPrecompiledConstructor(constructorName, constructorAst);
}
/// Returns `true` if fields added.

View file

@ -55,6 +55,8 @@ class CodeEmitterTask extends CompilerTask {
// TODO(ngeoffray): remove this field.
Set<ClassElement> instantiatedClasses;
List<TypedefElement> typedefsNeededForReflection;
JavaScriptBackend get backend => compiler.backend;
TypeVariableHandler get typeVariableHandler => backend.typeVariableHandler;
@ -63,9 +65,12 @@ class CodeEmitterTask extends CompilerTask {
String get n => compiler.enableMinification ? "" : "\n";
String get N => compiler.enableMinification ? "\n" : ";\n";
CodeBuffer getBuffer(OutputUnit outputUnit) {
return outputBuffers.putIfAbsent(outputUnit, () => new CodeBuffer());
}
CodeBuffer get mainBuffer {
return outputBuffers.putIfAbsent(compiler.deferredLoadTask.mainOutputUnit,
() => new CodeBuffer());
return getBuffer(compiler.deferredLoadTask.mainOutputUnit);
}
/**
@ -752,6 +757,8 @@ class CodeEmitterTask extends CompilerTask {
ClassElement cls = element;
if (cls.isUnnamedMixinApplication) return null;
return cls.name;
} else if (element.isTypedef) {
return element.name;
}
throw compiler.internalError(element,
'Do not know how to reflect on this $element.');
@ -861,7 +868,7 @@ class CodeEmitterTask extends CompilerTask {
for (Element element in Elements.sortedByPosition(elements)) {
ClassBuilder builder = new ClassBuilder(element, namer);
containerBuilder.addMember(element, builder);
getElementDecriptor(element).properties.addAll(builder.properties);
getElementDescriptor(element).properties.addAll(builder.properties);
}
}
@ -1149,10 +1156,15 @@ class CodeEmitterTask extends CompilerTask {
}
}
/**
* Compute all the classes that must be emitted.
*/
void computeNeededClasses() {
/// Compute all the classes and typedefs that must be emitted.
void computeNeededDeclarations() {
// Compute needed typedefs.
typedefsNeededForReflection = Elements.sortedByPosition(
compiler.world.allTypedefs
.where(backend.isAccessibleByReflection)
.toList());
// Compute needed classes.
instantiatedClasses =
compiler.codegenWorld.instantiatedClasses.where(computeClassFilter())
.toSet();
@ -1311,26 +1323,21 @@ class CodeEmitterTask extends CompilerTask {
uri = relativize(compiler.outputUri, library.canonicalUri, false);
}
}
Map<OutputUnit, ClassBuilder> descriptors = elementDescriptors[library];
String libraryName =
(!compiler.enableMinification || backend.mustRetainLibraryNames) ?
library.getLibraryName() :
"";
Map<OutputUnit, ClassBuilder> descriptors =
elementDescriptors[library];
for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) {
ClassBuilder descriptor =
descriptors.putIfAbsent(outputUnit,
() => new ClassBuilder(library, namer));
if (descriptor.properties.isEmpty) continue;
bool isDeferred =
outputUnit != compiler.deferredLoadTask.mainOutputUnit;
if (!descriptors.containsKey(outputUnit)) continue;
ClassBuilder descriptor = descriptors[outputUnit];
jsAst.Fun metadata = metadataEmitter.buildMetadataFunction(library);
jsAst.ObjectInitializer initializers =
descriptor.toObjectInitializer();
CodeBuffer outputBuffer =
outputBuffers.putIfAbsent(outputUnit, () => new CodeBuffer());
jsAst.ObjectInitializer initializers = descriptor.toObjectInitializer();
CodeBuffer outputBuffer = getBuffer(outputUnit);
int sizeBefore = outputBuffer.length;
compiler.dumpInfoTask.registerElementAst(library, metadata);
compiler.dumpInfoTask.registerElementAst(library, initializers);
@ -1352,6 +1359,30 @@ class CodeEmitterTask extends CompilerTask {
}
}
void emitPrecompiledConstructor(String constructorName,
jsAst.Expression constructorAst) {
precompiledFunction.add(
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration(constructorName), constructorAst));
precompiledFunction.add(
js.statement(r'''{
#.builtin$cls = #;
if (!"name" in #)
#.name = #;
$desc=$collectedClasses.#;
if ($desc instanceof Array) $desc = $desc[1];
#.prototype = $desc;
}''',
[ constructorName, js.string(constructorName),
constructorName,
constructorName, js.string(constructorName),
constructorName,
constructorName
]));
precompiledConstructorNames.add(js('#', constructorName));
}
String assembleProgram() {
measure(() {
invalidateCaches();
@ -1360,7 +1391,7 @@ class CodeEmitterTask extends CompilerTask {
// 'is$' method.
typeTestEmitter.computeRequiredTypeChecks();
computeNeededClasses();
computeNeededDeclarations();
mainBuffer.add(buildGeneratedBy());
addComment(HOOKS_API_USAGE, mainBuffer);
@ -1395,7 +1426,8 @@ class CodeEmitterTask extends CompilerTask {
// Only output the classesCollector if we actually have any classes.
if (!(nativeClasses.isEmpty &&
compiler.codegenWorld.staticFunctionsNeedingGetter.isEmpty &&
outputClassLists.values.every((classList) => classList.isEmpty))) {
outputClassLists.values.every((classList) => classList.isEmpty) &&
typedefsNeededForReflection.isEmpty)) {
// Shorten the code by using "$$" as temporary.
classesCollector = r"$$";
mainBuffer.add('var $classesCollector$_=$_{}$N$n');
@ -1419,7 +1451,7 @@ class CodeEmitterTask extends CompilerTask {
// Might create methodClosures.
for (List<ClassElement> outputClassList in outputClassLists.values) {
for (ClassElement element in outputClassList) {
generateClass(element, getElementDecriptor(element));
generateClass(element, getElementDescriptor(element));
}
}
@ -1439,6 +1471,8 @@ class CodeEmitterTask extends CompilerTask {
mainBuffer.write(';');
}
// TODO(karlklose): document what kinds of fields this loop adds to the
// library class builder.
for (Element element in elementDescriptors.keys) {
// TODO(ahe): Should iterate over all libraries. Otherwise, we will
// not see libraries that only have fields.
@ -1458,6 +1492,32 @@ class CodeEmitterTask extends CompilerTask {
}
}
// Emit all required typedef declarations into the main output unit.
// TODO(karlklose): unify required classes and typedefs to declarations
// and have builders for each kind.
for (TypedefElement typedef in typedefsNeededForReflection) {
OutputUnit mainUnit = compiler.deferredLoadTask.mainOutputUnit;
LibraryElement library = typedef.library;
// TODO(karlklose): add a TypedefBuilder and move this code there.
DartType type = typedef.alias;
int typeIndex = metadataEmitter.reifyType(type);
String typeReference =
encoding.encodeTypedefFieldDescriptor(typeIndex);
jsAst.Property descriptor = new jsAst.Property(
js.string(namer.classDescriptorProperty),
js.string(typeReference));
jsAst.Node declaration = new jsAst.ObjectInitializer([descriptor]);
String mangledName = namer.getNameX(typedef);
String reflectionName = getReflectionName(typedef, mangledName);
getElementDescriptorForOutputUnit(library, mainUnit)
..addProperty(mangledName, declaration)
..addProperty("+$reflectionName", js.string(''));
// Also emit a trivial constructor for CSP mode.
String constructorName = mangledName;
jsAst.Expression constructorAst = js('function() {}');
emitPrecompiledConstructor(constructorName, constructorAst);
}
if (!mangledFieldNames.isEmpty) {
var keys = mangledFieldNames.keys.toList();
keys.sort();
@ -1714,7 +1774,7 @@ class CodeEmitterTask extends CompilerTask {
() => new ClassBuilder(element, namer));
}
ClassBuilder getElementDecriptor(Element element) {
ClassBuilder getElementDescriptor(Element element) {
Element owner = element.library;
if (!element.isTopLevel && !element.isNative) {
// For static (not top level) elements, record their code in a buffer

View file

@ -69,6 +69,8 @@ import '../util/util.dart' show
import '../deferred_load.dart' show
OutputUnit;
import '../runtime_data.dart' as encoding;
part 'class_builder.dart';
part 'class_emitter.dart';
part 'code_emitter_helper.dart';

View file

@ -90,20 +90,8 @@ class MetadataEmitter extends CodeEmitterHelper {
}
void emitMetadata(CodeBuffer buffer) {
var literals = backend.typedefTypeLiterals.toList();
Elements.sortedByPosition(literals);
var properties = [];
for (TypedefElement literal in literals) {
var key = namer.getNameX(literal);
var value = js.number(reifyType(literal.rawType));
properties.add(new jsAst.Property(js.string(key), value));
}
var map = new jsAst.ObjectInitializer(properties);
buffer.write(
jsAst.prettyPrint(
js.statement('init.functionAliases = #', map), compiler));
buffer.write('${N}init.metadata$_=$_[');
for (var metadata in globalMetadata) {
buffer.write('init.metadata$_=$_[');
for (String metadata in globalMetadata) {
if (metadata is String) {
if (metadata != 'null') {
buffer.write(metadata);

View file

@ -1301,6 +1301,7 @@ class ResolverTask extends CompilerTask {
TreeElements resolveTypedef(TypedefElementX element) {
if (element.isResolved) return element.treeElements;
compiler.world.allTypedefs.add(element);
return _resolveTypeDeclaration(element, () {
ResolutionRegistry registry = new ResolutionRegistry(compiler, element);
return compiler.withCurrentElement(element, () {

View file

@ -0,0 +1,22 @@
// Copyright (c) 2014, 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.
/// Contains encoding, decoding and detection functionality for the
/// representation of program data at runtime.
///
/// This library is shared between the compiler and the runtime system.
library dart2js.runtime_data;
String encodeTypedefFieldDescriptor(int typeIndex) {
return ":$typeIndex;";
}
bool isTypedefDescriptor(String descriptor) {
return descriptor.startsWith(':');
}
int getTypeFromTypedef(String descriptor) {
return int.parse(descriptor.substring(1, descriptor.length - 1));
}

View file

@ -10,6 +10,8 @@ class World {
final Set<Element> functionsCalledInLoop = new Set<Element>();
final Map<Element, SideEffects> sideEffects = new Map<Element, SideEffects>();
final Set<TypedefElement> allTypedefs = new Set<TypedefElement>();
final Map<ClassElement, Set<MixinApplicationElement>> mixinUses =
new Map<ClassElement, Set<MixinApplicationElement>>();

View file

@ -4,7 +4,7 @@
library dart._js_mirrors;
import 'dart:async';
import '../compiler/implementation/runtime_data.dart' as encoding;
import 'dart:collection' show
UnmodifiableListView,
@ -334,10 +334,12 @@ class JsLibraryMirror extends JsDeclarationMirror with JsObjectMirror
var cls = reflectClassByMangledName(className);
if (cls is ClassMirror) {
cls = cls.originalDeclaration;
if (cls is JsClassMirror) {
result[cls.simpleName] = cls;
cls._owner = this;
}
}
if (cls is JsClassMirror) {
result[cls.simpleName] = cls;
cls._owner = this;
} else if (cls is JsTypedefMirror) {
result[cls.simpleName] = cls;
}
}
return _cachedClasses =
@ -572,12 +574,6 @@ TypeMirror reflectClassByName(Symbol symbol, String mangledName) {
}
var constructor = JS('var', 'init.allClasses[#]', mangledName);
if (constructor == null) {
int index = JS('int|Null', 'init.functionAliases[#]', mangledName);
if (index != null) {
mirror = new JsTypedefMirror(symbol, mangledName, getMetadata(index));
JsCache.update(classMirrors, mangledName, mirror);
return mirror;
}
// Probably an intercepted class.
// TODO(ahe): How to handle intercepted classes?
throw new UnsupportedError('Cannot find class for: ${n(symbol)}');
@ -602,23 +598,28 @@ TypeMirror reflectClassByName(Symbol symbol, String mangledName) {
}
}
var superclassName = fields.split(';')[0];
var mixins = superclassName.split('+');
if (mixins.length > 1 && mangledGlobalNames[mangledName] == null) {
mirror = reflectMixinApplication(mixins, mangledName);
if (encoding.isTypedefDescriptor(fields)) {
int index = encoding.getTypeFromTypedef(fields);
mirror = new JsTypedefMirror(symbol, mangledName, getMetadata(index));
} else {
ClassMirror classMirror = new JsClassMirror(
symbol, mangledName, constructor, fields, fieldsMetadata);
List typeVariables =
JS('JSExtendableArray|Null', '#.prototype["<>"]', constructor);
if (typeVariables == null || typeVariables.length == 0) {
mirror = classMirror;
var superclassName = fields.split(';')[0];
var mixins = superclassName.split('+');
if (mixins.length > 1 && mangledGlobalNames[mangledName] == null) {
mirror = reflectMixinApplication(mixins, mangledName);
} else {
String typeArguments = 'dynamic';
for (int i = 1; i < typeVariables.length; i++) {
typeArguments += ',dynamic';
ClassMirror classMirror = new JsClassMirror(
symbol, mangledName, constructor, fields, fieldsMetadata);
List typeVariables =
JS('JSExtendableArray|Null', '#.prototype["<>"]', constructor);
if (typeVariables == null || typeVariables.length == 0) {
mirror = classMirror;
} else {
String typeArguments = 'dynamic';
for (int i = 1; i < typeVariables.length; i++) {
typeArguments += ',dynamic';
}
mirror = new JsTypeBoundClassMirror(classMirror, typeArguments);
}
mirror = new JsTypeBoundClassMirror(classMirror, typeArguments);
}
}

View file

@ -22,6 +22,10 @@ const Map<String, List<String>> WHITE_LIST = const {
// Some things in dart_printer are not yet used
"implementation/dart_backend/backend_ast_nodes.dart" : const [" is never "],
// dart2js uses only the encoding functions, the decoding functions are used
// from the generated code.
"implementation/runtime_data.dart": const [" is never "],
// Setlet implements the Set interface: Issue 18959.
"implementation/util/setlet.dart": const [" is never "],
};

View file

@ -204,7 +204,6 @@ async/schedule_microtask6_test: Fail # Issue 10957 - may be related
convert/json_lib_test: Fail # Issue 10961
[ $compiler == dart2js && $minified ]
mirrors/typedef_test/01: Fail # http://dartbug.com/6490
mirrors/mirrors_used_get_name_test: RuntimeError
mirrors/mirrors_used_get_name2_test: RuntimeError
@ -253,6 +252,8 @@ mirrors/mirrors_used_inheritance_test: RuntimeError # Issue 16048
async/timer_not_available_test: SkipByDesign # only meant to test when there is no way to implement timer (currently only in d8)
mirrors/typedef_declaration_test/01: Fail # dartbug.com/16048. Remove multitest marker when it passes.
[ $compiler == none && ( $runtime == drt || $runtime == dartium || $runtime == ContentShellOnAndroid) ]
async/schedule_microtask6_test: Fail # Issue 10910
async/timer_test: Fail, Pass # Issue 15487

View file

@ -57,13 +57,12 @@ test(MirrorSystem mirrors) {
Expect.isFalse(Obj.isSubclassOf(Func));
// Function typedef.
// TODO(16939): retrieve via declaration when dart2js supports it.
var NumPred = reflectType(NumberPredicate);
var IntPred = reflectType(IntegerPredicate);
var DubPred = reflectType(DoublePredicate);
var NumGen = reflectType(NumberGenerator);
var IntGen = reflectType(IntegerGenerator);
var DubGen = reflectType(DoubleGenerator);
var NumPred = thisLibrary.declarations[#NumberPredicate];
var IntPred = thisLibrary.declarations[#IntegerPredicate];
var DubPred = thisLibrary.declarations[#DoublePredicate];
var NumGen = thisLibrary.declarations[#NumberGenerator];
var IntGen = thisLibrary.declarations[#IntegerGenerator];
var DubGen = thisLibrary.declarations[#DoubleGenerator];
isArgumentOrTypeError(e) => e is ArgumentError || e is TypeError;
Expect.throws(() => Func.isSubclassOf(NumPred), isArgumentOrTypeError);

View file

@ -0,0 +1,29 @@
// Copyright (c) 2014, 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 test;
import 'package:expect/expect.dart';
@MirrorsUsed(targets: "Foo")
import 'dart:mirrors';
typedef int Foo(String x);
typedef int Bar();
main() {
LibraryMirror thisLibrary = currentMirrorSystem().findLibrary(#test);
Mirror fooMirror = thisLibrary.declarations[#Foo];
Expect.isTrue(fooMirror != null, 'Foo not found.');
Expect.isTrue(thisLibrary.declarations[#Foo] is TypedefMirror,
'TypedefMirror expected, found $fooMirror');
// The following code does not currenty work on the VM, because it does not
// support MirrorsUsed (see dartbug.com/16048).
Mirror barMirror = thisLibrary.declarations[#Bar]; /// 01: ok
Expect.isTrue(barMirror == null, /// 01: continued
'Bar should not be emitted due to MirrorsUsed.'); /// 01: continued
}