OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart2js.js_emitter; | |
6 | |
7 class ClassEmitter extends CodeEmitterHelper { | |
8 | |
9 ClassStubGenerator get _stubGenerator => | |
10 new ClassStubGenerator(compiler, namer, backend); | |
11 | |
12 /** | |
13 * Documentation wanted -- johnniwinther | |
14 */ | |
15 void emitClass(Class cls, ClassBuilder enclosingBuilder, Fragment fragment) { | |
16 ClassElement classElement = cls.element; | |
17 | |
18 assert(invariant(classElement, classElement.isDeclaration)); | |
19 | |
20 emitter.needsClassSupport = true; | |
21 | |
22 ClassElement superclass = classElement.superclass; | |
23 jsAst.Name superName; | |
24 if (superclass != null) { | |
25 superName = namer.className(superclass); | |
26 } | |
27 | |
28 if (cls.isMixinApplication) { | |
29 MixinApplication mixinApplication = cls; | |
30 jsAst.Name mixinName = mixinApplication.mixinClass.name; | |
31 superName = | |
32 new CompoundName([superName, Namer.literalPlus, mixinName]); | |
33 emitter.needsMixinSupport = true; | |
34 } | |
35 | |
36 ClassBuilder builder = new ClassBuilder.forClass(classElement, namer); | |
37 builder.superName = superName; | |
38 emitConstructorsForCSP(cls); | |
39 emitFields(cls, builder); | |
40 emitCheckedClassSetters(cls, builder); | |
41 emitClassGettersSettersForCSP(cls, builder); | |
42 emitInstanceMembers(cls, builder); | |
43 emitStubs(cls.callStubs, builder); | |
44 emitStubs(cls.typeVariableReaderStubs, builder); | |
45 emitRuntimeTypeInformation(cls, builder); | |
46 emitNativeInfo(cls, builder); | |
47 | |
48 if (classElement == backend.closureClass) { | |
49 // We add a special getter here to allow for tearing off a closure from | |
50 // itself. | |
51 jsAst.Fun function = js('function() { return this; }'); | |
52 jsAst.Name name = namer.getterForMember(Selector.CALL_NAME); | |
53 builder.addProperty(name, function); | |
54 } | |
55 | |
56 emitClassBuilderWithReflectionData(cls, builder, enclosingBuilder, | |
57 fragment); | |
58 } | |
59 /** | |
60 * Emits the precompiled constructor when in CSP mode. | |
61 */ | |
62 void emitConstructorsForCSP(Class cls) { | |
63 List<jsAst.Name> fieldNames = <jsAst.Name>[]; | |
64 | |
65 if (!compiler.useContentSecurityPolicy) return; | |
66 | |
67 if (!cls.onlyForRti && !cls.isNative) { | |
68 fieldNames = cls.fields.map((Field field) => field.name).toList(); | |
69 } | |
70 | |
71 ClassElement classElement = cls.element; | |
72 | |
73 jsAst.Expression constructorAst = | |
74 _stubGenerator.generateClassConstructor(classElement, fieldNames); | |
75 | |
76 jsAst.Name constructorName = namer.className(classElement); | |
77 OutputUnit outputUnit = | |
78 compiler.deferredLoadTask.outputUnitForElement(classElement); | |
79 emitter.assemblePrecompiledConstructor( | |
80 outputUnit, constructorName, constructorAst, fieldNames); | |
81 } | |
82 | |
83 /// Returns `true` if fields added. | |
84 bool emitFields(FieldContainer container, | |
85 ClassBuilder builder, | |
86 { bool classIsNative: false, | |
87 bool emitStatics: false }) { | |
88 Iterable<Field> fields; | |
89 if (container is Class) { | |
90 if (emitStatics) { | |
91 fields = container.staticFieldsForReflection; | |
92 } else if (container.onlyForRti) { | |
93 return false; | |
94 } else { | |
95 fields = container.fields; | |
96 } | |
97 } else { | |
98 assert(container is Library); | |
99 assert(emitStatics); | |
100 fields = container.staticFieldsForReflection; | |
101 } | |
102 | |
103 var fieldMetadata = []; | |
104 bool hasMetadata = false; | |
105 bool fieldsAdded = false; | |
106 | |
107 for (Field field in fields) { | |
108 FieldElement fieldElement = field.element; | |
109 jsAst.Name name = field.name; | |
110 jsAst.Name accessorName = field.accessorName; | |
111 bool needsGetter = field.needsGetter; | |
112 bool needsSetter = field.needsUncheckedSetter; | |
113 | |
114 // Ignore needsCheckedSetter - that is handled below. | |
115 bool needsAccessor = (needsGetter || needsSetter); | |
116 // We need to output the fields for non-native classes so we can auto- | |
117 // generate the constructor. For native classes there are no | |
118 // constructors, so we don't need the fields unless we are generating | |
119 // accessors at runtime. | |
120 bool needsFieldsForConstructor = !emitStatics && !classIsNative; | |
121 if (needsFieldsForConstructor || needsAccessor) { | |
122 var metadata = | |
123 task.metadataCollector.buildMetadataFunction(fieldElement); | |
124 if (metadata != null) { | |
125 hasMetadata = true; | |
126 } else { | |
127 metadata = new jsAst.LiteralNull(); | |
128 } | |
129 fieldMetadata.add(metadata); | |
130 recordMangledField(fieldElement, accessorName, | |
131 namer.privateName(fieldElement.memberName)); | |
132 List<jsAst.Literal> fieldNameParts = <jsAst.Literal>[]; | |
133 if (!needsAccessor) { | |
134 // Emit field for constructor generation. | |
135 assert(!classIsNative); | |
136 fieldNameParts.add(name); | |
137 } else { | |
138 // Emit (possibly renaming) field name so we can add accessors at | |
139 // runtime. | |
140 if (name != accessorName) { | |
141 fieldNameParts.add(accessorName); | |
142 fieldNameParts.add(js.stringPart(':')); | |
143 } | |
144 fieldNameParts.add(name); | |
145 if (field.needsInterceptedGetter) { | |
146 emitter.interceptorEmitter.interceptorInvocationNames.add( | |
147 namer.getterForElement(fieldElement)); | |
148 } | |
149 // TODO(16168): The setter creator only looks at the getter-name. | |
150 // Even though the setter could avoid the interceptor convention we | |
151 // currently still need to add the additional argument. | |
152 if (field.needsInterceptedGetter || field.needsInterceptedSetter) { | |
153 emitter.interceptorEmitter.interceptorInvocationNames.add( | |
154 namer.setterForElement(fieldElement)); | |
155 } | |
156 | |
157 int code = field.getterFlags + (field.setterFlags << 2); | |
158 if (code == 0) { | |
159 compiler.internalError(fieldElement, | |
160 'Field code is 0 ($fieldElement).'); | |
161 } | |
162 fieldNameParts.add( | |
163 js.stringPart(FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE])); | |
164 } | |
165 // Fields can only be reflected if their declaring class is reflectable | |
166 // (as they are only accessible via [ClassMirror.declarations]). | |
167 // However, set/get operations can be performed on them, so they are | |
168 // reflectable in some sense, which leads to [isAccessibleByReflection] | |
169 // reporting `true`. | |
170 if (backend.isAccessibleByReflection(fieldElement)) { | |
171 fieldNameParts.add(new jsAst.LiteralString('-')); | |
172 if (fieldElement.isTopLevel || | |
173 backend.isAccessibleByReflection(fieldElement.enclosingClass)) { | |
174 DartType type = fieldElement.type; | |
175 fieldNameParts.add(task.metadataCollector.reifyType(type)); | |
176 } | |
177 } | |
178 jsAst.Literal fieldNameAst = js.concatenateStrings(fieldNameParts); | |
179 builder.addField(fieldNameAst); | |
180 // Add 1 because adding a field to the class also requires a comma | |
181 compiler.dumpInfoTask.registerElementAst(fieldElement, fieldNameAst); | |
182 fieldsAdded = true; | |
183 } | |
184 } | |
185 | |
186 if (hasMetadata) { | |
187 builder.fieldMetadata = fieldMetadata; | |
188 } | |
189 return fieldsAdded; | |
190 } | |
191 | |
192 /// Emits checked setters for fields. | |
193 void emitCheckedClassSetters(Class cls, ClassBuilder builder) { | |
194 if (cls.onlyForRti) return; | |
195 | |
196 for (Field field in cls.fields) { | |
197 if (field.needsCheckedSetter) { | |
198 assert(!field.needsUncheckedSetter); | |
199 compiler.withCurrentElement(field.element, () { | |
200 generateCheckedSetter( | |
201 field.element, field.name, field.accessorName, builder); | |
202 }); | |
203 } | |
204 } | |
205 } | |
206 | |
207 /// Emits getters/setters for fields if compiling in CSP mode. | |
208 void emitClassGettersSettersForCSP(Class cls, ClassBuilder builder) { | |
209 | |
210 if (!compiler.useContentSecurityPolicy || cls.onlyForRti) return; | |
211 | |
212 for (Field field in cls.fields) { | |
213 Element member = field.element; | |
214 compiler.withCurrentElement(member, () { | |
215 if (field.needsGetter) { | |
216 emitGetterForCSP(member, field.name, field.accessorName, builder); | |
217 } | |
218 if (field.needsUncheckedSetter) { | |
219 emitSetterForCSP(member, field.name, field.accessorName, builder); | |
220 } | |
221 }); | |
222 } | |
223 } | |
224 | |
225 void emitStubs(Iterable<StubMethod> stubs, ClassBuilder builder) { | |
226 for (Method method in stubs) { | |
227 jsAst.Property property = builder.addProperty(method.name, method.code); | |
228 compiler.dumpInfoTask.registerElementAst(method.element, property); | |
229 } | |
230 } | |
231 | |
232 /** | |
233 * Documentation wanted -- johnniwinther | |
234 * | |
235 * Invariant: [classElement] must be a declaration element. | |
236 */ | |
237 void emitInstanceMembers(Class cls, | |
238 ClassBuilder builder) { | |
239 ClassElement classElement = cls.element; | |
240 assert(invariant(classElement, classElement.isDeclaration)); | |
241 | |
242 if (cls.onlyForRti || cls.isMixinApplication) return; | |
243 | |
244 // TODO(herhut): This is a no-op. Should it be removed? | |
245 for (Field field in cls.fields) { | |
246 emitter.containerBuilder.addMemberField(field, builder); | |
247 } | |
248 | |
249 for (Method method in cls.methods) { | |
250 assert(invariant(classElement, method.element.isDeclaration)); | |
251 assert(invariant(classElement, method.element.isInstanceMember)); | |
252 emitter.containerBuilder.addMemberMethod(method, builder); | |
253 } | |
254 | |
255 if (identical(classElement, compiler.objectClass) | |
256 && backend.enabledNoSuchMethod) { | |
257 // Emit the noSuchMethod handlers on the Object prototype now, | |
258 // so that the code in the dynamicFunction helper can find | |
259 // them. Note that this helper is invoked before analyzing the | |
260 // full JS script. | |
261 emitter.nsmEmitter.emitNoSuchMethodHandlers(builder.addProperty); | |
262 } | |
263 } | |
264 | |
265 /// Emits the members from the model. | |
266 void emitRuntimeTypeInformation(Class cls, ClassBuilder builder) { | |
267 assert(builder.functionType == null); | |
268 if (cls.functionTypeIndex != null) { | |
269 builder.functionType = cls.functionTypeIndex; | |
270 } | |
271 | |
272 for (Method method in cls.isChecks) { | |
273 builder.addProperty(method.name, method.code); | |
274 } | |
275 } | |
276 | |
277 void emitNativeInfo(Class cls, ClassBuilder builder) { | |
278 jsAst.Expression nativeInfo = NativeGenerator.encodeNativeInfo(cls); | |
279 if (nativeInfo != null) { | |
280 builder.addPropertyByName(namer.nativeSpecProperty, nativeInfo); | |
281 } | |
282 } | |
283 | |
284 void emitClassBuilderWithReflectionData(Class cls, | |
285 ClassBuilder classBuilder, | |
286 ClassBuilder enclosingBuilder, | |
287 Fragment fragment) { | |
288 ClassElement classElement = cls.element; | |
289 jsAst.Name className = cls.name; | |
290 | |
291 var metadata = task.metadataCollector.buildMetadataFunction(classElement); | |
292 if (metadata != null) { | |
293 classBuilder.addPropertyByName("@", metadata); | |
294 } | |
295 | |
296 if (backend.isAccessibleByReflection(classElement)) { | |
297 List<DartType> typeVars = classElement.typeVariables; | |
298 Iterable typeVariableProperties = emitter.typeVariableHandler | |
299 .typeVariablesOf(classElement); | |
300 | |
301 ClassElement superclass = classElement.superclass; | |
302 bool hasSuper = superclass != null; | |
303 if ((!typeVariableProperties.isEmpty && !hasSuper) || | |
304 (hasSuper && !equalElements(superclass.typeVariables, typeVars))) { | |
305 classBuilder.addPropertyByName('<>', | |
306 new jsAst.ArrayInitializer(typeVariableProperties.toList())); | |
307 } | |
308 } | |
309 | |
310 List<jsAst.Property> statics = new List<jsAst.Property>(); | |
311 ClassBuilder staticsBuilder = | |
312 new ClassBuilder.forStatics(classElement, namer); | |
313 if (emitFields(cls, staticsBuilder, emitStatics: true)) { | |
314 jsAst.ObjectInitializer initializer = | |
315 staticsBuilder.toObjectInitializer(); | |
316 compiler.dumpInfoTask.registerElementAst(classElement, | |
317 initializer); | |
318 jsAst.Node property = initializer.properties.single; | |
319 compiler.dumpInfoTask.registerElementAst(classElement, property); | |
320 statics.add(property); | |
321 } | |
322 | |
323 // TODO(herhut): Do not grab statics out of the properties. | |
324 ClassBuilder classProperties = | |
325 emitter.elementDescriptors[fragment].remove(classElement); | |
326 if (classProperties != null) { | |
327 statics.addAll(classProperties.properties); | |
328 } | |
329 | |
330 if (!statics.isEmpty) { | |
331 classBuilder.addPropertyByName('static', | |
332 new jsAst.ObjectInitializer(statics)); | |
333 } | |
334 | |
335 // TODO(ahe): This method (generateClass) should return a jsAst.Expression. | |
336 jsAst.ObjectInitializer propertyValue = | |
337 classBuilder.toObjectInitializer(); | |
338 compiler.dumpInfoTask.registerElementAst(classBuilder.element, propertyValue
); | |
339 enclosingBuilder.addProperty(className, propertyValue); | |
340 | |
341 String reflectionName = emitter.getReflectionName(classElement, className); | |
342 if (reflectionName != null) { | |
343 if (!backend.isAccessibleByReflection(classElement)) { | |
344 // TODO(herhut): Fix use of reflection name here. | |
345 enclosingBuilder.addPropertyByName("+$reflectionName", js.number(0)); | |
346 } else { | |
347 List<jsAst.Expression> types = <jsAst.Expression>[]; | |
348 if (classElement.supertype != null) { | |
349 types.add(task.metadataCollector.reifyType(classElement.supertype)); | |
350 } | |
351 for (DartType interface in classElement.interfaces) { | |
352 types.add(task.metadataCollector.reifyType(interface)); | |
353 } | |
354 // TODO(herhut): Fix use of reflection name here. | |
355 enclosingBuilder.addPropertyByName("+$reflectionName", | |
356 new jsAst.ArrayInitializer(types)); | |
357 } | |
358 } | |
359 } | |
360 | |
361 /** | |
362 * Invokes [f] for each of the fields of [element]. | |
363 * | |
364 * [element] must be a [ClassElement] or a [LibraryElement]. | |
365 * | |
366 * If [element] is a [ClassElement], the static fields of the class are | |
367 * visited if [visitStatics] is true and the instance fields are visited if | |
368 * [visitStatics] is false. | |
369 * | |
370 * If [element] is a [LibraryElement], [visitStatics] must be true. | |
371 * | |
372 * When visiting the instance fields of a class, the fields of its superclass | |
373 * are also visited if the class is instantiated. | |
374 * | |
375 * Invariant: [element] must be a declaration element. | |
376 */ | |
377 void visitFields(Element element, bool visitStatics, AcceptField f) { | |
378 assert(invariant(element, element.isDeclaration)); | |
379 | |
380 bool isClass = false; | |
381 bool isLibrary = false; | |
382 if (element.isClass) { | |
383 isClass = true; | |
384 } else if (element.isLibrary) { | |
385 isLibrary = true; | |
386 assert(invariant(element, visitStatics)); | |
387 } else { | |
388 throw new SpannableAssertionFailure( | |
389 element, 'Expected a ClassElement or a LibraryElement.'); | |
390 } | |
391 | |
392 // If the class is never instantiated we still need to set it up for | |
393 // inheritance purposes, but we can simplify its JavaScript constructor. | |
394 bool isInstantiated = | |
395 compiler.codegenWorld.directlyInstantiatedClasses.contains(element); | |
396 | |
397 void visitField(Element holder, FieldElement field) { | |
398 assert(invariant(element, field.isDeclaration)); | |
399 | |
400 // Keep track of whether or not we're dealing with a field mixin | |
401 // into a native class. | |
402 bool isMixinNativeField = | |
403 isClass && element.isNative && holder.isMixinApplication; | |
404 | |
405 // See if we can dynamically create getters and setters. | |
406 // We can only generate getters and setters for [element] since | |
407 // the fields of super classes could be overwritten with getters or | |
408 // setters. | |
409 bool needsGetter = false; | |
410 bool needsSetter = false; | |
411 if (isLibrary || isMixinNativeField || holder == element) { | |
412 needsGetter = fieldNeedsGetter(field); | |
413 needsSetter = fieldNeedsSetter(field); | |
414 } | |
415 | |
416 if ((isInstantiated && !holder.isNative) | |
417 || needsGetter | |
418 || needsSetter) { | |
419 jsAst.Name accessorName = namer.fieldAccessorName(field); | |
420 jsAst.Name fieldName = namer.fieldPropertyName(field); | |
421 bool needsCheckedSetter = false; | |
422 if (compiler.enableTypeAssertions | |
423 && needsSetter | |
424 && !canAvoidGeneratedCheckedSetter(field)) { | |
425 needsCheckedSetter = true; | |
426 needsSetter = false; | |
427 } | |
428 // Getters and setters with suffixes will be generated dynamically. | |
429 f(field, fieldName, accessorName, needsGetter, needsSetter, | |
430 needsCheckedSetter); | |
431 } | |
432 } | |
433 | |
434 if (isLibrary) { | |
435 LibraryElement library = element; | |
436 library.implementation.forEachLocalMember((Element member) { | |
437 if (member.isField) visitField(library, member); | |
438 }); | |
439 } else if (visitStatics) { | |
440 ClassElement cls = element; | |
441 cls.implementation.forEachStaticField(visitField); | |
442 } else { | |
443 ClassElement cls = element; | |
444 // TODO(kasperl): We should make sure to only emit one version of | |
445 // overridden fields. Right now, we rely on the ordering so the | |
446 // fields pulled in from mixins are replaced with the fields from | |
447 // the class definition. | |
448 | |
449 // If a class is not instantiated then we add the field just so we can | |
450 // generate the field getter/setter dynamically. Since this is only | |
451 // allowed on fields that are in [element] we don't need to visit | |
452 // superclasses for non-instantiated classes. | |
453 cls.implementation.forEachInstanceField( | |
454 visitField, includeSuperAndInjectedMembers: isInstantiated); | |
455 } | |
456 } | |
457 | |
458 void recordMangledField(Element member, | |
459 jsAst.Name accessorName, | |
460 String memberName) { | |
461 if (!backend.shouldRetainGetter(member)) return; | |
462 String previousName; | |
463 if (member.isInstanceMember) { | |
464 previousName = emitter.mangledFieldNames.putIfAbsent( | |
465 namer.deriveGetterName(accessorName), | |
466 () => memberName); | |
467 } else { | |
468 previousName = emitter.mangledGlobalFieldNames.putIfAbsent( | |
469 accessorName, | |
470 () => memberName); | |
471 } | |
472 assert(invariant(member, previousName == memberName, | |
473 message: '$previousName != ${memberName}')); | |
474 } | |
475 | |
476 bool fieldNeedsGetter(VariableElement field) { | |
477 assert(field.isField); | |
478 if (fieldAccessNeverThrows(field)) return false; | |
479 if (backend.shouldRetainGetter(field)) return true; | |
480 return field.isClassMember && | |
481 compiler.codegenWorld.hasInvokedGetter(field, compiler.world); | |
482 } | |
483 | |
484 bool fieldNeedsSetter(VariableElement field) { | |
485 assert(field.isField); | |
486 if (fieldAccessNeverThrows(field)) return false; | |
487 if (field.isFinal || field.isConst) return false; | |
488 if (backend.shouldRetainSetter(field)) return true; | |
489 return field.isClassMember && | |
490 compiler.codegenWorld.hasInvokedSetter(field, compiler.world); | |
491 } | |
492 | |
493 static bool fieldAccessNeverThrows(VariableElement field) { | |
494 return | |
495 // We never access a field in a closure (a captured variable) without | |
496 // knowing that it is there. Therefore we don't need to use a getter | |
497 // (that will throw if the getter method is missing), but can always | |
498 // access the field directly. | |
499 field is ClosureFieldElement; | |
500 } | |
501 | |
502 bool canAvoidGeneratedCheckedSetter(VariableElement member) { | |
503 // We never generate accessors for top-level/static fields. | |
504 if (!member.isInstanceMember) return true; | |
505 DartType type = member.type; | |
506 return type.treatAsDynamic || (type.element == compiler.objectClass); | |
507 } | |
508 | |
509 void generateCheckedSetter(Element member, | |
510 jsAst.Name fieldName, | |
511 jsAst.Name accessorName, | |
512 ClassBuilder builder) { | |
513 jsAst.Expression code = backend.generatedCode[member]; | |
514 assert(code != null); | |
515 jsAst.Name setterName = namer.deriveSetterName(accessorName); | |
516 compiler.dumpInfoTask.registerElementAst(member, | |
517 builder.addProperty(setterName, code)); | |
518 generateReflectionDataForFieldGetterOrSetter( | |
519 member, setterName, builder, isGetter: false); | |
520 } | |
521 | |
522 void emitGetterForCSP(Element member, jsAst.Name fieldName, | |
523 jsAst.Name accessorName, | |
524 ClassBuilder builder) { | |
525 jsAst.Expression function = | |
526 _stubGenerator.generateGetter(member, fieldName); | |
527 | |
528 jsAst.Name getterName = namer.deriveGetterName(accessorName); | |
529 ClassElement cls = member.enclosingClass; | |
530 jsAst.Name className = namer.className(cls); | |
531 OutputUnit outputUnit = | |
532 compiler.deferredLoadTask.outputUnitForElement(member); | |
533 emitter.cspPrecompiledFunctionFor(outputUnit).add( | |
534 js('#.prototype.# = #', [className, getterName, function])); | |
535 if (backend.isAccessibleByReflection(member)) { | |
536 emitter.cspPrecompiledFunctionFor(outputUnit).add( | |
537 js('#.prototype.#.${namer.reflectableField} = 1', | |
538 [className, getterName])); | |
539 } | |
540 } | |
541 | |
542 void emitSetterForCSP(Element member, jsAst.Name fieldName, | |
543 jsAst.Name accessorName, | |
544 ClassBuilder builder) { | |
545 jsAst.Expression function = | |
546 _stubGenerator.generateSetter(member, fieldName); | |
547 | |
548 jsAst.Name setterName = namer.deriveSetterName(accessorName); | |
549 ClassElement cls = member.enclosingClass; | |
550 jsAst.Name className = namer.className(cls); | |
551 OutputUnit outputUnit = | |
552 compiler.deferredLoadTask.outputUnitForElement(member); | |
553 emitter.cspPrecompiledFunctionFor(outputUnit).add( | |
554 js('#.prototype.# = #', [className, setterName, function])); | |
555 if (backend.isAccessibleByReflection(member)) { | |
556 emitter.cspPrecompiledFunctionFor(outputUnit).add( | |
557 js('#.prototype.#.${namer.reflectableField} = 1', | |
558 [className, setterName])); | |
559 } | |
560 } | |
561 | |
562 void generateReflectionDataForFieldGetterOrSetter(Element member, | |
563 jsAst.Name name, | |
564 ClassBuilder builder, | |
565 {bool isGetter}) { | |
566 Selector selector = isGetter | |
567 ? new Selector.getter(member.name, member.library) | |
568 : new Selector.setter(member.name, member.library); | |
569 String reflectionName = emitter.getReflectionName(selector, name); | |
570 if (reflectionName != null) { | |
571 var reflectable = | |
572 js(backend.isAccessibleByReflection(member) ? '1' : '0'); | |
573 builder.addPropertyByName('+$reflectionName', reflectable); | |
574 } | |
575 } | |
576 } | |
OLD | NEW |