Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Side by Side Diff: sdk/lib/_internal/compiler/implementation/native_handler.dart

Issue 11304021: Add NativeEnqueuer to work with the Enqueuer. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review fixes Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library native; 5 library native;
6 6
7 import 'dart:uri'; 7 import 'dart:uri';
8 import 'dart2jslib.dart' hide SourceString; 8 import 'dart2jslib.dart' hide SourceString;
9 import 'elements/elements.dart'; 9 import 'elements/elements.dart';
10 import 'js_backend/js_backend.dart'; 10 import 'js_backend/js_backend.dart';
11 import 'scanner/scannerlib.dart'; 11 import 'scanner/scannerlib.dart';
12 import 'ssa/ssa.dart'; 12 import 'ssa/ssa.dart';
13 import 'tree/tree.dart'; 13 import 'tree/tree.dart';
14 import 'util/util.dart'; 14 import 'util/util.dart';
15 15
16 void processNativeClasses(Enqueuer world, 16
17 CodeEmitterTask emitter, 17 /// This class is a temporary work-around until we get a more powerful DartType.
18 Collection<LibraryElement> libraries) { 18 class SpecialType {
19 for (LibraryElement library in libraries) { 19 final String name;
20 processNativeClassesInLibrary(world, emitter, library); 20 const SpecialType._(this.name);
21 } 21
22 } 22 /// The type Object, but no subtypes:
23 23 static const JsObject = const SpecialType._('=Object');
24 void addSubtypes(ClassElement cls, 24
25 NativeEmitter emitter) { 25 /// The specific implementation of List that is JavaScript Array:
26 for (DartType type in cls.allSupertypes) { 26 static const JsArray = const SpecialType._('=List');
27 List<Element> subtypes = emitter.subtypes.putIfAbsent( 27 }
28 type.element, 28
29
30 /**
31 * This could be an abstract class but we use it as a stub for the dart_backend.
32 */
33 class NativeEnqueuer {
34 /// Initial entry point to
ngeoffray 2012/11/15 12:49:57 Aborted comment.
35 void processNativeClasses(Collection<LibraryElement> libraries) {}
36
37 void registerElement(Element element) {}
38
39 /// Method is a member of a native class.
40 void registerMethod(Element method) {}
41
42 /// [field], a member of a native class, has been loaded
ngeoffray 2012/11/15 12:49:57 Not a sentence.
43 void registerFieldLoad(Element field) {}
44
45 /// Field is a member of a native class.
46 void registerFieldStore(Element field) {}
47
48 /**
49 * Handles JS-calls, which can be an instantiation point for types.
50 *
51 * For example, the following code instantiates and returns native classes
52 * that are `_DOMWindowImpl` or a subtype.
53 *
54 * JS('_DOMWindowImpl', 'window')
55 *
56 */
57 // TODO(sra): The entry from codegen will not have a resolver.
58 void registerJsCall(Send node, ResolverVisitor resolver) {}
59
60 /// Emits a summary information using the [log] function.
61 void logSummary(log(message)) {}
62 }
63
64
65 class NativeEnqueuerBase implements NativeEnqueuer {
66
67 /**
68 * The set of all native classes. Each native class is in [nativeClasses] and
69 * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses].
70 */
71 final Set<ClassElement> nativeClasses = new Set<ClassElement>();
72
73 final Set<ClassElement> registeredClasses = new Set<ClassElement>();
74 final Set<ClassElement> pendingClasses = new Set<ClassElement>();
75 final Set<ClassElement> unusedClasses = new Set<ClassElement>();
76
77 /**
78 * Records matched constraints ([SpecialType] or [DartType]). Once a type
79 * constraint has been matched, there is no need to match it again.
80 */
81 final Set matchedTypeConstraints = new Set();
82
83 /// Pending actions. Classes in [pendingClasses] have action thunks in
84 /// [queue] to register the class.
85 final queue = new Queue();
86 bool flushing = false;
87
88
89 final Enqueuer world;
90 final Compiler compiler;
91 final bool enableLiveTypeAnalysis;
92
93 Element annotationCreatesClass;
94 Element annotationReturnsClass;
95
96 /// Subclasses of [NativeEnqueuerBase] are constructed by the backend.
97 NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis);
98
99 void processNativeClasses(Collection<LibraryElement> libraries) {
100 Element find(name) {
101 Element e = compiler.findHelper(name);
102 if (e == null) {
103 compiler.cancel("Could not find implementation class '${name}'");
104 }
105 return e;
106 }
107 annotationCreatesClass = find(const SourceString('Creates'));
108 annotationReturnsClass = find(const SourceString('Returns'));
109
110 libraries.forEach(processNativeClassesInLibrary);
111
112 if (!enableLiveTypeAnalysis) {
113 nativeClasses.forEach((c) => enqueueClass(c, 'forced'));
114 flushQueue();
115 }
116 }
117
118 void processNativeClassesInLibrary(LibraryElement library) {
119 // Use implementation to ensure the inclusion of injected members.
120 library.implementation.forEachLocalMember((Element element) {
121 if (element.kind == ElementKind.CLASS) {
122 ClassElement classElement = element;
123 if (classElement.isNative()) {
124 nativeClasses.add(classElement);
125 unusedClasses.add(classElement);
126
127 // Resolve class to ensure the class has valid inheritance info.
128 classElement.ensureResolved(compiler);
129 }
130 }
131 });
132 }
133
134 enqueueClass(ClassElement classElement, cause) {
135 assert(unusedClasses.contains(classElement));
136 unusedClasses.remove(classElement);
137 pendingClasses.add(classElement);
138 queue.add(() { processClass(classElement, cause); });
139 }
140
141 void flushQueue() {
142 if (flushing) return;
143 flushing = true;
144 while (!queue.isEmpty) {
145 (queue.removeFirst())();
146 }
147 flushing = false;
148 }
149
150 processClass(ClassElement classElement, cause) {
151 assert(!registeredClasses.contains(classElement));
152
153 bool firstTime = registeredClasses.isEmpty;
154 pendingClasses.remove(classElement);
155 registeredClasses.add(classElement);
156
157 world.registerInstantiatedClass(classElement);
158
159 // Also parse the node to know all its methods because otherwise it will
160 // only be parsed if there is a call to one of its constructors.
161 classElement.parseNode(compiler);
162
163 if (firstTime) {
164 queue.add(onFirstNativeClass);
165 }
166 }
167
168 registerElement(Element element) {
169 if (element.isFunction()) return registerMethod(element);
170 }
171
172 registerMethod(Element method) {
173 if (isNativeMethod(method)) {
174 processNativeBehavior(
175 NativeBehavior.ofMethod(method, compiler),
176 method);
177 flushQueue();
178 }
179 }
180
181 bool isNativeMethod(Element element) {
182 // Native method?
183 Node node = element.parseNode(compiler);
184 if (node is! FunctionExpression) return false;
185 node = node.body;
186 Token token = node.getBeginToken();
187 if (token.stringValue == 'native') return true;
188 return false;
189 }
190
191 void registerFieldLoad(Element field) {
192 processNativeBehavior(
193 NativeBehavior.ofFieldLoad(field, compiler),
194 field);
195 flushQueue();
196 }
197
198 void registerFieldStore(Element field) {
199 processNativeBehavior(
200 NativeBehavior.ofFieldStore(field, compiler),
201 field);
202 flushQueue();
203 }
204
205 void registerJsCall(Send node, ResolverVisitor resolver) {
206 processNativeBehavior(
207 NativeBehavior.ofJsCall(node, compiler, resolver),
208 node);
209 flushQueue();
210 }
211
212 processNativeBehavior(NativeBehavior behavior, cause) {
213 bool allUsedBefore = unusedClasses.isEmpty;
214 for (var type in behavior.typesInstantiated) {
215 if (matchedTypeConstraints.contains(type)) continue;
216 matchedTypeConstraints.add(type);
217 if (type is SpecialType) {
218 // The two special types (=Object, =List) are always instantiated.
219 continue;
220 }
221 assert(type is DartType);
222 enqueueUnusedClassesMatching(
223 (nativeClass) => compiler.types.isSubtype(nativeClass.type, type),
224 cause,
225 'subtypeof($type)');
226 }
227
228 // Give an info so that library developers can compile with -v to find why
229 // all the native classes are included.
230 if (unusedClasses.isEmpty && !allUsedBefore) {
231 compiler.log('All native types marked as used due to $cause.');
232 }
233 }
234
235 enqueueUnusedClassesMatching(bool predicate(classElement),
236 cause,
237 [String reason]) {
238 Collection matches = unusedClasses.filter(predicate);
239 matches.forEach((c) => enqueueClass(c, cause));
240 }
241
242 onFirstNativeClass() {
243 staticUse(name) => world.registerStaticUse(compiler.findHelper(name));
244
245 staticUse(const SourceString('dynamicFunction'));
246 staticUse(const SourceString('dynamicSetMetadata'));
247 staticUse(const SourceString('defineProperty'));
248 staticUse(const SourceString('toStringForNativeObject'));
249 staticUse(const SourceString('hashCodeForNativeObject'));
250
251 addNativeExceptions();
252 }
253
254 addNativeExceptions() {
255 enqueueUnusedClassesMatching((classElement) {
256 // TODO(sra): Annotate exception classes in dart:html.
257 String name = classElement.name.slowToString();
258 if (name.contains('Exception')) return true;
259 if (name.contains('Error')) return true;
260 return false;
261 },
262 'native exception');
263 }
264 }
265
266
267 class NativeResolutionEnqueuer extends NativeEnqueuerBase {
268
269 NativeResolutionEnqueuer(Enqueuer world, Compiler compiler)
270 : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
271
272 void logSummary(log(message)) {
273 log('Resolved ${registeredClasses.length} native elements used, '
274 '${unusedClasses.length} native elements dead.');
275 }
276 }
277
278
279 class NativeCodegenEnqueuer extends NativeEnqueuerBase {
280
281 final CodeEmitterTask emitter;
282
283 NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter)
284 : super(world, compiler, compiler.enableNativeLiveTypeAnalysis);
285
286 void processNativeClasses(Collection<LibraryElement> libraries) {
287 super.processNativeClasses(libraries);
288
289 // HACK HACK - add all the resolved classes.
290 for (final classElement
291 in compiler.enqueuer.resolution.nativeEnqueuer.registeredClasses) {
292 if (unusedClasses.contains(classElement)) {
293 enqueueClass(classElement, 'was resolved');
294 }
295 }
296 flushQueue();
297 }
298
299 processClass(ClassElement classElement, cause) {
300 super.processClass(classElement, cause);
301 // Add the information that this class is a subtype of its supertypes. The
302 // code emitter and the ssa builder use that information.
303 addSubtypes(classElement, emitter.nativeEmitter);
304 }
305
306 void addSubtypes(ClassElement cls, NativeEmitter emitter) {
307 for (DartType type in cls.allSupertypes) {
308 List<Element> subtypes = emitter.subtypes.putIfAbsent(
309 type.element,
310 () => <ClassElement>[]);
311 subtypes.add(cls);
312 }
313
314 List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent(
315 cls.superclass,
29 () => <ClassElement>[]); 316 () => <ClassElement>[]);
30 subtypes.add(cls); 317 directSubtypes.add(cls);
31 } 318 }
32 319
33 List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent( 320 void logSummary(log(message)) {
34 cls.superclass, 321 log('Compiled ${registeredClasses.length} native classes, '
35 () => <ClassElement>[]); 322 '${unusedClasses.length} native classes omitted.');
36 directSubtypes.add(cls); 323 }
37 } 324 }
38 325
39 void processNativeClassesInLibrary(Enqueuer world,
40 CodeEmitterTask emitter,
41 LibraryElement library) {
42 bool hasNativeClass = false;
43 final compiler = emitter.compiler;
44 // Use implementation to ensure the inclusion of injected members.
45 library.implementation.forEachLocalMember((Element element) {
46 if (element.kind == ElementKind.CLASS) {
47 ClassElement classElement = element;
48 if (classElement.isNative()) {
49 hasNativeClass = true;
50 world.registerInstantiatedClass(classElement);
51 // Also parse the node to know all its methods because
52 // otherwise it will only be parsed if there is a call to
53 // one of its constructor.
54 classElement.parseNode(compiler);
55 // Resolve to setup the inheritance.
56 classElement.ensureResolved(compiler);
57 // Add the information that this class is a subtype of
58 // its supertypes. The code emitter and the ssa builder use that
59 // information.
60 addSubtypes(classElement, emitter.nativeEmitter);
61 }
62 }
63 });
64 if (hasNativeClass) {
65 world.registerStaticUse(compiler.findHelper(
66 const SourceString('dynamicFunction')));
67 world.registerStaticUse(compiler.findHelper(
68 const SourceString('dynamicSetMetadata')));
69 world.registerStaticUse(compiler.findHelper(
70 const SourceString('defineProperty')));
71 world.registerStaticUse(compiler.findHelper(
72 const SourceString('toStringForNativeObject')));
73 world.registerStaticUse(compiler.findHelper(
74 const SourceString('hashCodeForNativeObject')));
75 }
76 }
77
78 void maybeEnableNative(Compiler compiler, 326 void maybeEnableNative(Compiler compiler,
79 LibraryElement library, 327 LibraryElement library,
80 Uri uri) { 328 Uri uri) {
81 String libraryName = uri.toString(); 329 String libraryName = uri.toString();
82 if (library.entryCompilationUnit.script.name.contains( 330 if (library.entryCompilationUnit.script.name.contains(
83 'dart/tests/compiler/dart2js_native') 331 'dart/tests/compiler/dart2js_native')
84 || libraryName == 'dart:isolate' 332 || libraryName == 'dart:isolate'
85 || libraryName == 'dart:html' 333 || libraryName == 'dart:html'
86 || libraryName == 'dart:svg') { 334 || libraryName == 'dart:svg') {
87 library.canUseNative = true; 335 library.canUseNative = true;
88 } 336 }
89 } 337 }
90 338
339 /**
340 * A summary of the behavior of a native element.
341 *
342 * Native code can return values of one type and cause native subtypes of
343 * another type to be instantiated. By default, we compute both from the
344 * declared type.
345 *
346 * A field might yield any native type that 'is' the field type.
347 *
348 * A method might create and return instances of native subclasses of its
349 * declared return type, and a callback argument may be called with instances of
350 * the callback parameter type (e.g. Event).
351 *
352 * If there is one or more @Creates annotations, the union of the named types
353 * replaces the inferred instantiated type, and the return type is ignored for
354 * the purpose of inferring instantiated types.
355 *
356 * @Creates(IDBCursor) // Created asynchronously.
357 * @Creates(IDBRequest) // Created synchronously (for return value).
358 * IDBRequest request = objectStore.openCursor();
359 *
360 * If there is one or more @Returns annotations, the union of the named types
361 * replaces the declared return type.
362 *
363 * @Returns(IDBRequest)
364 * IDBRequest request = objectStore.openCursor();
365 */
366 class NativeBehavior {
367
368 /// [DartType]s or [SpecialType]s returned or yielded by the native element.
369 final Collection typesReturned = [];
370
371 /// [DartType]s or [SpecialType]s instantiated by the native element.
372 final Collection typesInstantiated = [];
373
374 const NativeBehavior();
375
376 static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) {
377 // The first argument of a JS-call is a string encoding various attributes
378 // of the code.
379 //
380 // 'Type1|Type2'. A union type.
381 // '=Object'. A JavaScript Object, no subtype.
382 // '=List'. A JavaScript Array, no subtype.
383
384 var argNodes = jsCall.arguments;
385 if (argNodes.isEmpty) {
386 compiler.cancel("JS expression has no type", node: node);
387 }
388
389 var firstArg = argNodes.head;
390 LiteralString specLiteral = firstArg.asLiteralString();
391 if (specLiteral != null) {
392 String specString = specLiteral.dartString.slowToString();
393 // Various things that are not in fact types.
394 if (specString == 'void') return const NativeBehavior();
395 if (specString == '' || specString == 'var') {
396 var behavior = new NativeBehavior();
397 behavior.typesReturned.add(compiler.objectClass.computeType(compiler));
398 return behavior;
399 }
400 var behavior = new NativeBehavior();
401 for (final typeString in specString.split('|')) {
402 var type = _parseType(typeString, compiler,
403 (name) => resolver.resolveTypeFromString(name),
404 jsCall);
405 behavior.typesInstantiated.add(type);
406 behavior.typesReturned.add(type);
407 }
408 return behavior;
409 }
410
411 // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It
412 // is not very satisfactory because it does not work for void, dynamic.
413
414 compiler.cancel("Unexpected JS first argument", node: firstArg);
415 }
416
417 static NativeBehavior ofMethod(Element method, Compiler compiler) {
418 DartType type = method.computeType(compiler);
419 var behavior = new NativeBehavior();
420 behavior.typesReturned.add(type.returnType);
421 behavior._capture(type, compiler);
422
423 // TODO(sra): Optional arguments are currently missing from the
424 // DartType. This should be fixed so the following work-around can be
425 // removed.
426 method.computeSignature(compiler).forEachOptionalParameter(
427 (Element parameter) {
428 behavior._escape(parameter.computeType(compiler), compiler);
429 });
430
431 behavior._overrideWithAnnotations(method, compiler);
432 return behavior;
433 }
434
435 static NativeBehavior ofFieldLoad(Element field, Compiler compiler) {
436 DartType type = field.computeType(compiler);
437 var behavior = new NativeBehavior();
438 behavior.typesReturned.add(type);
439 behavior._capture(type, compiler);
440 behavior._overrideWithAnnotations(field, compiler);
441 return behavior;
442 }
443
444 static NativeBehavior ofFieldStore(Element field, Compiler compiler) {
445 DartType type = field.computeType(compiler);
446 var behavior = new NativeBehavior();
447 behavior._escape(type, compiler);
448 // We don't override the default behaviour - the annotations apply to
449 // loading the field.
450 return behavior;
451 }
452
453 void _overrideWithAnnotations(Element element, Compiler compiler) {
454 if (element.metadata.isEmpty) return;
455
456 DartType lookup(String name) {
457 Element e = element.buildScope().lookup(new SourceString(name));
458 if (e == null) return null;
459 if (e is! ClassElement) return null;
460 e.ensureResolved(compiler);
461 return e.computeType(compiler);
462 }
463
464 var creates =
465 _collect(element, compiler,
466 compiler.enqueuer.resolution.nativeEnqueuer.annotationCreatesClass,
467 lookup);
468 var returns =
469 _collect(element, compiler,
470 compiler.enqueuer.resolution.nativeEnqueuer.annotationReturnsClass,
471 lookup);
472
473 if (creates != null) {
474 typesInstantiated..clear()..addAll(creates);
475 }
476 if (returns != null) {
477 typesReturned..clear()..addAll(returns);
478 }
479 }
480
481 /**
482 * Returns a list of type constraints from the annotations of
483 * [annotationClass].
484 * Returns `null` if no constraints.
485 */
486 static _collect(Element element, Compiler compiler, Element annotationClass,
487 lookup(str)) {
488 var types = null;
489 for (Link<MetadataAnnotation> link = element.metadata;
490 !link.isEmpty;
491 link = link.tail) {
492 MetadataAnnotation annotation = link.head.ensureResolved(compiler);
493 var value = annotation.value;
494 if (value is! ConstructedConstant) continue;
495 if (value.type is! InterfaceType) continue;
496 if (!identical(value.type.element, annotationClass)) continue;
497
498 var fields = value.fields;
499 // TODO(sra): Better validation of the constant.
500 if (fields.length != 1 ||
501 fields[0] is! StringConstant) {
ngeoffray 2012/11/15 12:49:57 fits in one line?
502 compiler.cancel(
503 'Annotations needs one string: ${annotation.parseNode(compiler)}');
504 }
505 String specString = fields[0].toDartString().slowToString();
506 for (final typeString in specString.split('|')) {
507 var type = _parseType(typeString, compiler, lookup, annotation);
508 if (types == null) types = [];
509 types.add(type);
510 }
511 }
512 return types;
513 }
514
515 /// Models the behavior of having intances of [type] escape from Dart code
516 /// into native code.
517 void _escape(DartType type, Compiler compiler) {
518 type = type.unalias(compiler);
519 if (type is FunctionType) {
520 // A function might be called from native code, passing us novel
521 // parameters.
522 _escape(type.returnType, compiler);
523 for (Link<DartType> parameters = type.parameterTypes;
524 !parameters.isEmpty;
525 parameters = parameters.tail) {
526 _capture(parameters.head, compiler);
527 }
528 }
529 }
530
531 /// Models the behavior of Dart code receiving instances and methods of [type]
532 /// from native code. We usually start the analysis by capturing a native
533 /// method that has been used.
534 void _capture(DartType type, Compiler compiler) {
535 type = type.unalias(compiler);
536 if (type is FunctionType) {
537 _capture(type.returnType, compiler);
538 for (Link<DartType> parameters = type.parameterTypes;
539 !parameters.isEmpty;
540 parameters = parameters.tail) {
541 _escape(parameters.head, compiler);
542 }
543 } else {
544 typesInstantiated.add(type);
545 }
546 }
547
548 static _parseType(String typeString, Compiler compiler,
549 lookup(name), locationNodeOrElement) {
550 if (typeString == '=Object') return SpecialType.JsObject;
551 if (typeString == '=List') return SpecialType.JsArray;
552 if (typeString == 'dynamic') {
553 return compiler.dynamicClass.computeType(compiler);
554 }
555 DartType type = lookup(typeString);
556 if (type != null) return type;
557
558 int index = typeString.indexOf('<');
559 if (index < 1) {
560 compiler.cancel("Type '$typeString' not found",
561 node: _errorNode(locationNodeOrElement, compiler));
562 }
563 type = lookup(typeString.substring(0, index));
564 if (type != null) {
565 // TODO(sra): Parse type parameters.
566 return type;
567 }
568 compiler.cancel("Type '$typeString' not found",
569 node: _errorNode(locationNodeOrElement, compiler));
570 }
571
572 static _errorNode(locationNodeOrElement, compiler) {
573 if (locationNodeOrElement is Node) return locationNodeOrElement;
574 return locationNodeOrElement.parseNode(compiler);
575 }
576 }
577
91 void checkAllowedLibrary(ElementListener listener, Token token) { 578 void checkAllowedLibrary(ElementListener listener, Token token) {
92 LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); 579 LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary();
93 if (!currentLibrary.canUseNative) { 580 if (!currentLibrary.canUseNative) {
94 listener.recoverableError("Unexpected token", token: token); 581 listener.recoverableError("Unexpected token", token: token);
95 } 582 }
96 } 583 }
97 584
98 Token handleNativeBlockToSkip(Listener listener, Token token) { 585 Token handleNativeBlockToSkip(Listener listener, Token token) {
99 checkAllowedLibrary(listener, token); 586 checkAllowedLibrary(listener, token);
100 token = token.next; 587 token = token.next;
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 String parameters) { 820 String parameters) {
334 buffer.add(" if (Object.getPrototypeOf(this).hasOwnProperty"); 821 buffer.add(" if (Object.getPrototypeOf(this).hasOwnProperty");
335 buffer.add("('$methodName')) {\n"); 822 buffer.add("('$methodName')) {\n");
336 buffer.add(" $code"); 823 buffer.add(" $code");
337 buffer.add(" } else {\n"); 824 buffer.add(" } else {\n");
338 buffer.add(" return Object.prototype.$methodName.call(this"); 825 buffer.add(" return Object.prototype.$methodName.call(this");
339 buffer.add(parameters == '' ? '' : ', $parameters'); 826 buffer.add(parameters == '' ? '' : ', $parameters');
340 buffer.add(");\n"); 827 buffer.add(");\n");
341 buffer.add(" }\n"); 828 buffer.add(" }\n");
342 } 829 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698