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

Side by Side Diff: lib/src/codegen/js_codegen.dart

Issue 1879373004: Implement modular compilation (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 8 months 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
« no previous file with comments | « lib/src/codegen/html_codegen.dart ('k') | lib/src/codegen/js_field_storage.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015, 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 import 'dart:collection' show HashSet, HashMap, SplayTreeSet;
6
7 import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
8 import 'package:analyzer/dart/ast/token.dart';
9 import 'package:analyzer/dart/element/element.dart';
10 import 'package:analyzer/dart/element/visitor.dart';
11 import 'package:analyzer/dart/element/type.dart';
12 import 'package:analyzer/dart/ast/ast.dart' hide ConstantEvaluator;
13 import 'package:analyzer/src/generated/constant.dart';
14 //TODO(leafp): Remove deprecated dependency
15 //ignore: DEPRECATED_MEMBER_USE
16 import 'package:analyzer/src/generated/element.dart'
17 show DynamicElementImpl, DynamicTypeImpl, LocalVariableElementImpl;
18 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
19 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
20 import 'package:analyzer/src/dart/ast/token.dart'
21 show StringToken, Token, TokenType;
22 import 'package:analyzer/src/generated/type_system.dart'
23 show StrongTypeSystemImpl;
24 import 'package:analyzer/src/task/strong/info.dart';
25
26 import 'ast_builder.dart' show AstBuilder;
27 import 'reify_coercions.dart' show CoercionReifier, Tuple2;
28
29 import '../js/js_ast.dart' as JS;
30 import '../js/js_ast.dart' show js;
31
32 import '../closure/closure_annotator.dart' show ClosureAnnotator;
33 import '../compiler.dart'
34 show AbstractCompiler, corelibOrder, getCorelibModuleName;
35 import '../options.dart' show CodegenOptions;
36 import '../utils.dart';
37
38 import 'js_field_storage.dart';
39 import 'js_interop.dart';
40 import 'js_names.dart' as JS;
41 import 'js_metalet.dart' as JS;
42 import 'js_module_item_order.dart';
43 import 'js_names.dart';
44 import 'js_printer.dart' show writeJsLibrary;
45 import 'js_typeref_codegen.dart';
46 import 'module_builder.dart';
47 import 'nullable_type_inference.dart';
48 import 'side_effect_analysis.dart';
49
50 // Various dynamic helpers we call.
51 // If renaming these, make sure to check other places like the
52 // _runtime.js file and comments.
53 // TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can
54 // import and generate calls to, rather than dart_runtime.js
55 const DPUT = 'dput';
56 const DLOAD = 'dload';
57 const DINDEX = 'dindex';
58 const DSETINDEX = 'dsetindex';
59 const DCALL = 'dcall';
60 const DSEND = 'dsend';
61
62 class JSCodegenVisitor extends GeneralizingAstVisitor
63 with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference {
64 final AbstractCompiler compiler;
65 final CodegenOptions options;
66 final LibraryElement currentLibrary;
67 final StrongTypeSystemImpl rules;
68
69 /// The global extension type table.
70 final ExtensionTypeSet _extensionTypes;
71
72 /// Information that is precomputed for this library, indicates which fields
73 /// need storage slots.
74 final HashSet<FieldElement> _fieldsNeedingStorage;
75
76 /// The variable for the target of the current `..` cascade expression.
77 ///
78 /// Usually a [SimpleIdentifier], but it can also be other expressions
79 /// that are safe to evaluate multiple times, such as `this`.
80 Expression _cascadeTarget;
81
82 /// The variable for the current catch clause
83 SimpleIdentifier _catchParameter;
84
85 /// In an async* function, this represents the stream controller parameter.
86 JS.TemporaryId _asyncStarController;
87
88 /// Imported libraries, and the temporaries used to refer to them.
89 final _imports = new Map<LibraryElement, JS.TemporaryId>();
90 final _exports = <String, String>{};
91 final _properties = <FunctionDeclaration>[];
92 final _privateNames = new HashMap<String, JS.TemporaryId>();
93 final _moduleItems = <JS.Statement>[];
94 final _temps = new HashMap<Element, JS.TemporaryId>();
95 final _qualifiedIds = new List<Tuple2<Element, JS.MaybeQualifiedId>>();
96
97 /// The name for the library's exports inside itself.
98 /// `exports` was chosen as the most similar to ES module patterns.
99 final _dartxVar = new JS.Identifier('dartx');
100 final _exportsVar = new JS.TemporaryId('exports');
101 final _runtimeLibVar = new JS.Identifier('dart');
102 final namedArgumentTemp = new JS.TemporaryId('opts');
103
104 final TypeProvider _types;
105
106 ConstFieldVisitor _constField;
107
108 ModuleItemLoadOrder _loader;
109
110 /// _interceptors.JSArray<E>, used for List literals.
111 ClassElement _jsArray;
112
113 /// The current function body being compiled.
114 FunctionBody _currentFunction;
115
116 /// The default value of the module object. See [visitLibraryDirective].
117 String _jsModuleValue;
118
119 bool _isDartRuntime;
120
121 JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary,
122 this._extensionTypes, this._fieldsNeedingStorage)
123 : compiler = compiler,
124 options = compiler.options.codegenOptions,
125 _types = compiler.context.typeProvider {
126 _loader = new ModuleItemLoadOrder(_emitModuleItem);
127
128 var context = compiler.context;
129 var src = context.sourceFactory.forUri('dart:_interceptors');
130 var interceptors = context.computeLibraryElement(src);
131 _jsArray = interceptors.getType('JSArray');
132 _isDartRuntime = currentLibrary.source.uri.toString() == 'dart:_runtime';
133 }
134
135 TypeProvider get types => _types;
136
137 JS.Program emitLibrary(List<CompilationUnit> units) {
138 // Modify the AST to make coercions explicit.
139 units = new CoercionReifier().reify(units);
140
141 units.last.directives.forEach(_visit);
142
143 // Rather than directly visit declarations, we instead use [_loader] to
144 // visit them. It has the ability to sort elements on demand, so
145 // dependencies between top level items are handled with a minimal
146 // reordering of the user's input code. The loader will call back into
147 // this visitor via [_emitModuleItem] when it's ready to visit the item
148 // for real.
149 _loader.collectElements(currentLibrary, units);
150
151 // TODO(jmesserly): ideally we could do this at a smaller granularity.
152 // We'll need to be consistent about when we're generating functions, and
153 // only run this on the outermost function.
154 inferNullableTypesInLibrary(units);
155
156 _constField = new ConstFieldVisitor(types, currentLibrary.source);
157
158 for (var unit in units) {
159 for (var decl in unit.declarations) {
160 if (decl is TopLevelVariableDeclaration) {
161 visitTopLevelVariableDeclaration(decl);
162 } else {
163 _loader.loadDeclaration(decl, decl.element);
164 }
165 }
166 }
167
168 // Flush any unwritten fields/properties.
169 _flushLibraryProperties(_moduleItems);
170
171 // Mark all qualified names as qualified or not, depending on if they need
172 // to be loaded lazily or not.
173 for (var elementIdPairs in _qualifiedIds) {
174 var element = elementIdPairs.e0;
175 var id = elementIdPairs.e1;
176 id.setQualified(!_loader.isLoaded(element));
177 }
178
179 var moduleBuilder = new ModuleBuilder(options.moduleFormat);
180
181 _exports.forEach(moduleBuilder.addExport);
182
183 var currentModuleName = compiler.getModuleName(currentLibrary.source.uri);
184 var items = <JS.ModuleItem>[];
185 if (!_isDartRuntime) {
186 if (currentLibrary.source.isInSystemLibrary) {
187 // Force the import order of runtime libs.
188 // TODO(ochafik): Reduce this to a minimum.
189 for (var libUri in corelibOrder.reversed) {
190 var moduleName = compiler.getModuleName(libUri);
191 if (moduleName == currentModuleName) continue;
192 moduleBuilder.addImport(moduleName, null);
193 }
194 }
195 moduleBuilder.addImport('dart/_runtime', _runtimeLibVar);
196
197 var dartxImport =
198 js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]);
199 items.add(dartxImport);
200 }
201 items.addAll(_moduleItems);
202
203 _imports.forEach((LibraryElement lib, JS.TemporaryId temp) {
204 moduleBuilder.addImport(compiler.getModuleName(lib.source.uri), temp,
205 isLazy: _isDartRuntime || !_loader.libraryIsLoaded(lib));
206 });
207
208 // TODO(jmesserly): scriptTag support.
209 // Enable this if we know we're targetting command line environment?
210 // It doesn't work in browser.
211 // var jsBin = compiler.options.runnerOptions.v8Binary;
212 // String scriptTag = null;
213 // if (library.library.scriptTag != null) scriptTag = '/usr/bin/env $jsBin';
214 return moduleBuilder.build(
215 currentModuleName, _jsModuleValue, _exportsVar, items);
216 }
217
218 void _emitModuleItem(AstNode node) {
219 // Attempt to group adjacent properties.
220 if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems);
221
222 var code = _visit(node);
223 if (code != null) _moduleItems.add(code);
224 }
225
226 @override
227 void visitLibraryDirective(LibraryDirective node) {
228 assert(_jsModuleValue == null);
229
230 var jsName = findAnnotation(node.element, isJSAnnotation);
231 _jsModuleValue =
232 getConstantField(jsName, 'name', types.stringType)?.toStringValue();
233 }
234
235 @override
236 void visitImportDirective(ImportDirective node) {
237 // Nothing to do yet, but we'll want to convert this to an ES6 import once
238 // we have support for modules.
239 }
240
241 @override
242 void visitPartDirective(PartDirective node) {}
243 @override
244 void visitPartOfDirective(PartOfDirective node) {}
245
246 @override
247 void visitExportDirective(ExportDirective node) {
248 var exportName = emitLibraryName(node.uriElement);
249
250 var currentLibNames = currentLibrary.publicNamespace.definedNames;
251
252 var args = [_exportsVar, exportName];
253 if (node.combinators.isNotEmpty) {
254 var shownNames = <JS.Expression>[];
255 var hiddenNames = <JS.Expression>[];
256
257 var show = node.combinators.firstWhere((c) => c is ShowCombinator,
258 orElse: () => null) as ShowCombinator;
259 var hide = node.combinators.firstWhere((c) => c is HideCombinator,
260 orElse: () => null) as HideCombinator;
261 if (show != null) {
262 shownNames.addAll(show.shownNames
263 .map((i) => i.name)
264 .where((s) => !currentLibNames.containsKey(s))
265 .map((s) => js.string(s, "'")));
266 }
267 if (hide != null) {
268 hiddenNames.addAll(hide.hiddenNames.map((i) => js.string(i.name, "'")));
269 }
270 args.add(new JS.ArrayInitializer(shownNames));
271 args.add(new JS.ArrayInitializer(hiddenNames));
272 }
273
274 _moduleItems.add(js.statement('dart.export(#);', [args]));
275 }
276
277 JS.Identifier _initSymbol(JS.Identifier id) {
278 var s =
279 js.statement('const # = $_SYMBOL(#);', [id, js.string(id.name, "'")]);
280 _moduleItems.add(s);
281 return id;
282 }
283
284 // TODO(jmesserly): this is a temporary workaround for `Symbol` in core,
285 // until we have better name tracking.
286 String get _SYMBOL {
287 var name = currentLibrary.name;
288 if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol';
289 return 'Symbol';
290 }
291
292 bool isPublic(String name) => !name.startsWith('_');
293
294 @override
295 visitAsExpression(AsExpression node) {
296 var from = getStaticType(node.expression);
297 var to = node.type.type;
298
299 var fromExpr = _visit(node.expression);
300
301 // Skip the cast if it's not needed.
302 if (rules.isSubtypeOf(from, to)) return fromExpr;
303
304 // All Dart number types map to a JS double.
305 if (_isNumberInJS(from) && _isNumberInJS(to)) {
306 // Make sure to check when converting to int.
307 if (from != _types.intType && to == _types.intType) {
308 return js.call('dart.asInt(#)', [fromExpr]);
309 }
310
311 // A no-op in JavaScript.
312 return fromExpr;
313 }
314
315 return js.call('dart.as(#, #)', [fromExpr, _emitTypeName(to)]);
316 }
317
318 @override
319 visitIsExpression(IsExpression node) {
320 // Generate `is` as `dart.is` or `typeof` depending on the RHS type.
321 JS.Expression result;
322 var type = node.type.type;
323 var lhs = _visit(node.expression);
324 var typeofName = _jsTypeofName(type);
325 if (typeofName != null) {
326 result = js.call('typeof # == #', [lhs, js.string(typeofName, "'")]);
327 } else {
328 // Always go through a runtime helper, because implicit interfaces.
329 result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]);
330 }
331
332 if (node.notOperator != null) {
333 return js.call('!#', result);
334 }
335 return result;
336 }
337
338 String _jsTypeofName(DartType t) {
339 if (_isNumberInJS(t)) return 'number';
340 if (t == _types.stringType) return 'string';
341 if (t == _types.boolType) return 'boolean';
342 return null;
343 }
344
345 @override
346 visitFunctionTypeAlias(FunctionTypeAlias node) {
347 var element = node.element;
348 var type = element.type;
349 var name = element.name;
350
351 var fnType = annotate(
352 js.statement('const # = dart.typedef(#, () => #);', [
353 name,
354 js.string(name, "'"),
355 _emitTypeName(type, lowerTypedef: true)
356 ]),
357 node,
358 node.element);
359
360 return _finishClassDef(type, fnType);
361 }
362
363 @override
364 JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type);
365
366 @override
367 JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
368 var element = node.element;
369
370 // Forward all generative constructors from the base class.
371 var body = <JS.Method>[];
372
373 var supertype = element.supertype;
374 if (!supertype.isObject) {
375 for (var ctor in element.constructors) {
376 var parentCtor = supertype.lookUpConstructor(ctor.name, ctor.library);
377 var fun = js.call('function() { super.#(...arguments); }',
378 [_constructorName(parentCtor)]) as JS.Fun;
379 body.add(new JS.Method(_constructorName(ctor), fun));
380 }
381 }
382
383 var cls = _emitClassDeclaration(element, body);
384 return _finishClassDef(element.type, cls);
385 }
386
387 JS.Statement _emitJsType(String dartClassName, DartObject jsName) {
388 var jsTypeName =
389 getConstantField(jsName, 'name', types.stringType)?.toStringValue();
390
391 if (jsTypeName != null && jsTypeName != dartClassName) {
392 // We export the JS type as if it was a Dart type. For example this allows
393 // `dom.InputElement` to actually be HTMLInputElement.
394 // TODO(jmesserly): if we had the JS name on the Element, we could just
395 // generate it correctly when we refer to it.
396 if (isPublic(dartClassName)) _addExport(dartClassName);
397 return js.statement('const # = #;', [dartClassName, jsTypeName]);
398 }
399 return null;
400 }
401
402 @override
403 JS.Statement visitClassDeclaration(ClassDeclaration node) {
404 var classElem = node.element;
405 var type = classElem.type;
406 var jsName = findAnnotation(classElem, isJSAnnotation);
407
408 if (jsName != null) return _emitJsType(node.name.name, jsName);
409
410 var ctors = <ConstructorDeclaration>[];
411 var fields = <FieldDeclaration>[];
412 var staticFields = <FieldDeclaration>[];
413 var methods = <MethodDeclaration>[];
414 for (var member in node.members) {
415 if (member is ConstructorDeclaration) {
416 ctors.add(member);
417 } else if (member is FieldDeclaration) {
418 (member.isStatic ? staticFields : fields).add(member);
419 } else if (member is MethodDeclaration) {
420 methods.add(member);
421 }
422 }
423
424 var allFields = new List.from(fields)..addAll(staticFields);
425
426 var classDecl = _emitClassDeclaration(
427 classElem, _emitClassMethods(node, ctors, fields),
428 fields: allFields);
429
430 String jsPeerName;
431 var jsPeer = findAnnotation(classElem, isJsPeerInterface);
432 // Only look at "Native" annotations on registered extension types.
433 // E.g., we're current ignoring the ones in dart:html.
434 if (jsPeer == null && _extensionTypes.contains(classElem)) {
435 jsPeer = findAnnotation(classElem, isNativeAnnotation);
436 }
437 if (jsPeer != null) {
438 jsPeerName =
439 getConstantField(jsPeer, 'name', types.stringType)?.toStringValue();
440 if (jsPeerName.contains(',')) {
441 jsPeerName = jsPeerName.split(',')[0];
442 }
443 }
444
445 var body = _finishClassMembers(classElem, classDecl, ctors, fields,
446 staticFields, methods, node.metadata, jsPeerName);
447
448 var result = _finishClassDef(type, body);
449
450 if (jsPeerName != null) {
451 // This class isn't allowed to be lazy, because we need to set up
452 // the native JS type eagerly at this point.
453 // If we wanted to support laziness, we could defer the hookup until
454 // the end of the Dart library cycle load.
455 assert(_loader.isLoaded(classElem));
456
457 // TODO(jmesserly): this copies the dynamic members.
458 // Probably fine for objects coming from JS, but not if we actually
459 // want to support construction of instances with generic types other
460 // than dynamic. See issue #154 for Array and List<E> related bug.
461 var copyMembers = js.statement(
462 'dart.registerExtension(dart.global.#, #);',
463 [_propertyName(jsPeerName), classElem.name]);
464 return _statement([result, copyMembers]);
465 }
466 return result;
467 }
468
469 List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) {
470 return typeFormals
471 .map((t) => new JS.Identifier(t.name))
472 .toList(growable: false);
473 }
474
475 /// Emits a field declaration for TypeScript & Closure's ES6_TYPED
476 /// (e.g. `class Foo { i: string; }`)
477 JS.VariableDeclarationList _emitTypeScriptField(FieldDeclaration field) {
478 return new JS.VariableDeclarationList(
479 field.isStatic ? 'static' : null,
480 field.fields.variables
481 .map((decl) => new JS.VariableInitialization(
482 new JS.Identifier(
483 // TODO(ochafik): use a refactored _emitMemberName instead.
484 decl.name.name,
485 type: emitTypeRef(decl.element.type)),
486 null))
487 .toList(growable: false));
488 }
489
490 @override
491 JS.Statement visitEnumDeclaration(EnumDeclaration node) {
492 var element = node.element;
493 var type = element.type;
494 var name = js.string(type.name);
495 var id = new JS.Identifier(type.name);
496
497 // Generate a class per section 13 of the spec.
498 // TODO(vsm): Generate any accompanying metadata
499
500 // Create constructor and initialize index
501 var constructor = new JS.Method(
502 name, js.call('function(index) { this.index = index; }') as JS.Fun);
503 var fields = new List<FieldElement>.from(
504 element.fields.where((f) => f.type == type));
505
506 // Create toString() method
507 var properties = new List<JS.Property>();
508 for (var i = 0; i < fields.length; ++i) {
509 properties.add(new JS.Property(
510 js.number(i), js.string('${type.name}.${fields[i].name}')));
511 }
512 var nameMap = new JS.ObjectInitializer(properties, multiline: true);
513 var toStringF = new JS.Method(js.string('toString'),
514 js.call('function() { return #[this.index]; }', nameMap) as JS.Fun);
515
516 // Create enum class
517 var classExpr = new JS.ClassExpression(
518 id, _emitClassHeritage(element), [constructor, toStringF]);
519 var result = <JS.Statement>[js.statement('#', classExpr)];
520
521 // Create static fields for each enum value
522 for (var i = 0; i < fields.length; ++i) {
523 result.add(js.statement('#.# = dart.const(new #(#));',
524 [id, fields[i].name, id, js.number(i)]));
525 }
526
527 // Create static values list
528 var values = new JS.ArrayInitializer(new List<JS.Expression>.from(
529 fields.map((f) => js.call('#.#', [id, f.name]))));
530 result.add(js.statement('#.values = dart.const(dart.list(#, #));',
531 [id, values, _emitTypeName(type)]));
532
533 if (isPublic(type.name)) _addExport(type.name);
534 return _statement(result);
535 }
536
537 /// Given a class element and body, complete the class declaration.
538 /// This handles generic type parameters, laziness (in library-cycle cases),
539 /// and ensuring dependencies are loaded first.
540 JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) {
541 var name = type.name;
542 var genericName = '$name\$';
543
544 JS.Statement genericDef = null;
545 if (_typeFormalsOf(type).isNotEmpty) {
546 genericDef = _emitGenericClassDef(type, body);
547 }
548 // The base class and all mixins must be declared before this class.
549 if (!_loader.isLoaded(type.element)) {
550 // TODO(jmesserly): the lazy class def is a simple solution for now.
551 // We may want to consider other options in the future.
552
553 if (genericDef != null) {
554 return js.statement(
555 '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }',
556 [genericDef, _exportsVar, _propertyName(name), genericName]);
557 }
558
559 return js.statement(
560 'dart.defineLazyClass(#, { get #() { #; return #; } });',
561 [_exportsVar, _propertyName(name), body, name]);
562 }
563
564 if (isPublic(name)) _addExport(name);
565
566 if (genericDef != null) {
567 var dynType = fillDynamicTypeArgs(type, types);
568 var genericInst = _emitTypeName(dynType, lowerGeneric: true);
569 return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
570 }
571 return body;
572 }
573
574 JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) {
575 var name = type.name;
576 var genericName = '$name\$';
577 var typeParams = _typeFormalsOf(type).map((p) => p.name);
578 if (isPublic(name)) _addExport(genericName);
579 return js.statement('const # = dart.generic(function(#) { #; return #; });',
580 [genericName, typeParams, body, name]);
581 }
582
583 final _hasDeferredSupertype = new HashSet<ClassElement>();
584
585 bool _deferIfNeeded(DartType type, ClassElement current) {
586 if (type is ParameterizedType) {
587 var typeArguments = type.typeArguments;
588 for (var typeArg in typeArguments) {
589 var typeElement = typeArg.element;
590 // FIXME(vsm): This does not track mutual recursive dependences.
591 if (current == typeElement || _deferIfNeeded(typeArg, current)) {
592 return true;
593 }
594 }
595 }
596 return false;
597 }
598
599 JS.Statement _emitClassDeclaration(
600 ClassElement element, List<JS.Method> methods,
601 {List<FieldDeclaration> fields}) {
602 String name = element.name;
603 var heritage = _emitClassHeritage(element);
604 var typeParams = _emitTypeFormals(element.typeParameters);
605 var jsFields = fields?.map(_emitTypeScriptField)?.toList();
606
607 // Workaround for Closure: super classes must be qualified paths.
608 // TODO(jmesserly): is there a bug filed? we need to get out of the business
609 // of working around bugs in other transpilers...
610 JS.Statement workaroundSuper = null;
611 if (options.closure && heritage is JS.Call) {
612 var superVar = new JS.TemporaryId('$name\$super');
613 workaroundSuper = js.statement('const # = #;', [superVar, heritage]);
614 heritage = superVar;
615 }
616 var decl = new JS.ClassDeclaration(new JS.ClassExpression(
617 new JS.Identifier(name), heritage, methods,
618 typeParams: typeParams, fields: jsFields));
619 if (workaroundSuper != null) {
620 return new JS.Block([workaroundSuper, decl]);
621 }
622 return decl;
623 }
624
625 JS.Expression _emitClassHeritage(ClassElement element) {
626 var type = element.type;
627 if (type.isObject) return null;
628
629 // Assume we can load eagerly, until proven otherwise.
630 _loader.startTopLevel(element);
631
632 // Find the super type
633 JS.Expression heritage;
634 var supertype = type.superclass;
635 if (_deferIfNeeded(supertype, element)) {
636 // Fall back to raw type.
637 supertype = fillDynamicTypeArgs(supertype.element.type, _types);
638 _hasDeferredSupertype.add(element);
639 }
640 heritage = _emitTypeName(supertype);
641
642 if (type.mixins.isNotEmpty) {
643 var mixins = type.mixins.map(_emitTypeName).toList();
644 mixins.insert(0, heritage);
645 heritage = js.call('dart.mixin(#)', [mixins]);
646 }
647
648 _loader.finishTopLevel(element);
649 return heritage;
650 }
651
652 /// Provide Dart getters and setters that forward to the underlying native
653 /// field. Note that the Dart names are always symbolized to avoid
654 /// conflicts. They will be installed as extension methods on the underlying
655 /// native type.
656 List<JS.Method> _emitNativeFieldAccessors(FieldDeclaration node) {
657 // TODO(vsm): Can this by meta-programmed?
658 // E.g., dart.nativeField(symbol, jsName)
659 // Alternatively, perhaps it could be meta-programmed directly in
660 // dart.registerExtensions?
661 var jsMethods = <JS.Method>[];
662 if (!node.isStatic) {
663 for (var decl in node.fields.variables) {
664 var field = decl.element;
665 var name = decl.name.name;
666 var annotation = findAnnotation(field, isJsName);
667 if (annotation != null) {
668 name = getConstantField(annotation, 'name', types.stringType)
669 ?.toStringValue();
670 }
671 // Generate getter
672 var fn = new JS.Fun([], js.statement('{ return this.#; }', [name]));
673 var method =
674 new JS.Method(_elementMemberName(field.getter), fn, isGetter: true);
675 jsMethods.add(method);
676
677 // Generate setter
678 if (!decl.isFinal) {
679 var value = new JS.TemporaryId('value');
680 fn = new JS.Fun(
681 [value], js.statement('{ this.# = #; }', [name, value]));
682 method = new JS.Method(_elementMemberName(field.setter), fn,
683 isSetter: true);
684 jsMethods.add(method);
685 }
686 }
687 }
688 return jsMethods;
689 }
690
691 List<JS.Method> _emitClassMethods(ClassDeclaration node,
692 List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
693 var element = node.element;
694 var type = element.type;
695 var isObject = type.isObject;
696
697 // Iff no constructor is specified for a class C, it implicitly has a
698 // default constructor `C() : super() {}`, unless C is class Object.
699 var jsMethods = <JS.Method>[];
700 if (ctors.isEmpty && !isObject) {
701 jsMethods.add(_emitImplicitConstructor(node, fields));
702 }
703
704 bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null;
705
706 bool hasIterator = false;
707 for (var m in node.members) {
708 if (m is ConstructorDeclaration) {
709 jsMethods.add(_emitConstructor(m, type, fields, isObject));
710 } else if (m is MethodDeclaration) {
711 jsMethods.add(_emitMethodDeclaration(type, m));
712
713 if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') {
714 hasIterator = true;
715 jsMethods.add(_emitIterable(type));
716 }
717 } else if (m is FieldDeclaration && _extensionTypes.contains(element)) {
718 jsMethods.addAll(_emitNativeFieldAccessors(m));
719 }
720 }
721
722 // If the type doesn't have an `iterator`, but claims to implement Iterable,
723 // we inject the adaptor method here, as it's less code size to put the
724 // helper on a parent class. This pattern is common in the core libraries
725 // (e.g. IterableMixin<E> and IterableBase<E>).
726 //
727 // (We could do this same optimization for any interface with an `iterator`
728 // method, but that's more expensive to check for, so it doesn't seem worth
729 // it. The above case for an explicit `iterator` method will catch those.)
730 if (!hasJsPeer && !hasIterator && _implementsIterable(type)) {
731 jsMethods.add(_emitIterable(type));
732 }
733
734 return jsMethods.where((m) => m != null).toList(growable: false);
735 }
736
737 bool _implementsIterable(InterfaceType t) =>
738 t.interfaces.any((i) => i.element.type == types.iterableType);
739
740 /// Support for adapting dart:core Iterable to ES6 versions.
741 ///
742 /// This lets them use for-of loops transparently:
743 /// <https://github.com/lukehoban/es6features#iterators--forof>
744 ///
745 /// This will return `null` if the adapter was already added on a super type,
746 /// otherwise it returns the adapter code.
747 // TODO(jmesserly): should we adapt `Iterator` too?
748 JS.Method _emitIterable(InterfaceType t) {
749 // If a parent had an `iterator` (concrete or abstract) or implements
750 // Iterable, we know the adapter is already there, so we can skip it as a
751 // simple code size optimization.
752 var parent = t.lookUpGetterInSuperclass('iterator', t.element.library);
753 if (parent != null) return null;
754 var parentType = findSupertype(t, _implementsIterable);
755 if (parentType != null) return null;
756
757 // Otherwise, emit the adapter method, which wraps the Dart iterator in
758 // an ES6 iterator.
759 return new JS.Method(
760 js.call('$_SYMBOL.iterator'),
761 js.call('function() { return new dart.JsIterator(this.#); }',
762 [_emitMemberName('iterator', type: t)]) as JS.Fun);
763 }
764
765 JS.Expression _instantiateAnnotation(Annotation node) {
766 var element = node.element;
767 if (element is ConstructorElement) {
768 return _emitInstanceCreationExpression(element, element.returnType,
769 node.constructorName, node.arguments, true);
770 } else {
771 return _visit(node.name);
772 }
773 }
774
775 /// Emit class members that need to come after the class declaration, such
776 /// as static fields. See [_emitClassMethods] for things that are emitted
777 /// inside the ES6 `class { ... }` node.
778 JS.Statement _finishClassMembers(
779 ClassElement classElem,
780 JS.Statement classDecl,
781 List<ConstructorDeclaration> ctors,
782 List<FieldDeclaration> fields,
783 List<FieldDeclaration> staticFields,
784 List<MethodDeclaration> methods,
785 List<Annotation> metadata,
786 String jsPeerName) {
787 var name = classElem.name;
788 var body = <JS.Statement>[];
789
790 if (_extensionTypes.contains(classElem)) {
791 var dartxNames = <JS.Expression>[];
792 for (var m in methods) {
793 if (!m.isAbstract && !m.isStatic && m.element.isPublic) {
794 dartxNames.add(_elementMemberName(m.element, allowExtensions: false));
795 }
796 }
797 for (var f in fields) {
798 if (!f.isStatic) {
799 for (var d in f.fields.variables) {
800 if (d.element.isPublic) {
801 dartxNames.add(
802 _elementMemberName(d.element.getter, allowExtensions: false));
803 }
804 }
805 }
806 }
807 if (dartxNames.isNotEmpty) {
808 body.add(js.statement('dart.defineExtensionNames(#)',
809 [new JS.ArrayInitializer(dartxNames, multiline: true)]));
810 }
811 }
812
813 body.add(classDecl);
814
815 // TODO(jmesserly): we should really just extend native Array.
816 if (jsPeerName != null && classElem.typeParameters.isNotEmpty) {
817 body.add(js.statement('dart.setBaseClass(#, dart.global.#);',
818 [classElem.name, _propertyName(jsPeerName)]));
819 }
820
821 // Deferred Superclass
822 if (_hasDeferredSupertype.contains(classElem)) {
823 body.add(js.statement('#.prototype.__proto__ = #.prototype;',
824 [name, _emitTypeName(classElem.type.superclass)]));
825 }
826
827 // Interfaces
828 if (classElem.interfaces.isNotEmpty) {
829 body.add(js.statement('#[dart.implements] = () => #;', [
830 name,
831 new JS.ArrayInitializer(new List<JS.Expression>.from(
832 classElem.interfaces.map(_emitTypeName)))
833 ]));
834 }
835
836 // Named constructors
837 for (ConstructorDeclaration member in ctors) {
838 if (member.name != null && member.factoryKeyword == null) {
839 body.add(js.statement('dart.defineNamedConstructor(#, #);',
840 [name, _emitMemberName(member.name.name, isStatic: true)]));
841 }
842 }
843
844 // Emits instance fields, if they are virtual
845 // (in other words, they override a getter/setter pair).
846 for (FieldDeclaration member in fields) {
847 for (VariableDeclaration field in member.fields.variables) {
848 if (_fieldsNeedingStorage.contains(field.element)) {
849 body.add(_overrideField(field.element));
850 }
851 }
852 }
853
854 // Emit the signature on the class recording the runtime type information
855 var extensions = _extensionsToImplement(classElem);
856 {
857 var tStatics = <JS.Property>[];
858 var tMethods = <JS.Property>[];
859 var sNames = <JS.Expression>[];
860 for (MethodDeclaration node in methods) {
861 if (!(node.isSetter || node.isGetter || node.isAbstract)) {
862 var name = node.name.name;
863 var element = node.element;
864 var inheritedElement =
865 classElem.lookUpInheritedConcreteMethod(name, currentLibrary);
866 if (inheritedElement != null &&
867 inheritedElement.type == element.type) {
868 continue;
869 }
870 var memberName = _elementMemberName(element);
871 var parts = _emitFunctionTypeParts(element.type);
872 var property =
873 new JS.Property(memberName, new JS.ArrayInitializer(parts));
874 if (node.isStatic) {
875 tStatics.add(property);
876 sNames.add(memberName);
877 } else {
878 tMethods.add(property);
879 }
880 }
881 }
882
883 var tCtors = <JS.Property>[];
884 for (ConstructorDeclaration node in ctors) {
885 var memberName = _constructorName(node.element);
886 var element = node.element;
887 var parts = _emitFunctionTypeParts(element.type, node.parameters);
888 var property =
889 new JS.Property(memberName, new JS.ArrayInitializer(parts));
890 tCtors.add(property);
891 }
892
893 JS.Property build(String name, List<JS.Property> elements) {
894 var o =
895 new JS.ObjectInitializer(elements, multiline: elements.length > 1);
896 var e = js.call('() => #', o);
897 return new JS.Property(_propertyName(name), e);
898 }
899 var sigFields = <JS.Property>[];
900 if (!tCtors.isEmpty) sigFields.add(build('constructors', tCtors));
901 if (!tMethods.isEmpty) sigFields.add(build('methods', tMethods));
902 if (!tStatics.isEmpty) {
903 assert(!sNames.isEmpty);
904 var aNames = new JS.Property(
905 _propertyName('names'), new JS.ArrayInitializer(sNames));
906 sigFields.add(build('statics', tStatics));
907 sigFields.add(aNames);
908 }
909 if (!sigFields.isEmpty || extensions.isNotEmpty) {
910 var sig = new JS.ObjectInitializer(sigFields);
911 var classExpr = new JS.Identifier(name);
912 body.add(js.statement('dart.setSignature(#, #);', [classExpr, sig]));
913 }
914 }
915
916 // If a concrete class implements one of our extensions, we might need to
917 // add forwarders.
918 if (extensions.isNotEmpty) {
919 var methodNames = <JS.Expression>[];
920 for (var e in extensions) {
921 methodNames.add(_elementMemberName(e));
922 }
923 body.add(js.statement('dart.defineExtensionMembers(#, #);', [
924 name,
925 new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4)
926 ]));
927 }
928
929 // TODO(vsm): Make this optional per #268.
930 // Metadata
931 if (metadata.isNotEmpty) {
932 body.add(js.statement('#[dart.metadata] = () => #;', [
933 name,
934 new JS.ArrayInitializer(
935 new List<JS.Expression>.from(metadata.map(_instantiateAnnotation)))
936 ]));
937 }
938
939 // Emits static fields. These are eager initialized if possible, otherwise
940 // they are made lazy.
941 var lazyStatics = <VariableDeclaration>[];
942 for (FieldDeclaration member in staticFields) {
943 for (VariableDeclaration field in member.fields.variables) {
944 JS.Statement eagerField = _emitConstantStaticField(classElem, field);
945 if (eagerField != null) {
946 body.add(eagerField);
947 } else {
948 lazyStatics.add(field);
949 }
950 }
951 }
952 if (lazyStatics.isNotEmpty) {
953 body.add(_emitLazyFields(classElem, lazyStatics));
954 }
955
956 return _statement(body);
957 }
958
959 List<ExecutableElement> _extensionsToImplement(ClassElement element) {
960 var members = <ExecutableElement>[];
961 if (_extensionTypes.contains(element)) return members;
962
963 // Collect all extension types we implement.
964 var type = element.type;
965 var types = new Set<ClassElement>();
966 _collectExtensions(type, types);
967 if (types.isEmpty) return members;
968
969 // Collect all possible extension method names.
970 var extensionMembers = new HashSet<String>();
971 for (var t in types) {
972 for (var m in [t.methods, t.accessors].expand((e) => e)) {
973 if (!m.isStatic) extensionMembers.add(m.name);
974 }
975 }
976
977 // Collect all of extension methods this type implements.
978 for (var m in [type.methods, type.accessors].expand((e) => e)) {
979 if (!m.isStatic && !m.isAbstract && extensionMembers.contains(m.name)) {
980 members.add(m);
981 }
982 }
983 return members;
984 }
985
986 /// Collections the type and all supertypes, including interfaces, but
987 /// excluding [Object].
988 void _collectExtensions(InterfaceType type, Set<ClassElement> types) {
989 if (type.isObject) return;
990 var element = type.element;
991 if (_extensionTypes.contains(element)) types.add(element);
992 for (var m in type.mixins.reversed) {
993 _collectExtensions(m, types);
994 }
995 for (var i in type.interfaces) {
996 _collectExtensions(i, types);
997 }
998 _collectExtensions(type.superclass, types);
999 }
1000
1001 JS.Statement _overrideField(FieldElement e) {
1002 var cls = e.enclosingElement;
1003 return js.statement('dart.virtualField(#, #)',
1004 [cls.name, _emitMemberName(e.name, type: cls.type)]);
1005 }
1006
1007 /// Generates the implicit default constructor for class C of the form
1008 /// `C() : super() {}`.
1009 JS.Method _emitImplicitConstructor(
1010 ClassDeclaration node, List<FieldDeclaration> fields) {
1011 assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty);
1012
1013 // If we don't have a method body, skip this.
1014 var superCall = _superConstructorCall(node.element);
1015 if (fields.isEmpty && superCall == null) return null;
1016
1017 dynamic body = _initializeFields(node, fields);
1018 if (superCall != null) {
1019 body = [
1020 [body, superCall]
1021 ];
1022 }
1023 var name = _constructorName(node.element.unnamedConstructor);
1024 return annotate(
1025 new JS.Method(name, js.call('function() { #; }', body) as JS.Fun),
1026 node,
1027 node.element);
1028 }
1029
1030 JS.Method _emitConstructor(ConstructorDeclaration node, InterfaceType type,
1031 List<FieldDeclaration> fields, bool isObject) {
1032 if (_externalOrNative(node)) return null;
1033
1034 var name = _constructorName(node.element);
1035 var returnType = emitTypeRef(node.element.enclosingElement.type);
1036
1037 // Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
1038 var redirect = node.redirectedConstructor;
1039 if (redirect != null) {
1040 var newKeyword = redirect.staticElement.isFactory ? '' : 'new';
1041 // Pass along all arguments verbatim, and let the callee handle them.
1042 // TODO(jmesserly): we'll need something different once we have
1043 // rest/spread support, but this should work for now.
1044 var params =
1045 visitFormalParameterList(node.parameters, destructure: false);
1046
1047 var fun = new JS.Fun(
1048 params,
1049 js.statement(
1050 '{ return $newKeyword #(#); }', [_visit(redirect), params]),
1051 returnType: returnType);
1052 return annotate(
1053 new JS.Method(name, fun, isStatic: true), node, node.element);
1054 }
1055
1056 // For const constructors we need to ensure default values are
1057 // available for use by top-level constant initializers.
1058 ClassDeclaration cls = node.parent;
1059 if (node.constKeyword != null) _loader.startTopLevel(cls.element);
1060 var params = visitFormalParameterList(node.parameters);
1061 if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
1062
1063 // Factory constructors are essentially static methods.
1064 if (node.factoryKeyword != null) {
1065 var body = <JS.Statement>[];
1066 var init = _emitArgumentInitializers(node, constructor: true);
1067 if (init != null) body.add(init);
1068 body.add(_visit(node.body));
1069 var fun = new JS.Fun(params, new JS.Block(body), returnType: returnType);
1070 return annotate(
1071 new JS.Method(name, fun, isStatic: true), node, node.element);
1072 }
1073
1074 // Code generation for Object's constructor.
1075 JS.Block body;
1076 if (isObject &&
1077 node.body is EmptyFunctionBody &&
1078 node.constKeyword != null &&
1079 node.name == null) {
1080 // Implements Dart constructor behavior. Because of V8 `super`
1081 // [constructor restrictions]
1082 // (https://code.google.com/p/v8/issues/detail?id=3330#c65)
1083 // we cannot currently emit actual ES6 constructors with super calls.
1084 // Instead we use the same trick as named constructors, and do them as
1085 // instance methods that perform initialization.
1086 // TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8
1087 // settles. See <https://github.com/dart-lang/dev_compiler/issues/51>.
1088 // Performance of this pattern is likely to be bad.
1089 name = _propertyName('constructor');
1090 // Mark the parameter as no-rename.
1091 body = js.statement('''{
1092 // Get the class name for this instance.
1093 let name = this.constructor.name;
1094 // Call the default constructor.
1095 let result = void 0;
1096 if (name in this) result = this[name](...arguments);
1097 return result === void 0 ? this : result;
1098 }''') as JS.Block;
1099 } else {
1100 var savedFunction = _currentFunction;
1101 _currentFunction = node.body;
1102 body = _emitConstructorBody(node, fields);
1103 _currentFunction = savedFunction;
1104 }
1105
1106 // We generate constructors as initializer methods in the class;
1107 // this allows use of `super` for instance methods/properties.
1108 // It also avoids V8 restrictions on `super` in default constructors.
1109 return annotate(
1110 new JS.Method(name, new JS.Fun(params, body, returnType: returnType)),
1111 node,
1112 node.element);
1113 }
1114
1115 JS.Expression _constructorName(ConstructorElement ctor) {
1116 var name = ctor.name;
1117 if (name != '') {
1118 return _emitMemberName(name, isStatic: true);
1119 }
1120
1121 // Factory default constructors use `new` as their name, for readability
1122 // Other default constructors use the class name, as they aren't called
1123 // from call sites, but rather from Object's constructor.
1124 // TODO(jmesserly): revisit in the context of Dart metaclasses, and cleaning
1125 // up constructors to integrate more closely with ES6.
1126 return _propertyName(ctor.isFactory ? 'new' : ctor.enclosingElement.name);
1127 }
1128
1129 JS.Block _emitConstructorBody(
1130 ConstructorDeclaration node, List<FieldDeclaration> fields) {
1131 var body = <JS.Statement>[];
1132
1133 // Generate optional/named argument value assignment. These can not have
1134 // side effects, and may be used by the constructor's initializers, so it's
1135 // nice to do them first.
1136 // Also for const constructors we need to ensure default values are
1137 // available for use by top-level constant initializers.
1138 ClassDeclaration cls = node.parent;
1139 if (node.constKeyword != null) _loader.startTopLevel(cls.element);
1140 var init = _emitArgumentInitializers(node, constructor: true);
1141 if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
1142 if (init != null) body.add(init);
1143
1144 // Redirecting constructors: these are not allowed to have initializers,
1145 // and the redirecting ctor invocation runs before field initializers.
1146 var redirectCall = node.initializers.firstWhere(
1147 (i) => i is RedirectingConstructorInvocation,
1148 orElse: () => null);
1149
1150 if (redirectCall != null) {
1151 body.add(_visit(redirectCall));
1152 return new JS.Block(body);
1153 }
1154
1155 // Generate field initializers.
1156 // These are expanded into each non-redirecting constructor.
1157 // In the future we may want to create an initializer function if we have
1158 // multiple constructors, but it needs to be balanced against readability.
1159 body.add(_initializeFields(cls, fields, node));
1160
1161 var superCall = node.initializers.firstWhere(
1162 (i) => i is SuperConstructorInvocation,
1163 orElse: () => null) as SuperConstructorInvocation;
1164
1165 // If no superinitializer is provided, an implicit superinitializer of the
1166 // form `super()` is added at the end of the initializer list, unless the
1167 // enclosing class is class Object.
1168 var jsSuper = _superConstructorCall(cls.element, superCall);
1169 if (jsSuper != null) body.add(jsSuper);
1170
1171 body.add(_visit(node.body));
1172 return new JS.Block(body)..sourceInformation = node;
1173 }
1174
1175 @override
1176 JS.Statement visitRedirectingConstructorInvocation(
1177 RedirectingConstructorInvocation node) {
1178 var name = _constructorName(node.staticElement);
1179 return js.statement('this.#(#);', [name, _visit(node.argumentList)]);
1180 }
1181
1182 JS.Statement _superConstructorCall(ClassElement element,
1183 [SuperConstructorInvocation node]) {
1184 ConstructorElement superCtor;
1185 if (node != null) {
1186 superCtor = node.staticElement;
1187 } else {
1188 // Get the supertype's unnamed constructor.
1189 superCtor = element.supertype.element.unnamedConstructor;
1190 if (superCtor == null) {
1191 // This will only happen if the code has errors:
1192 // we're trying to generate an implicit constructor for a type where
1193 // we don't have a default constructor in the supertype.
1194 assert(options.forceCompile);
1195 return null;
1196 }
1197 }
1198
1199 if (superCtor == null) {
1200 print('Error generating: ${element.displayName}');
1201 }
1202 if (superCtor.name == '' && !_shouldCallUnnamedSuperCtor(element)) {
1203 return null;
1204 }
1205
1206 var name = _constructorName(superCtor);
1207 var args = node != null ? _visit(node.argumentList) : [];
1208 return annotate(js.statement('super.#(#);', [name, args]), node);
1209 }
1210
1211 bool _shouldCallUnnamedSuperCtor(ClassElement e) {
1212 var supertype = e.supertype;
1213 if (supertype == null) return false;
1214 if (_hasUnnamedConstructor(supertype.element)) return true;
1215 for (var mixin in e.mixins) {
1216 if (_hasUnnamedConstructor(mixin.element)) return true;
1217 }
1218 return false;
1219 }
1220
1221 bool _hasUnnamedConstructor(ClassElement e) {
1222 if (e.type.isObject) return false;
1223 if (!e.unnamedConstructor.isSynthetic) return true;
1224 return e.fields.any((f) => !f.isStatic && !f.isSynthetic);
1225 }
1226
1227 /// Initialize fields. They follow the sequence:
1228 ///
1229 /// 1. field declaration initializer if non-const,
1230 /// 2. field initializing parameters,
1231 /// 3. constructor field initializers,
1232 /// 4. initialize fields not covered in 1-3
1233 JS.Statement _initializeFields(
1234 ClassDeclaration cls, List<FieldDeclaration> fieldDecls,
1235 [ConstructorDeclaration ctor]) {
1236 bool isConst = ctor != null && ctor.constKeyword != null;
1237 if (isConst) _loader.startTopLevel(cls.element);
1238
1239 // Run field initializers if they can have side-effects.
1240 var fields = new Map<FieldElement, JS.Expression>();
1241 var unsetFields = new Map<FieldElement, VariableDeclaration>();
1242 for (var declaration in fieldDecls) {
1243 for (var fieldNode in declaration.fields.variables) {
1244 var element = fieldNode.element;
1245 if (_constField.isFieldInitConstant(fieldNode)) {
1246 unsetFields[element as FieldElement] = fieldNode;
1247 } else {
1248 fields[element as FieldElement] = _visitInitializer(fieldNode);
1249 }
1250 }
1251 }
1252
1253 // Initialize fields from `this.fieldName` parameters.
1254 if (ctor != null) {
1255 for (var p in ctor.parameters.parameters) {
1256 var element = p.element;
1257 if (element is FieldFormalParameterElement) {
1258 fields[element.field] = visitSimpleIdentifier(p.identifier);
1259 }
1260 }
1261
1262 // Run constructor field initializers such as `: foo = bar.baz`
1263 for (var init in ctor.initializers) {
1264 if (init is ConstructorFieldInitializer) {
1265 fields[init.fieldName.staticElement as FieldElement] =
1266 _visit(init.expression);
1267 }
1268 }
1269 }
1270
1271 for (var f in fields.keys) unsetFields.remove(f);
1272
1273 // Initialize all remaining fields
1274 unsetFields.forEach((element, fieldNode) {
1275 JS.Expression value;
1276 if (fieldNode.initializer != null) {
1277 value = _visit(fieldNode.initializer);
1278 } else {
1279 value = new JS.LiteralNull();
1280 }
1281 fields[element] = value;
1282 });
1283
1284 var body = <JS.Statement>[];
1285 fields.forEach((FieldElement e, JS.Expression initialValue) {
1286 var access = _emitMemberName(e.name, type: e.enclosingElement.type);
1287 body.add(js.statement('this.# = #;', [access, initialValue]));
1288 });
1289
1290 if (isConst) _loader.finishTopLevel(cls.element);
1291 return _statement(body);
1292 }
1293
1294 FormalParameterList _parametersOf(node) {
1295 // TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we
1296 // could handle argument initializers more consistently in a separate
1297 // lowering pass.
1298 if (node is ConstructorDeclaration) return node.parameters;
1299 if (node is MethodDeclaration) return node.parameters;
1300 if (node is FunctionDeclaration) node = node.functionExpression;
1301 return (node as FunctionExpression).parameters;
1302 }
1303
1304 /// Emits argument initializers, which handles optional/named args, as well
1305 /// as generic type checks needed due to our covariance.
1306 JS.Statement _emitArgumentInitializers(node, {bool constructor: false}) {
1307 // Constructor argument initializers are emitted earlier in the code, rather
1308 // than always when we visit the function body, so we control it explicitly.
1309 if (node is ConstructorDeclaration != constructor) return null;
1310
1311 var parameters = _parametersOf(node);
1312 if (parameters == null) return null;
1313
1314 var body = <JS.Statement>[];
1315 for (var param in parameters.parameters) {
1316 var jsParam = visitSimpleIdentifier(param.identifier);
1317
1318 if (!options.destructureNamedParams) {
1319 if (param.kind == ParameterKind.NAMED) {
1320 // Parameters will be passed using their real names, not the (possibly
1321 // renamed) local variable.
1322 var paramName = js.string(param.identifier.name, "'");
1323
1324 // TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming.
1325 body.add(js.statement('let # = # && # in # ? #.# : #;', [
1326 jsParam,
1327 namedArgumentTemp,
1328 paramName,
1329 namedArgumentTemp,
1330 namedArgumentTemp,
1331 paramName,
1332 _defaultParamValue(param),
1333 ]));
1334 } else if (param.kind == ParameterKind.POSITIONAL) {
1335 body.add(js.statement('if (# === void 0) # = #;',
1336 [jsParam, jsParam, _defaultParamValue(param)]));
1337 }
1338 }
1339
1340 // TODO(jmesserly): various problems here, see:
1341 // https://github.com/dart-lang/dev_compiler/issues/161
1342 var paramType = param.element.type;
1343 if (!constructor && _hasUnsoundTypeParameter(paramType)) {
1344 body.add(js
1345 .statement('dart.as(#, #);', [jsParam, _emitTypeName(paramType)]));
1346 }
1347 }
1348 return body.isEmpty ? null : _statement(body);
1349 }
1350
1351 bool _isUnsoundTypeParameter(DartType t) =>
1352 t is TypeParameterType && t.element.enclosingElement is ClassElement;
1353
1354 bool _hasUnsoundTypeParameter(DartType t) =>
1355 _isUnsoundTypeParameter(t) ||
1356 t is ParameterizedType && t.typeArguments.any(_hasUnsoundTypeParameter);
1357
1358 JS.Expression _defaultParamValue(FormalParameter param) {
1359 if (param is DefaultFormalParameter && param.defaultValue != null) {
1360 return _visit(param.defaultValue);
1361 } else {
1362 return new JS.LiteralNull();
1363 }
1364 }
1365
1366 JS.Fun _emitNativeFunctionBody(MethodDeclaration node) {
1367 if (node.isStatic) {
1368 // TODO(vsm): Do we need to handle this case?
1369 return null;
1370 }
1371
1372 var params = visitFormalParameterList(node.parameters, destructure: false);
1373 String name = node.name.name;
1374 var annotation = findAnnotation(node.element, isJsName);
1375 if (annotation != null) {
1376 name = getConstantField(annotation, 'name', types.stringType)
1377 ?.toStringValue();
1378 }
1379 if (node.isGetter) {
1380 return new JS.Fun(params, js.statement('{ return this.#; }', [name]));
1381 } else if (node.isSetter) {
1382 return new JS.Fun(
1383 params, js.statement('{ this.# = #; }', [name, params.last]));
1384 } else {
1385 return new JS.Fun(
1386 params, js.statement('{ return this.#(#); }', [name, params]));
1387 }
1388 }
1389
1390 JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
1391 if (node.isAbstract) {
1392 return null;
1393 }
1394
1395 JS.Fun fn;
1396 if (_externalOrNative(node)) {
1397 fn = _emitNativeFunctionBody(node);
1398 // TODO(vsm): Remove if / when we handle the static case above.
1399 if (fn == null) return null;
1400 } else {
1401 fn = _emitFunctionBody(node.element, node.parameters, node.body);
1402
1403 if (node.operatorKeyword != null &&
1404 node.name.name == '[]=' &&
1405 fn.params.isNotEmpty) {
1406 // []= methods need to return the value. We could also address this at
1407 // call sites, but it's cleaner to instead transform the operator method .
1408 fn = _alwaysReturnLastParameter(fn);
1409 }
1410 }
1411
1412 return annotate(
1413 new JS.Method(_elementMemberName(node.element), fn,
1414 isGetter: node.isGetter,
1415 isSetter: node.isSetter,
1416 isStatic: node.isStatic),
1417 node,
1418 node.element);
1419 }
1420
1421 /// Transform the function so the last parameter is always returned.
1422 ///
1423 /// This is useful for indexed set methods, which otherwise would not have
1424 /// the right return value in JS.
1425 JS.Fun _alwaysReturnLastParameter(JS.Fun fn) {
1426 var body = fn.body;
1427 if (JS.Return.foundIn(fn)) {
1428 // If a return is inside body, transform `(params) { body }` to
1429 // `(params) { (() => { body })(); return value; }`.
1430 // TODO(jmesserly): we could instead generate the return differently,
1431 // and avoid the immediately invoked function.
1432 body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement();
1433 }
1434 // Rewrite the function to include the return.
1435 return new JS.Fun(
1436 fn.params, new JS.Block([body, new JS.Return(fn.params.last)]),
1437 typeParams: fn.typeParams,
1438 returnType: fn.returnType)..sourceInformation = fn.sourceInformation;
1439 }
1440
1441 @override
1442 JS.Statement visitFunctionDeclaration(FunctionDeclaration node) {
1443 assert(node.parent is CompilationUnit);
1444
1445 if (_externalOrNative(node)) return null;
1446
1447 if (node.isGetter || node.isSetter) {
1448 // Add these later so we can use getter/setter syntax.
1449 _properties.add(node);
1450 return null;
1451 }
1452
1453 var body = <JS.Statement>[];
1454 _flushLibraryProperties(body);
1455
1456 var name = node.name.name;
1457 var fn = _emitFunction(node.functionExpression);
1458
1459 if (currentLibrary.source.isInSystemLibrary &&
1460 _isInlineJSFunction(node.functionExpression)) {
1461 fn = _simplifyPassThroughArrowFunCallBody(fn);
1462 }
1463
1464 var id = new JS.Identifier(name);
1465 body.add(annotate(new JS.FunctionDeclaration(id, fn), node, node.element));
1466 if (!_isDartRuntime) {
1467 body.add(_emitFunctionTagged(id, node.element.type, topLevel: true)
1468 .toStatement());
1469 }
1470
1471 if (isPublic(name)) {
1472 _addExport(name, getJSExportName(node.element, types) ?? name);
1473 }
1474 return _statement(body);
1475 }
1476
1477 bool _isInlineJSFunction(FunctionExpression functionExpression) {
1478 var body = functionExpression.body;
1479 if (body is ExpressionFunctionBody) {
1480 return _isJSInvocation(body.expression);
1481 } else if (body is BlockFunctionBody) {
1482 if (body.block.statements.length == 1) {
1483 var stat = body.block.statements.single;
1484 if (stat is ReturnStatement) {
1485 return _isJSInvocation(stat.expression);
1486 }
1487 }
1488 }
1489 return false;
1490 }
1491
1492 bool _isJSInvocation(Expression expr) =>
1493 expr is MethodInvocation && isInlineJS(expr.methodName.staticElement);
1494
1495 // Simplify `(args) => (() => { ... })()` to `(args) => { ... }`.
1496 // Note: this allows silently passing args through to the body, which only
1497 // works if we don't do weird renamings of Dart params.
1498 JS.Fun _simplifyPassThroughArrowFunCallBody(JS.Fun fn) {
1499 if (fn.body is JS.Block && fn.body.statements.length == 1) {
1500 var stat = fn.body.statements.single;
1501 if (stat is JS.Return && stat.value is JS.Call) {
1502 JS.Call call = stat.value;
1503 if (call.target is JS.ArrowFun && call.arguments.isEmpty) {
1504 JS.ArrowFun innerFun = call.target;
1505 if (innerFun.params.isEmpty) {
1506 return new JS.Fun(fn.params, innerFun.body,
1507 typeParams: fn.typeParams, returnType: fn.returnType);
1508 }
1509 }
1510 }
1511 }
1512 return fn;
1513 }
1514
1515 JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
1516 var name = node.name.name;
1517 return annotate(
1518 new JS.Method(
1519 _propertyName(name), _emitFunction(node.functionExpression),
1520 isGetter: node.isGetter, isSetter: node.isSetter),
1521 node,
1522 node.element);
1523 }
1524
1525 bool _executesAtTopLevel(AstNode node) {
1526 var ancestor = node.getAncestor((n) =>
1527 n is FunctionBody ||
1528 (n is FieldDeclaration && n.staticKeyword == null) ||
1529 (n is ConstructorDeclaration && n.constKeyword == null));
1530 return ancestor == null;
1531 }
1532
1533 bool _typeIsLoaded(DartType type) {
1534 if (type is FunctionType && (type.name == '' || type.name == null)) {
1535 return (_typeIsLoaded(type.returnType) &&
1536 type.optionalParameterTypes.every(_typeIsLoaded) &&
1537 type.namedParameterTypes.values.every(_typeIsLoaded) &&
1538 type.normalParameterTypes.every(_typeIsLoaded));
1539 }
1540 if (type.isDynamic || type.isVoid || type.isBottom) return true;
1541 if (type is ParameterizedType && !type.typeArguments.every(_typeIsLoaded)) {
1542 return false;
1543 }
1544 return _loader.isLoaded(type.element);
1545 }
1546
1547 JS.Expression _emitFunctionTagged(JS.Expression fn, DartType type,
1548 {topLevel: false}) {
1549 var name = type.name;
1550 var lazy = topLevel && !_typeIsLoaded(type);
1551
1552 if (type is FunctionType && (name == '' || name == null)) {
1553 if (type.returnType.isDynamic &&
1554 type.optionalParameterTypes.isEmpty &&
1555 type.namedParameterTypes.isEmpty &&
1556 type.normalParameterTypes.every((t) => t.isDynamic)) {
1557 return js.call('dart.fn(#)', [fn]);
1558 }
1559 if (lazy) {
1560 return js.call('dart.fn(#, () => #)', [fn, _emitFunctionRTTI(type)]);
1561 }
1562 return js.call('dart.fn(#, #)', [fn, _emitFunctionTypeParts(type)]);
1563 }
1564 throw 'Function has non function type: $type';
1565 }
1566
1567 /// Emits an arrow FunctionExpression node.
1568 ///
1569 /// This should be used for all places in Dart's AST where FunctionExpression
1570 /// appears and the function is actually in an Expression context. These
1571 /// correspond to arrow functions in Dart.
1572 ///
1573 /// Contrast with [_emitFunction].
1574 @override
1575 JS.Expression visitFunctionExpression(FunctionExpression node) {
1576 assert(node.parent is! FunctionDeclaration &&
1577 node.parent is! MethodDeclaration);
1578 return _emitFunctionTagged(_emitArrowFunction(node), getStaticType(node),
1579 topLevel: _executesAtTopLevel(node));
1580 }
1581
1582 JS.ArrowFun _emitArrowFunction(FunctionExpression node) {
1583 JS.Fun f = _emitFunctionBody(node.element, node.parameters, node.body);
1584 var body = f.body;
1585
1586 // Simplify `=> { return e; }` to `=> e`
1587 if (body is JS.Block) {
1588 JS.Block block = body;
1589 if (block.statements.length == 1) {
1590 JS.Statement s = block.statements[0];
1591 if (s is JS.Return) body = s.value;
1592 }
1593 }
1594
1595 // Convert `function(...) { ... }` to `(...) => ...`
1596 // This is for readability, but it also ensures correct `this` binding.
1597 return annotate(
1598 new JS.ArrowFun(f.params, body,
1599 typeParams: f.typeParams, returnType: f.returnType),
1600 node);
1601 }
1602
1603 /// Emits a non-arrow FunctionExpression node.
1604 ///
1605 /// This should be used for all places in Dart's AST where FunctionExpression
1606 /// appears but the function is not actually in an Expression context, such
1607 /// as methods, properties, and top-level functions.
1608 ///
1609 /// Contrast with [visitFunctionExpression].
1610 JS.Fun _emitFunction(FunctionExpression node) {
1611 var fn = _emitFunctionBody(node.element, node.parameters, node.body);
1612 return annotate(fn, node);
1613 }
1614
1615 JS.Fun _emitFunctionBody(ExecutableElement element,
1616 FormalParameterList parameters, FunctionBody body) {
1617 var returnType = emitTypeRef(element.returnType);
1618
1619 // sync*, async, async*
1620 if (element.isAsynchronous || element.isGenerator) {
1621 return new JS.Fun(
1622 visitFormalParameterList(parameters, destructure: false),
1623 js.statement('{ return #; }',
1624 [_emitGeneratorFunctionBody(element, parameters, body)]),
1625 returnType: returnType);
1626 }
1627 // normal function (sync)
1628 return new JS.Fun(visitFormalParameterList(parameters), _visit(body),
1629 typeParams: _emitTypeFormals(element.typeParameters),
1630 returnType: returnType);
1631 }
1632
1633 JS.Expression _emitGeneratorFunctionBody(ExecutableElement element,
1634 FormalParameterList parameters, FunctionBody body) {
1635 var kind = element.isSynchronous ? 'sync' : 'async';
1636 if (element.isGenerator) kind += 'Star';
1637
1638 // Transforms `sync*` `async` and `async*` function bodies
1639 // using ES6 generators.
1640 //
1641 // `sync*` wraps a generator in a Dart Iterable<T>:
1642 //
1643 // function name(<args>) {
1644 // return dart.syncStar(function*(<args>) {
1645 // <body>
1646 // }, T, <args>).bind(this);
1647 // }
1648 //
1649 // We need to include <args> in case any are mutated, so each `.iterator`
1650 // gets the same initial values.
1651 //
1652 // TODO(jmesserly): we could omit the args for the common case where args
1653 // are not mutated inside the generator.
1654 //
1655 // In the future, we might be able to simplify this, see:
1656 // https://github.com/dart-lang/dev_compiler/issues/247.
1657 //
1658 // `async` works the same, but uses the `dart.async` helper.
1659 //
1660 // In the body of a `sync*` and `async`, `yield`/`await` are both generated
1661 // simply as `yield`.
1662 //
1663 // `async*` uses the `dart.asyncStar` helper, and also has an extra `stream`
1664 // argument to the generator, which is used for passing values to the
1665 // _AsyncStarStreamController implementation type.
1666 // `yield` is specially generated inside `async*`, see visitYieldStatement.
1667 // `await` is generated as `yield`.
1668 // runtime/_generators.js has an example of what the code is generated as.
1669 var savedController = _asyncStarController;
1670 List jsParams = visitFormalParameterList(parameters);
1671 if (kind == 'asyncStar') {
1672 _asyncStarController = new JS.TemporaryId('stream');
1673 jsParams.insert(0, _asyncStarController);
1674 } else {
1675 _asyncStarController = null;
1676 }
1677 // Visit the body with our async* controller set.
1678 var jsBody = _visit(body);
1679 _asyncStarController = savedController;
1680
1681 DartType returnType = _getExpectedReturnType(element);
1682 JS.Expression gen = new JS.Fun(jsParams, jsBody,
1683 isGenerator: true, returnType: emitTypeRef(returnType));
1684 if (JS.This.foundIn(gen)) {
1685 gen = js.call('#.bind(this)', gen);
1686 }
1687
1688 var T = _emitTypeName(returnType);
1689 return js.call('dart.#(#)', [
1690 kind,
1691 [gen, T]..addAll(visitFormalParameterList(parameters, destructure: false))
1692 ]);
1693 }
1694
1695 @override
1696 JS.Statement visitFunctionDeclarationStatement(
1697 FunctionDeclarationStatement node) {
1698 var func = node.functionDeclaration;
1699 if (func.isGetter || func.isSetter) {
1700 return js.comment('Unimplemented function get/set statement: $node');
1701 }
1702
1703 var fn = _emitFunction(func.functionExpression);
1704
1705 var name = new JS.Identifier(func.name.name);
1706 JS.Statement declareFn;
1707 if (JS.This.foundIn(fn)) {
1708 declareFn = js.statement('const # = #.bind(this);', [name, fn]);
1709 } else {
1710 declareFn = new JS.FunctionDeclaration(name, fn);
1711 }
1712 declareFn = annotate(declareFn, node, node.functionDeclaration.element);
1713
1714 return new JS.Block([
1715 declareFn,
1716 _emitFunctionTagged(name, func.element.type).toStatement()
1717 ]);
1718 }
1719
1720 /// Writes a simple identifier. This can handle implicit `this` as well as
1721 /// going through the qualified library name if necessary.
1722 @override
1723 JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
1724 var accessor = node.staticElement;
1725 if (accessor == null) {
1726 return js.commentExpression(
1727 'Unimplemented unknown name', new JS.Identifier(node.name));
1728 }
1729
1730 // Get the original declaring element. If we had a property accessor, this
1731 // indirects back to a (possibly synthetic) field.
1732 var element = accessor;
1733 if (accessor is PropertyAccessorElement) element = accessor.variable;
1734
1735 _loader.declareBeforeUse(element);
1736
1737 // type literal
1738 if (element is TypeDefiningElement) {
1739 return _emitTypeName(
1740 fillDynamicTypeArgs((element as dynamic).type, types));
1741 }
1742
1743 // library member
1744 if (element.enclosingElement is CompilationUnitElement) {
1745 return _emitTopLevelName(element);
1746 }
1747
1748 var name = element.name;
1749
1750 // Unqualified class member. This could mean implicit-this, or implicit
1751 // call to a static from the same class.
1752 if (element is ClassMemberElement && element is! ConstructorElement) {
1753 bool isStatic = element.isStatic;
1754 var type = element.enclosingElement.type;
1755 var member = _emitMemberName(name, isStatic: isStatic, type: type);
1756
1757 // For static methods, we add the raw type name, without generics or
1758 // library prefix. We don't need those because static calls can't use
1759 // the generic type.
1760 if (isStatic) {
1761 var dynType = _emitTypeName(fillDynamicTypeArgs(type, types));
1762 return new JS.PropertyAccess(dynType, member);
1763 }
1764
1765 // For instance members, we add implicit-this.
1766 // For method tear-offs, we ensure it's a bound method.
1767 var tearOff = element is MethodElement && !inInvocationContext(node);
1768 var code = (tearOff) ? 'dart.bind(this, #)' : 'this.#';
1769 return js.call(code, member);
1770 }
1771
1772 if (element is ParameterElement) {
1773 return _emitParameter(element);
1774 }
1775
1776 if (element is TemporaryVariableElement) {
1777 if (name[0] == '#') {
1778 return new JS.InterpolatedExpression(name.substring(1));
1779 } else {
1780 return _getTemp(element, name);
1781 }
1782 }
1783
1784 return new JS.Identifier(name);
1785 }
1786
1787 JS.Identifier _emitParameter(ParameterElement element,
1788 {bool declaration: false}) {
1789 // initializing formal parameter, e.g. `Point(this._x)`
1790 // TODO(jmesserly): type ref is not attached in this case.
1791 if (element.isInitializingFormal && element.isPrivate) {
1792 /// Rename private names so they don't shadow the private field symbol.
1793 /// The renamer would handle this, but it would prefer to rename the
1794 /// temporary used for the private symbol. Instead rename the parameter.
1795 return _getTemp(element, '${element.name.substring(1)}');
1796 }
1797
1798 var type = declaration ? emitTypeRef(element.type) : null;
1799 return new JS.Identifier(element.name, type: type);
1800 }
1801
1802 JS.TemporaryId _getTemp(Element key, String name) =>
1803 _temps.putIfAbsent(key, () => new JS.TemporaryId(name));
1804
1805 List<Annotation> _parameterMetadata(FormalParameter p) =>
1806 (p is NormalFormalParameter)
1807 ? p.metadata
1808 : (p as DefaultFormalParameter).parameter.metadata;
1809
1810 JS.ArrayInitializer _emitTypeNames(List<DartType> types,
1811 [List<FormalParameter> parameters]) {
1812 var result = <JS.Expression>[];
1813 for (int i = 0; i < types.length; ++i) {
1814 var metadata =
1815 parameters != null ? _parameterMetadata(parameters[i]) : [];
1816 var typeName = _emitTypeName(types[i]);
1817 var value = typeName;
1818 // TODO(vsm): Make this optional per #268.
1819 if (metadata.isNotEmpty) {
1820 metadata = metadata.map(_instantiateAnnotation).toList();
1821 value = new JS.ArrayInitializer([typeName]..addAll(metadata));
1822 }
1823 result.add(value);
1824 }
1825 return new JS.ArrayInitializer(result);
1826 }
1827
1828 JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) {
1829 var properties = <JS.Property>[];
1830 types.forEach((name, type) {
1831 var key = _propertyName(name);
1832 var value = _emitTypeName(type);
1833 properties.add(new JS.Property(key, value));
1834 });
1835 return new JS.ObjectInitializer(properties);
1836 }
1837
1838 /// Emit the pieces of a function type, as an array of return type,
1839 /// regular args, and optional/named args.
1840 List<JS.Expression> _emitFunctionTypeParts(FunctionType type,
1841 [FormalParameterList parameterList]) {
1842 var parameters = parameterList?.parameters;
1843 var returnType = type.returnType;
1844 var parameterTypes = type.normalParameterTypes;
1845 var optionalTypes = type.optionalParameterTypes;
1846 var namedTypes = type.namedParameterTypes;
1847 var rt = _emitTypeName(returnType);
1848 var ra = _emitTypeNames(parameterTypes, parameters);
1849 if (!namedTypes.isEmpty) {
1850 assert(optionalTypes.isEmpty);
1851 // TODO(vsm): Pass in annotations here as well.
1852 var na = _emitTypeProperties(namedTypes);
1853 return [rt, ra, na];
1854 }
1855 if (!optionalTypes.isEmpty) {
1856 assert(namedTypes.isEmpty);
1857 var oa = _emitTypeNames(
1858 optionalTypes, parameters?.sublist(parameterTypes.length));
1859 return [rt, ra, oa];
1860 }
1861 return [rt, ra];
1862 }
1863
1864 JS.Expression _emitFunctionRTTI(FunctionType type) {
1865 var parts = _emitFunctionTypeParts(type);
1866 return js.call('dart.definiteFunctionType(#)', [parts]);
1867 }
1868
1869 /// Emits a Dart [type] into code.
1870 ///
1871 /// If [lowerTypedef] is set, a typedef will be expanded as if it were a
1872 /// function type. Similarly if [lowerGeneric] is set, the `List$()` form
1873 /// will be used instead of `List`. These flags are used when generating
1874 /// the definitions for typedefs and generic types, respectively.
1875 JS.Expression _emitTypeName(DartType type,
1876 {bool lowerTypedef: false, bool lowerGeneric: false}) {
1877 // The void and dynamic types are not defined in core.
1878 if (type.isVoid) {
1879 return js.call('dart.void');
1880 } else if (type.isDynamic) {
1881 return js.call('dart.dynamic');
1882 } else if (type.isBottom) {
1883 return js.call('dart.bottom');
1884 }
1885
1886 _loader.declareBeforeUse(type.element);
1887
1888 // TODO(jmesserly): like constants, should we hoist function types out of
1889 // methods? Similar issue with generic types. For all of these, we may want
1890 // to canonicalize them too, at least when inside the same library.
1891 var name = type.name;
1892 var element = type.element;
1893 if (name == '' || name == null || lowerTypedef) {
1894 var parts = _emitFunctionTypeParts(type as FunctionType);
1895 return js.call('dart.functionType(#)', [parts]);
1896 }
1897 // For now, reify generic method parameters as dynamic
1898 bool _isGenericTypeParameter(DartType type) =>
1899 (type is TypeParameterType) &&
1900 !(type.element.enclosingElement is ClassElement ||
1901 type.element.enclosingElement is FunctionTypeAliasElement);
1902
1903 if (_isGenericTypeParameter(type)) {
1904 return js.call('dart.dynamic');
1905 }
1906
1907 if (type is TypeParameterType) {
1908 return new JS.Identifier(name);
1909 }
1910
1911 if (type is ParameterizedType) {
1912 var args = type.typeArguments;
1913 var isCurrentClass =
1914 args.isNotEmpty && _loader.isCurrentElement(type.element);
1915 Iterable jsArgs = null;
1916 if (args
1917 .any((a) => a != types.dynamicType && !_isGenericTypeParameter(a))) {
1918 jsArgs = args.map(_emitTypeName);
1919 } else if (lowerGeneric || isCurrentClass) {
1920 // When creating a `new S<dynamic>` we try and use the raw form
1921 // `new S()`, but this does not work if we're inside the same class,
1922 // because `S` refers to the current S<T> we are generating.
1923 jsArgs = [];
1924 }
1925 if (jsArgs != null) {
1926 var genericName = _emitTopLevelName(element, suffix: '\$');
1927 return js.call('#(#)', [genericName, jsArgs]);
1928 }
1929 }
1930
1931 return _emitTopLevelName(element);
1932 }
1933
1934 JS.Expression _emitTopLevelName(Element e, {String suffix: ''}) {
1935 var libName = emitLibraryName(e.library);
1936
1937 // Always qualify:
1938 // * mutable top-level fields
1939 // * elements from other libraries
1940 bool mutableTopLevel = e is TopLevelVariableElement &&
1941 !e.isConst &&
1942 !_isFinalJSDecl(e.computeNode());
1943 bool fromAnotherLibrary = e.library != currentLibrary;
1944 var nameExpr;
1945 if (fromAnotherLibrary) {
1946 nameExpr = _propertyName((getJSExportName(e, types) ?? e.name) + suffix);
1947 } else {
1948 nameExpr = _propertyName(e.name + suffix);
1949 }
1950 if (mutableTopLevel || fromAnotherLibrary) {
1951 return new JS.PropertyAccess(libName, nameExpr);
1952 }
1953
1954 var id = new JS.MaybeQualifiedId(libName, nameExpr);
1955 _qualifiedIds.add(new Tuple2(e, id));
1956 return id;
1957 }
1958
1959 @override
1960 JS.Expression visitAssignmentExpression(AssignmentExpression node) {
1961 var left = node.leftHandSide;
1962 var right = node.rightHandSide;
1963 if (node.operator.type == TokenType.EQ) return _emitSet(left, right);
1964 var op = node.operator.lexeme;
1965 assert(op.endsWith('='));
1966 op = op.substring(0, op.length - 1); // remove trailing '='
1967 return _emitOpAssign(left, right, op, node.staticElement, context: node);
1968 }
1969
1970 JS.MetaLet _emitOpAssign(
1971 Expression left, Expression right, String op, MethodElement element,
1972 {Expression context}) {
1973 if (op == '??') {
1974 // Desugar `l ??= r` as ((x) => x == null ? l = r : x)(l)
1975 // Note that if `x` contains subexpressions, we need to ensure those
1976 // are also evaluated only once. This is similar to desguaring for
1977 // postfix expressions like `i++`.
1978
1979 // Handle the left hand side, to ensure each of its subexpressions are
1980 // evaluated only once.
1981 var vars = <String, JS.Expression>{};
1982 var x = _bindLeftHandSide(vars, left, context: left);
1983 // Capture the result of evaluating the left hand side in a temp.
1984 var t = _bindValue(vars, 't', x, context: x);
1985 return new JS.MetaLet(vars, [
1986 js.call('# == null ? # : #', [_visit(t), _emitSet(x, right), _visit(t)])
1987 ]);
1988 }
1989
1990 // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
1991 // (for example, x is IndexExpression) we evaluate those once.
1992 var vars = <String, JS.Expression>{};
1993 var lhs = _bindLeftHandSide(vars, left, context: context);
1994 var inc = AstBuilder.binaryExpression(lhs, op, right);
1995 inc.staticElement = element;
1996 inc.staticType = getStaticType(left);
1997 return new JS.MetaLet(vars, [_emitSet(lhs, inc)]);
1998 }
1999
2000 JS.Expression _emitSet(Expression lhs, Expression rhs) {
2001 if (lhs is IndexExpression) {
2002 var target = _getTarget(lhs);
2003 if (_useNativeJsIndexer(target.staticType)) {
2004 return js
2005 .call('#[#] = #', [_visit(target), _visit(lhs.index), _visit(rhs)]);
2006 }
2007 return _emitSend(target, '[]=', [lhs.index, rhs]);
2008 }
2009
2010 Expression target = null;
2011 SimpleIdentifier id;
2012 if (lhs is PropertyAccess) {
2013 if (lhs.operator.lexeme == '?.') {
2014 return _emitNullSafeSet(lhs, rhs);
2015 }
2016
2017 target = _getTarget(lhs);
2018 id = lhs.propertyName;
2019 } else if (lhs is PrefixedIdentifier) {
2020 target = lhs.prefix;
2021 id = lhs.identifier;
2022 }
2023
2024 if (target != null && DynamicInvoke.get(target)) {
2025 return js.call('dart.$DPUT(#, #, #)',
2026 [_visit(target), _emitMemberName(id.name), _visit(rhs)]);
2027 }
2028
2029 return _visit(rhs).toAssignExpression(_visit(lhs));
2030 }
2031
2032 JS.Expression _emitNullSafeSet(PropertyAccess node, Expression right) {
2033 // Emit `obj?.prop = expr` as:
2034 //
2035 // (_ => _ == null ? null : _.prop = expr)(obj).
2036 //
2037 // We could use a helper, e.g.: `nullSafeSet(e1, _ => _.v = e2)`
2038 //
2039 // However with MetaLet, we get clean code in statement or void context,
2040 // or when one of the expressions is stateless, which seems common.
2041 var vars = <String, JS.Expression>{};
2042 var left = _bindValue(vars, 'l', node.target);
2043 var body = js.call('# == null ? null : #',
2044 [_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]);
2045 return new JS.MetaLet(vars, [body]);
2046 }
2047
2048 @override
2049 JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) {
2050 var savedFunction = _currentFunction;
2051 _currentFunction = node;
2052 var initArgs = _emitArgumentInitializers(node.parent);
2053 var ret = new JS.Return(_visit(node.expression));
2054 _currentFunction = savedFunction;
2055 return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]);
2056 }
2057
2058 @override
2059 JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]);
2060
2061 @override
2062 JS.Block visitBlockFunctionBody(BlockFunctionBody node) {
2063 var savedFunction = _currentFunction;
2064 _currentFunction = node;
2065 var initArgs = _emitArgumentInitializers(node.parent);
2066 var stmts = _visitList(node.block.statements) as List<JS.Statement>;
2067 if (initArgs != null) stmts.insert(0, initArgs);
2068 _currentFunction = savedFunction;
2069 return new JS.Block(stmts);
2070 }
2071
2072 @override
2073 JS.Block visitBlock(Block node) =>
2074 new JS.Block(_visitList(node.statements) as List<JS.Statement>,
2075 isScope: true);
2076
2077 @override
2078 visitMethodInvocation(MethodInvocation node) {
2079 if (node.operator != null && node.operator.lexeme == '?.') {
2080 return _emitNullSafe(node);
2081 }
2082
2083 var target = _getTarget(node);
2084 var result = _emitForeignJS(node);
2085 if (result != null) return result;
2086
2087 String code;
2088 if (target == null || isLibraryPrefix(target)) {
2089 if (DynamicInvoke.get(node.methodName)) {
2090 code = 'dart.$DCALL(#, #)';
2091 } else {
2092 code = '#(#)';
2093 }
2094 return js
2095 .call(code, [_visit(node.methodName), _visit(node.argumentList)]);
2096 }
2097
2098 var type = getStaticType(target);
2099 var name = node.methodName.name;
2100 var element = node.methodName.staticElement;
2101 bool isStatic = element is ExecutableElement && element.isStatic;
2102 var memberName = _emitMemberName(name, type: type, isStatic: isStatic);
2103
2104 if (DynamicInvoke.get(target)) {
2105 code = 'dart.$DSEND(#, #, #)';
2106 } else if (DynamicInvoke.get(node.methodName)) {
2107 // This is a dynamic call to a statically known target. For example:
2108 // class Foo { Function bar; }
2109 // new Foo().bar(); // dynamic call
2110 code = 'dart.$DCALL(#.#, #)';
2111 } else if (_requiresStaticDispatch(target, name)) {
2112 // Object methods require a helper for null checks.
2113 return js.call('dart.#(#, #)',
2114 [memberName, _visit(target), _visit(node.argumentList)]);
2115 } else {
2116 code = '#.#(#)';
2117 }
2118
2119 return js
2120 .call(code, [_visit(target), memberName, _visit(node.argumentList)]);
2121 }
2122
2123 /// Emits code for the `JS(...)` builtin.
2124 _emitForeignJS(MethodInvocation node) {
2125 var e = node.methodName.staticElement;
2126 if (isInlineJS(e)) {
2127 var args = node.argumentList.arguments;
2128 // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer`
2129 var code = args[1];
2130 var templateArgs;
2131 var source;
2132 if (code is StringInterpolation) {
2133 if (args.length > 2) {
2134 throw new ArgumentError(
2135 "Can't mix template args and string interpolation in JS calls.");
2136 }
2137 templateArgs = <Expression>[];
2138 source = code.elements.map((element) {
2139 if (element is InterpolationExpression) {
2140 templateArgs.add(element.expression);
2141 return '#';
2142 } else {
2143 return (element as InterpolationString).value;
2144 }
2145 }).join();
2146 } else {
2147 templateArgs = args.skip(2);
2148 source = (code as StringLiteral).stringValue;
2149 }
2150
2151 var template = js.parseForeignJS(source);
2152 var result = template.instantiate(_visitList(templateArgs));
2153 // `throw` is emitted as a statement by `parseForeignJS`.
2154 assert(result is JS.Expression || node.parent is ExpressionStatement);
2155 return result;
2156 }
2157 return null;
2158 }
2159
2160 @override
2161 JS.Expression visitFunctionExpressionInvocation(
2162 FunctionExpressionInvocation node) {
2163 var code;
2164 if (DynamicInvoke.get(node.function)) {
2165 code = 'dart.$DCALL(#, #)';
2166 } else {
2167 code = '#(#)';
2168 }
2169 return js.call(code, [_visit(node.function), _visit(node.argumentList)]);
2170 }
2171
2172 @override
2173 List<JS.Expression> visitArgumentList(ArgumentList node) {
2174 var args = <JS.Expression>[];
2175 var named = <JS.Property>[];
2176 for (var arg in node.arguments) {
2177 if (arg is NamedExpression) {
2178 named.add(_visit(arg));
2179 } else if (arg is MethodInvocation && isJsSpreadInvocation(arg)) {
2180 args.add(
2181 new JS.RestParameter(_visit(arg.argumentList.arguments.single)));
2182 } else {
2183 args.add(_visit(arg));
2184 }
2185 }
2186 if (named.isNotEmpty) {
2187 args.add(new JS.ObjectInitializer(named));
2188 }
2189 return args;
2190 }
2191
2192 @override
2193 JS.Property visitNamedExpression(NamedExpression node) {
2194 assert(node.parent is ArgumentList);
2195 return new JS.Property(
2196 _propertyName(node.name.label.name), _visit(node.expression));
2197 }
2198
2199 @override
2200 List<JS.Parameter> visitFormalParameterList(FormalParameterList node,
2201 {bool destructure: true}) {
2202 if (node == null) return [];
2203
2204 destructure = destructure && options.destructureNamedParams;
2205
2206 var result = <JS.Parameter>[];
2207 var namedVars = <JS.DestructuredVariable>[];
2208 var hasNamedArgsConflictingWithObjectProperties = false;
2209 var needsOpts = false;
2210
2211 for (FormalParameter param in node.parameters) {
2212 if (param.kind == ParameterKind.NAMED) {
2213 if (destructure) {
2214 if (_jsObjectProperties.contains(param.identifier.name)) {
2215 hasNamedArgsConflictingWithObjectProperties = true;
2216 }
2217 JS.Expression name;
2218 JS.SimpleBindingPattern structure = null;
2219 String paramName = param.identifier.name;
2220 if (invalidVariableName(paramName)) {
2221 name = js.string(paramName);
2222 structure = new JS.SimpleBindingPattern(_visit(param.identifier));
2223 } else {
2224 name = _visit(param.identifier);
2225 }
2226 namedVars.add(new JS.DestructuredVariable(
2227 name: name,
2228 structure: structure,
2229 defaultValue: _defaultParamValue(param)));
2230 } else {
2231 needsOpts = true;
2232 }
2233 } else {
2234 var jsParam = _visit(param);
2235 result.add(param is DefaultFormalParameter && destructure
2236 ? new JS.DestructuredVariable(
2237 name: jsParam, defaultValue: _defaultParamValue(param))
2238 : jsParam);
2239 }
2240 }
2241
2242 if (needsOpts) {
2243 result.add(namedArgumentTemp);
2244 } else if (namedVars.isNotEmpty) {
2245 // Note: `var {valueOf} = {}` extracts `Object.prototype.valueOf`, so
2246 // in case there are conflicting names we create an object without
2247 // any prototype.
2248 var defaultOpts = hasNamedArgsConflictingWithObjectProperties
2249 ? js.call('Object.create(null)')
2250 : js.call('{}');
2251 result.add(new JS.DestructuredVariable(
2252 structure: new JS.ObjectBindingPattern(namedVars),
2253 type: emitNamedParamsArgType(node.parameterElements),
2254 defaultValue: defaultOpts));
2255 }
2256 return result;
2257 }
2258
2259 /// See ES6 spec (and `Object.getOwnPropertyNames(Object.prototype)`):
2260 /// http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-obje ct-prototype-object
2261 /// http://www.ecma-international.org/ecma-262/6.0/#sec-additional-properties- of-the-object.prototype-object
2262 static final Set<String> _jsObjectProperties = new Set<String>()
2263 ..addAll([
2264 "constructor",
2265 "toString",
2266 "toLocaleString",
2267 "valueOf",
2268 "hasOwnProperty",
2269 "isPrototypeOf",
2270 "propertyIsEnumerable",
2271 "__defineGetter__",
2272 "__lookupGetter__",
2273 "__defineSetter__",
2274 "__lookupSetter__",
2275 "__proto__"
2276 ]);
2277
2278 @override
2279 JS.Statement visitExpressionStatement(ExpressionStatement node) =>
2280 _visit(node.expression).toStatement();
2281
2282 @override
2283 JS.EmptyStatement visitEmptyStatement(EmptyStatement node) =>
2284 new JS.EmptyStatement();
2285
2286 @override
2287 JS.Statement visitAssertStatement(AssertStatement node) =>
2288 // TODO(jmesserly): only emit in checked mode.
2289 js.statement('dart.assert(#);', _visit(node.condition));
2290
2291 @override
2292 JS.Statement visitReturnStatement(ReturnStatement node) {
2293 var e = node.expression;
2294 if (e == null) return new JS.Return();
2295 return (_visit(e) as JS.Expression).toReturn();
2296 }
2297
2298 @override
2299 JS.Statement visitYieldStatement(YieldStatement node) {
2300 JS.Expression jsExpr = _visit(node.expression);
2301 var star = node.star != null;
2302 if (_asyncStarController != null) {
2303 // async* yields are generated differently from sync* yields. `yield e`
2304 // becomes:
2305 //
2306 // if (stream.add(e)) return;
2307 // yield;
2308 //
2309 // `yield* e` becomes:
2310 //
2311 // if (stream.addStream(e)) return;
2312 // yield;
2313 var helperName = star ? 'addStream' : 'add';
2314 return js.statement('{ if(#.#(#)) return; #; }',
2315 [_asyncStarController, helperName, jsExpr, new JS.Yield(null)]);
2316 }
2317 // A normal yield in a sync*
2318 return jsExpr.toYieldStatement(star: star);
2319 }
2320
2321 @override
2322 JS.Expression visitAwaitExpression(AwaitExpression node) {
2323 return new JS.Yield(_visit(node.expression));
2324 }
2325
2326 @override
2327 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
2328 for (var v in node.variables.variables) {
2329 _loader.loadDeclaration(v, v.element);
2330 }
2331 }
2332
2333 /// Emits static fields.
2334 ///
2335 /// Instance fields are emitted in [_initializeFields].
2336 ///
2337 /// These are generally treated the same as top-level fields, see
2338 /// [visitTopLevelVariableDeclaration].
2339 @override
2340 visitFieldDeclaration(FieldDeclaration node) {
2341 if (!node.isStatic) return;
2342
2343 node.fields.variables.forEach(_emitModuleItem);
2344 }
2345
2346 _addExport(String name, [String exportName]) {
2347 if (_exports.containsKey(name)) {
2348 throw 'Duplicate top level name found: $name';
2349 }
2350 _exports[name] = exportName ?? name;
2351 }
2352
2353 @override
2354 JS.Statement visitVariableDeclarationStatement(
2355 VariableDeclarationStatement node) {
2356 // Special case a single variable with an initializer.
2357 // This helps emit cleaner code for things like:
2358 // var result = []..add(1)..add(2);
2359 if (node.variables.variables.length == 1) {
2360 var v = node.variables.variables.single;
2361 if (v.initializer != null) {
2362 var name = new JS.Identifier(v.name.name);
2363 return _visit(v.initializer).toVariableDeclaration(name);
2364 }
2365 }
2366 return _visit(node.variables).toStatement();
2367 }
2368
2369 @override
2370 visitVariableDeclarationList(VariableDeclarationList node) {
2371 return new JS.VariableDeclarationList(
2372 'let', _visitList(node.variables) as List<JS.VariableInitialization>);
2373 }
2374
2375 @override
2376 visitVariableDeclaration(VariableDeclaration node) {
2377 if (node.element is PropertyInducingElement) {
2378 // Static and instance fields are handled elsewhere.
2379 assert(node.element is TopLevelVariableElement);
2380 return _emitTopLevelField(node);
2381 }
2382
2383 var name =
2384 new JS.Identifier(node.name.name, type: emitTypeRef(node.element.type));
2385 return new JS.VariableInitialization(name, _visitInitializer(node));
2386 }
2387
2388 bool _isFinalJSDecl(AstNode field) =>
2389 field is VariableDeclaration &&
2390 field.isFinal &&
2391 _isJSInvocation(field.initializer);
2392
2393 /// Try to emit a constant static field.
2394 ///
2395 /// If the field's initializer does not cause side effects, and if all of
2396 /// dependencies are safe to refer to while we are initializing the class,
2397 /// then we can initialize it eagerly:
2398 ///
2399 /// // Baz must be const constructor, and the name "Baz" must be defined
2400 /// // by this point.
2401 /// Foo.bar = dart.const(new Baz(42));
2402 ///
2403 /// Otherwise, we'll need to generate a lazy-static field. That ensures
2404 /// correct visible behavior, as well as avoiding referencing something that
2405 /// isn't defined yet (because it is defined later in the module).
2406 JS.Statement _emitConstantStaticField(
2407 ClassElement classElem, VariableDeclaration field) {
2408 PropertyInducingElement element = field.element;
2409 assert(element.isStatic);
2410
2411 _loader.startCheckingReferences();
2412 JS.Expression jsInit = _visitInitializer(field);
2413 bool isLoaded = _loader.finishCheckingReferences();
2414
2415 bool eagerInit =
2416 isLoaded && (field.isConst || _constField.isFieldInitConstant(field));
2417
2418 var fieldName = field.name.name;
2419 if (eagerInit && !JS.invalidStaticFieldName(fieldName)) {
2420 return annotate(
2421 js.statement('#.# = #;', [
2422 classElem.name,
2423 _emitMemberName(fieldName, isStatic: true),
2424 jsInit
2425 ]),
2426 field,
2427 field.element);
2428 }
2429
2430 // This means it should be treated as a lazy field.
2431 // TODO(jmesserly): we're throwing away the initializer expression,
2432 // which will force us to regenerate it.
2433 return null;
2434 }
2435
2436 /// Emits a top-level field.
2437 JS.Statement _emitTopLevelField(VariableDeclaration field) {
2438 TopLevelVariableElement element = field.element;
2439 assert(element.isStatic);
2440
2441 bool eagerInit;
2442 JS.Expression jsInit;
2443 if (field.isConst || _constField.isFieldInitConstant(field)) {
2444 // If the field is constant, try and generate it at the top level.
2445 _loader.startTopLevel(element);
2446 jsInit = _visitInitializer(field);
2447 _loader.finishTopLevel(element);
2448 eagerInit = _loader.isLoaded(element);
2449 } else {
2450 // TODO(jmesserly): we're visiting the initializer here, and again
2451 // later on when we emit lazy fields. That seems busted.
2452 jsInit = _visitInitializer(field);
2453 eagerInit = false;
2454 }
2455
2456 // Treat `final x = JS('', '...')` as a const (non-lazy) to help compile
2457 // runtime helpers.
2458 var isJSTopLevel = field.isFinal && _isFinalJSDecl(field);
2459 if (isJSTopLevel) eagerInit = true;
2460
2461 var fieldName = field.name.name;
2462 var exportName = fieldName;
2463 if (element is TopLevelVariableElement) {
2464 exportName = getJSExportName(element, types) ?? fieldName;
2465 }
2466 if ((field.isConst && eagerInit && element is TopLevelVariableElement) ||
2467 isJSTopLevel) {
2468 // constant fields don't change, so we can generate them as `let`
2469 // but add them to the module's exports. However, make sure we generate
2470 // anything they depend on first.
2471
2472 if (isPublic(fieldName)) _addExport(fieldName, exportName);
2473 var declKeyword = field.isConst || field.isFinal ? 'const' : 'let';
2474 if (isJSTopLevel && jsInit is JS.ClassExpression) {
2475 return new JS.ClassDeclaration(jsInit);
2476 }
2477 return js.statement('#;', [
2478 annotate(
2479 new JS.VariableDeclarationList(declKeyword, [
2480 new JS.VariableInitialization(
2481 new JS.Identifier(fieldName,
2482 type: emitTypeRef(field.element.type)),
2483 jsInit)
2484 ]),
2485 field,
2486 field.element)
2487 ]);
2488 }
2489
2490 if (eagerInit && !JS.invalidStaticFieldName(fieldName)) {
2491 return annotate(js.statement('# = #;', [_visit(field.name), jsInit]),
2492 field, field.element);
2493 }
2494
2495 return _emitLazyFields(currentLibrary, [field]);
2496 }
2497
2498 JS.Expression _visitInitializer(VariableDeclaration node) {
2499 var value = _visit(node.initializer);
2500 // explicitly initialize to null, to avoid getting `undefined`.
2501 // TODO(jmesserly): do this only for vars that aren't definitely assigned.
2502 return value ?? new JS.LiteralNull();
2503 }
2504
2505 JS.Statement _emitLazyFields(
2506 Element target, List<VariableDeclaration> fields) {
2507 var methods = [];
2508 for (var node in fields) {
2509 var name = node.name.name;
2510 var element = node.element;
2511 var access = _emitMemberName(name, isStatic: true);
2512 methods.add(annotate(
2513 new JS.Method(
2514 access,
2515 js.call('function() { return #; }', _visit(node.initializer))
2516 as JS.Fun,
2517 isGetter: true),
2518 node,
2519 _findAccessor(element, getter: true)));
2520
2521 // TODO(jmesserly): currently uses a dummy setter to indicate writable.
2522 if (!node.isFinal && !node.isConst) {
2523 methods.add(annotate(
2524 new JS.Method(access, js.call('function(_) {}') as JS.Fun,
2525 isSetter: true),
2526 node,
2527 _findAccessor(element, getter: false)));
2528 }
2529 }
2530
2531 JS.Expression objExpr;
2532 if (target is ClassElement) {
2533 objExpr = new JS.Identifier(target.type.name);
2534 } else {
2535 objExpr = emitLibraryName(target);
2536 }
2537
2538 return js
2539 .statement('dart.defineLazyProperties(#, { # });', [objExpr, methods]);
2540 }
2541
2542 PropertyAccessorElement _findAccessor(VariableElement element,
2543 {bool getter}) {
2544 var parent = element.enclosingElement;
2545 if (parent is ClassElement) {
2546 return getter
2547 ? parent.getGetter(element.name)
2548 : parent.getSetter(element.name);
2549 }
2550 return null;
2551 }
2552
2553 void _flushLibraryProperties(List<JS.Statement> body) {
2554 if (_properties.isEmpty) return;
2555 body.add(js.statement('dart.copyProperties(#, { # });',
2556 [_exportsVar, _properties.map(_emitTopLevelProperty)]));
2557 _properties.clear();
2558 }
2559
2560 JS.Expression _emitConstructorName(
2561 ConstructorElement element, DartType type, SimpleIdentifier name) {
2562 var typeName = _emitTypeName(type);
2563 if (name != null || element.isFactory) {
2564 var namedCtor = _constructorName(element);
2565 return new JS.PropertyAccess(typeName, namedCtor);
2566 }
2567 return typeName;
2568 }
2569
2570 @override
2571 visitConstructorName(ConstructorName node) {
2572 return _emitConstructorName(node.staticElement, node.type.type, node.name);
2573 }
2574
2575 JS.Expression _emitInstanceCreationExpression(
2576 ConstructorElement element,
2577 DartType type,
2578 SimpleIdentifier name,
2579 ArgumentList argumentList,
2580 bool isConst) {
2581 JS.Expression emitNew() {
2582 JS.Expression ctor;
2583 bool isFactory = false;
2584 // var element = node.staticElement;
2585 if (element == null) {
2586 // TODO(jmesserly): this only happens if we had a static error.
2587 // Should we generate a throw instead?
2588 ctor = _emitTypeName(type);
2589 if (name != null) {
2590 ctor = new JS.PropertyAccess(ctor, _propertyName(name.name));
2591 }
2592 } else {
2593 ctor = _emitConstructorName(element, type, name);
2594 isFactory = element.isFactory;
2595 }
2596 var args = _visit(argumentList) as List<JS.Expression>;
2597 return isFactory ? new JS.Call(ctor, args) : new JS.New(ctor, args);
2598 }
2599 if (isConst) return _emitConst(emitNew);
2600 return emitNew();
2601 }
2602
2603 @override
2604 visitInstanceCreationExpression(InstanceCreationExpression node) {
2605 var element = node.staticElement;
2606 var constructor = node.constructorName;
2607 var name = constructor.name;
2608 var type = constructor.type.type;
2609 return _emitInstanceCreationExpression(
2610 element, type, name, node.argumentList, node.isConst);
2611 }
2612
2613 /// True if this type is built-in to JS, and we use the values unwrapped.
2614 /// For these types we generate a calling convention via static
2615 /// "extension methods". This allows types to be extended without adding
2616 /// extensions directly on the prototype.
2617 bool isPrimitiveType(DartType t) =>
2618 typeIsPrimitiveInJS(t) || t == _types.stringType;
2619
2620 bool typeIsPrimitiveInJS(DartType t) =>
2621 _isNumberInJS(t) || t == _types.boolType;
2622
2623 bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) =>
2624 typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT);
2625
2626 bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t);
2627
2628 JS.Expression notNull(Expression expr) {
2629 if (expr == null) return null;
2630 var jsExpr = _visit(expr);
2631 if (!isNullable(expr)) return jsExpr;
2632 return js.call('dart.notNull(#)', jsExpr);
2633 }
2634
2635 @override
2636 JS.Expression visitBinaryExpression(BinaryExpression node) {
2637 var op = node.operator;
2638 var left = node.leftOperand;
2639 var right = node.rightOperand;
2640
2641 var leftType = getStaticType(left);
2642 var rightType = getStaticType(right);
2643
2644 var code;
2645 if (op.type.isEqualityOperator) {
2646 // If we statically know LHS or RHS is null we can generate a clean check.
2647 // We can also do this if both sides are the same primitive type.
2648 if (_canUsePrimitiveEquality(left, right)) {
2649 code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #';
2650 } else if (left is SuperExpression) {
2651 return _emitSend(left, op.lexeme, [right]);
2652 } else {
2653 var bang = op.type == TokenType.BANG_EQ ? '!' : '';
2654 code = '${bang}dart.equals(#, #)';
2655 }
2656 return js.call(code, [_visit(left), _visit(right)]);
2657 }
2658
2659 if (op.type.lexeme == '??') {
2660 // TODO(jmesserly): leave RHS for debugging?
2661 // This should be a hint or warning for dead code.
2662 if (!isNullable(left)) return _visit(left);
2663
2664 var vars = <String, JS.Expression>{};
2665 // Desugar `l ?? r` as `l != null ? l : r`
2666 var l = _visit(_bindValue(vars, 'l', left, context: left));
2667 return new JS.MetaLet(vars, [
2668 js.call('# != null ? # : #', [l, l, _visit(right)])
2669 ]);
2670 }
2671
2672 if (binaryOperationIsPrimitive(leftType, rightType) ||
2673 leftType == _types.stringType && op.type == TokenType.PLUS) {
2674 // special cases where we inline the operation
2675 // these values are assumed to be non-null (determined by the checker)
2676 // TODO(jmesserly): it would be nice to just inline the method from core,
2677 // instead of special cases here.
2678 if (op.type == TokenType.TILDE_SLASH) {
2679 // `a ~/ b` is equivalent to `(a / b).truncate()`
2680 var div = AstBuilder.binaryExpression(left, '/', right)
2681 ..staticType = node.staticType;
2682 return _emitSend(div, 'truncate', []);
2683 } else {
2684 // TODO(vsm): When do Dart ops not map to JS?
2685 code = '# $op #';
2686 }
2687 return js.call(code, [notNull(left), notNull(right)]);
2688 }
2689
2690 return _emitSend(left, op.lexeme, [right]);
2691 }
2692
2693 /// If the type [t] is [int] or [double], or a type parameter
2694 /// bounded by [int], [double] or [num] returns [num].
2695 /// Otherwise returns [t].
2696 DartType _canonicalizeNumTypes(DartType t) {
2697 var numType = types.numType;
2698 if (rules.isSubtypeOf(t, numType)) return numType;
2699 return t;
2700 }
2701
2702 bool _canUsePrimitiveEquality(Expression left, Expression right) {
2703 if (_isNull(left) || _isNull(right)) return true;
2704
2705 var leftType = _canonicalizeNumTypes(getStaticType(left));
2706 var rightType = _canonicalizeNumTypes(getStaticType(right));
2707 return isPrimitiveType(leftType) && leftType == rightType;
2708 }
2709
2710 bool _isNull(Expression expr) => expr is NullLiteral;
2711
2712 SimpleIdentifier _createTemporary(String name, DartType type,
2713 {bool nullable: true}) {
2714 // We use an invalid source location to signal that this is a temporary.
2715 // See [_isTemporary].
2716 // TODO(jmesserly): alternatives are
2717 // * (ab)use Element.isSynthetic, which isn't currently used for
2718 // LocalVariableElementImpl, so we could repurpose to mean "temp".
2719 // * add a new property to LocalVariableElementImpl.
2720 // * create a new subtype of LocalVariableElementImpl to mark a temp.
2721 var id =
2722 new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1));
2723 id.staticElement = new TemporaryVariableElement.forNode(id);
2724 id.staticType = type;
2725 DynamicInvoke.set(id, type.isDynamic);
2726 addTemporaryVariable(id.staticElement, nullable: nullable);
2727 return id;
2728 }
2729
2730 JS.Expression _emitConst(JS.Expression expr()) {
2731 // TODO(jmesserly): emit the constants at top level if possible.
2732 // This wasn't quite working, so disabled for now.
2733 return js.call('dart.const(#)', expr());
2734 }
2735
2736 /// Returns a new expression, which can be be used safely *once* on the
2737 /// left hand side, and *once* on the right side of an assignment.
2738 /// For example: `expr1[expr2] += y` can be compiled as
2739 /// `expr1[expr2] = expr1[expr2] + y`.
2740 ///
2741 /// The temporary scope will ensure `expr1` and `expr2` are only evaluated
2742 /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`.
2743 ///
2744 /// If the expression does not end up using `x1` or `x2` more than once, or
2745 /// if those expressions can be treated as stateless (e.g. they are
2746 /// non-mutated variables), then the resulting code will be simplified
2747 /// automatically.
2748 ///
2749 /// [scope] can be mutated to contain any new temporaries that were created,
2750 /// unless [expr] is a SimpleIdentifier, in which case a temporary is not
2751 /// needed.
2752 Expression _bindLeftHandSide(
2753 Map<String, JS.Expression> scope, Expression expr,
2754 {Expression context}) {
2755 Expression result;
2756 if (expr is IndexExpression) {
2757 IndexExpression index = expr;
2758 result = new IndexExpression.forTarget(
2759 _bindValue(scope, 'o', index.target, context: context),
2760 index.leftBracket,
2761 _bindValue(scope, 'i', index.index, context: context),
2762 index.rightBracket);
2763 } else if (expr is PropertyAccess) {
2764 PropertyAccess prop = expr;
2765 result = new PropertyAccess(
2766 _bindValue(scope, 'o', _getTarget(prop), context: context),
2767 prop.operator,
2768 prop.propertyName);
2769 } else if (expr is PrefixedIdentifier) {
2770 PrefixedIdentifier ident = expr;
2771 if (isLibraryPrefix(ident.prefix)) {
2772 return expr;
2773 }
2774 result = new PrefixedIdentifier(
2775 _bindValue(scope, 'o', ident.prefix, context: context)
2776 as SimpleIdentifier,
2777 ident.period,
2778 ident.identifier);
2779 } else {
2780 return expr as SimpleIdentifier;
2781 }
2782 result.staticType = expr.staticType;
2783 DynamicInvoke.set(result, DynamicInvoke.get(expr));
2784 return result;
2785 }
2786
2787 /// Creates a temporary to contain the value of [expr]. The temporary can be
2788 /// used multiple times in the resulting expression. For example:
2789 /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will
2790 /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`.
2791 ///
2792 /// If the expression does not end up using `x` more than once, or if those
2793 /// expressions can be treated as stateless (e.g. they are non-mutated
2794 /// variables), then the resulting code will be simplified automatically.
2795 ///
2796 /// [scope] will be mutated to contain the new temporary's initialization.
2797 Expression _bindValue(
2798 Map<String, JS.Expression> scope, String name, Expression expr,
2799 {Expression context}) {
2800 // No need to do anything for stateless expressions.
2801 if (isStateless(_currentFunction, expr, context)) return expr;
2802
2803 var t = _createTemporary('#$name', getStaticType(expr));
2804 scope[name] = _visit(expr);
2805 return t;
2806 }
2807
2808 /// Desugars postfix increment.
2809 ///
2810 /// In the general case [expr] can be one of [IndexExpression],
2811 /// [PrefixExpression] or [PropertyAccess] and we need to
2812 /// ensure sub-expressions are evaluated once.
2813 ///
2814 /// We also need to ensure we can return the original value of the expression,
2815 /// and that it is only evaluated once.
2816 ///
2817 /// We desugar this using let*.
2818 ///
2819 /// For example, `expr1[expr2]++` can be transformed to this:
2820 ///
2821 /// // psuedocode mix of Scheme and JS:
2822 /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
2823 ///
2824 /// The [JS.MetaLet] nodes automatically simplify themselves if they can.
2825 /// For example, if the result value is not used, then `t` goes away.
2826 @override
2827 JS.Expression visitPostfixExpression(PostfixExpression node) {
2828 var op = node.operator;
2829 var expr = node.operand;
2830
2831 var dispatchType = getStaticType(expr);
2832 if (unaryOperationIsPrimitive(dispatchType)) {
2833 if (!isNullable(expr)) {
2834 return js.call('#$op', _visit(expr));
2835 }
2836 }
2837
2838 assert(op.lexeme == '++' || op.lexeme == '--');
2839
2840 // Handle the left hand side, to ensure each of its subexpressions are
2841 // evaluated only once.
2842 var vars = <String, JS.Expression>{};
2843 var left = _bindLeftHandSide(vars, expr, context: expr);
2844
2845 // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
2846 // and `x1` is the new value for `x`.
2847 var x = _bindValue(vars, 'x', left, context: expr);
2848
2849 var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
2850 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
2851 ..staticElement = node.staticElement
2852 ..staticType = getStaticType(expr);
2853
2854 var body = <JS.Expression>[_emitSet(left, increment), _visit(x)];
2855 return new JS.MetaLet(vars, body, statelessResult: true);
2856 }
2857
2858 @override
2859 JS.Expression visitPrefixExpression(PrefixExpression node) {
2860 var op = node.operator;
2861 var expr = node.operand;
2862
2863 var dispatchType = getStaticType(expr);
2864 if (unaryOperationIsPrimitive(dispatchType)) {
2865 if (!isNullable(expr)) {
2866 return js.call('$op#', _visit(expr));
2867 } else if (op.lexeme == '++' || op.lexeme == '--') {
2868 // We need a null check, so the increment must be expanded out.
2869 var vars = <String, JS.Expression>{};
2870 var x = _bindLeftHandSide(vars, expr, context: expr);
2871
2872 var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
2873 var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
2874 ..staticElement = node.staticElement
2875 ..staticType = getStaticType(expr);
2876
2877 return new JS.MetaLet(vars, [_emitSet(x, increment)]);
2878 } else {
2879 return js.call('$op#', notNull(expr));
2880 }
2881 }
2882
2883 if (op.lexeme == '++' || op.lexeme == '--') {
2884 // Increment or decrement requires expansion.
2885 // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions
2886 // (for example, x is IndexExpression) we evaluate those once.
2887 var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
2888 return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement,
2889 context: expr);
2890 }
2891
2892 return _emitSend(expr, op.lexeme[0], []);
2893 }
2894
2895 // Cascades can contain [IndexExpression], [MethodInvocation] and
2896 // [PropertyAccess]. The code generation for those is handled in their
2897 // respective visit methods.
2898 @override
2899 JS.Node visitCascadeExpression(CascadeExpression node) {
2900 var savedCascadeTemp = _cascadeTarget;
2901
2902 var vars = <String, JS.Expression>{};
2903 _cascadeTarget = _bindValue(vars, '_', node.target, context: node);
2904 var sections = _visitList(node.cascadeSections) as List<JS.Expression>;
2905 sections.add(_visit(_cascadeTarget));
2906 var result = new JS.MetaLet(vars, sections, statelessResult: true);
2907 _cascadeTarget = savedCascadeTemp;
2908 return result;
2909 }
2910
2911 @override
2912 visitParenthesizedExpression(ParenthesizedExpression node) =>
2913 // The printer handles precedence so we don't need to.
2914 _visit(node.expression);
2915
2916 @override
2917 visitFormalParameter(FormalParameter node) {
2918 var id = _emitParameter(node.element, declaration: true);
2919 var isRestArg = findAnnotation(node.element, isJsRestAnnotation) != null;
2920 return isRestArg ? new JS.RestParameter(id) : id;
2921 }
2922
2923 @override
2924 JS.This visitThisExpression(ThisExpression node) => new JS.This();
2925
2926 @override
2927 JS.Super visitSuperExpression(SuperExpression node) => new JS.Super();
2928
2929 @override
2930 visitPrefixedIdentifier(PrefixedIdentifier node) {
2931 if (isLibraryPrefix(node.prefix)) {
2932 return _visit(node.identifier);
2933 } else {
2934 return _emitGet(node.prefix, node.identifier);
2935 }
2936 }
2937
2938 @override
2939 visitPropertyAccess(PropertyAccess node) {
2940 if (node.operator.lexeme == '?.') {
2941 return _emitNullSafe(node);
2942 }
2943 return _emitGet(_getTarget(node), node.propertyName);
2944 }
2945
2946 JS.Expression _emitNullSafe(Expression node) {
2947 // Desugar ?. sequence by passing a sequence of callbacks that applies
2948 // each operation in sequence:
2949 //
2950 // obj?.foo()?.bar
2951 // -->
2952 // nullSafe(obj, _ => _.foo(), _ => _.bar);
2953 //
2954 // This pattern has the benefit of preserving order, as well as minimizing
2955 // code expansion: each `?.` becomes `, _ => _`, plus one helper call.
2956 //
2957 // TODO(jmesserly): we could desugar with MetaLet instead, which may
2958 // lead to higher performing code, but at the cost of readability.
2959 var tail = <JS.Expression>[];
2960 for (;;) {
2961 var op = _getOperator(node);
2962 if (op != null && op.lexeme == '?.') {
2963 var nodeTarget = _getTarget(node);
2964 if (!isNullable(nodeTarget)) {
2965 node = _stripNullAwareOp(node, nodeTarget);
2966 break;
2967 }
2968
2969 var param =
2970 _createTemporary('_', nodeTarget.staticType, nullable: false);
2971 var baseNode = _stripNullAwareOp(node, param);
2972 tail.add(new JS.ArrowFun([_visit(param)], _visit(baseNode)));
2973 node = nodeTarget;
2974 } else {
2975 break;
2976 }
2977 }
2978 if (tail.isEmpty) return _visit(node);
2979 return js.call('dart.nullSafe(#, #)', [_visit(node), tail.reversed]);
2980 }
2981
2982 static Token _getOperator(Expression node) {
2983 if (node is PropertyAccess) return node.operator;
2984 if (node is MethodInvocation) return node.operator;
2985 return null;
2986 }
2987
2988 // TODO(jmesserly): this is dropping source location.
2989 Expression _stripNullAwareOp(Expression node, Expression newTarget) {
2990 if (node is PropertyAccess) {
2991 return AstBuilder.propertyAccess(newTarget, node.propertyName);
2992 } else {
2993 var invoke = node as MethodInvocation;
2994 return AstBuilder.methodInvoke(
2995 newTarget, invoke.methodName, invoke.argumentList.arguments);
2996 }
2997 }
2998
2999 bool _requiresStaticDispatch(Expression target, String memberName) {
3000 var type = getStaticType(target);
3001 if (!isObjectProperty(memberName)) {
3002 return false;
3003 }
3004
3005 // If the target could be `null`, we need static dispatch.
3006 // If the target may be an extension type, we also use static dispatch
3007 // as we don't symbolize object properties like hashCode.
3008 return isNullable(target) ||
3009 (_extensionTypes.contains(type.element) && target is! SuperExpression);
3010 }
3011
3012 /// Shared code for [PrefixedIdentifier] and [PropertyAccess].
3013 JS.Expression _emitGet(Expression target, SimpleIdentifier memberId) {
3014 var member = memberId.staticElement;
3015 if (member is PropertyAccessorElement) {
3016 member = (member as PropertyAccessorElement).variable;
3017 }
3018 bool isStatic = member is ClassMemberElement && member.isStatic;
3019 var name = _emitMemberName(memberId.name,
3020 type: getStaticType(target), isStatic: isStatic);
3021 if (DynamicInvoke.get(target)) {
3022 return js.call('dart.$DLOAD(#, #)', [_visit(target), name]);
3023 }
3024
3025 String code;
3026 if (member != null && member is MethodElement && !isStatic) {
3027 // Tear-off methods: explicitly bind it.
3028 if (target is SuperExpression) {
3029 return js.call('dart.bind(this, #, #.#)', [name, _visit(target), name]);
3030 } else if (_requiresStaticDispatch(target, memberId.name)) {
3031 var type = member.type;
3032 var clos = js.call('dart.#.bind(#)', [name, _visit(target)]);
3033 return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]);
3034 }
3035 code = 'dart.bind(#, #)';
3036 } else if (_requiresStaticDispatch(target, memberId.name)) {
3037 return js.call('dart.#(#)', [name, _visit(target)]);
3038 } else {
3039 code = '#.#';
3040 }
3041
3042 return js.call(code, [_visit(target), name]);
3043 }
3044
3045 /// Emits a generic send, like an operator method.
3046 ///
3047 /// **Please note** this function does not support method invocation syntax
3048 /// `obj.name(args)` because that could be a getter followed by a call.
3049 /// See [visitMethodInvocation].
3050 JS.Expression _emitSend(
3051 Expression target, String name, List<Expression> args) {
3052 var type = getStaticType(target);
3053 var memberName = _emitMemberName(name, unary: args.isEmpty, type: type);
3054 if (DynamicInvoke.get(target)) {
3055 // dynamic dispatch
3056 var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name];
3057 if (dynamicHelper != null) {
3058 return js.call(
3059 'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]);
3060 }
3061 return js.call('dart.$DSEND(#, #, #)',
3062 [_visit(target), memberName, _visitList(args)]);
3063 }
3064
3065 // Generic dispatch to a statically known method.
3066 return js.call('#.#(#)', [_visit(target), memberName, _visitList(args)]);
3067 }
3068
3069 @override
3070 visitIndexExpression(IndexExpression node) {
3071 var target = _getTarget(node);
3072 if (_useNativeJsIndexer(target.staticType)) {
3073 return new JS.PropertyAccess(_visit(target), _visit(node.index));
3074 }
3075 return _emitSend(target, '[]', [node.index]);
3076 }
3077
3078 // TODO(jmesserly): ideally we'd check the method and see if it is marked
3079 // `external`, but that doesn't work because it isn't in the element model.
3080 bool _useNativeJsIndexer(DartType type) =>
3081 findAnnotation(type.element, isJSAnnotation) != null;
3082
3083 /// Gets the target of a [PropertyAccess], [IndexExpression], or
3084 /// [MethodInvocation]. These three nodes can appear in a [CascadeExpression].
3085 Expression _getTarget(node) {
3086 assert(node is IndexExpression ||
3087 node is PropertyAccess ||
3088 node is MethodInvocation);
3089 return node.isCascaded ? _cascadeTarget : node.target;
3090 }
3091
3092 @override
3093 visitConditionalExpression(ConditionalExpression node) {
3094 return js.call('# ? # : #', [
3095 notNull(node.condition),
3096 _visit(node.thenExpression),
3097 _visit(node.elseExpression)
3098 ]);
3099 }
3100
3101 @override
3102 visitThrowExpression(ThrowExpression node) {
3103 var expr = _visit(node.expression);
3104 if (node.parent is ExpressionStatement) {
3105 return js.statement('dart.throw(#);', expr);
3106 } else {
3107 return js.call('dart.throw(#)', expr);
3108 }
3109 }
3110
3111 @override
3112 visitRethrowExpression(RethrowExpression node) {
3113 if (node.parent is ExpressionStatement) {
3114 return js.statement('throw #;', _visit(_catchParameter));
3115 } else {
3116 return js.call('throw #', _visit(_catchParameter));
3117 }
3118 }
3119
3120 /// Visits a statement, and ensures the resulting AST handles block scope
3121 /// correctly. Essentially, we need to promote a variable declaration
3122 /// statement into a block in some cases, e.g.
3123 ///
3124 /// do var x = 5; while (false); // Dart
3125 /// do { let x = 5; } while (false); // JS
3126 JS.Statement _visitScope(Statement stmt) {
3127 var result = _visit(stmt);
3128 if (result is JS.ExpressionStatement &&
3129 result.expression is JS.VariableDeclarationList) {
3130 return new JS.Block([result]);
3131 }
3132 return result;
3133 }
3134
3135 @override
3136 JS.If visitIfStatement(IfStatement node) {
3137 return new JS.If(notNull(node.condition), _visitScope(node.thenStatement),
3138 _visitScope(node.elseStatement));
3139 }
3140
3141 @override
3142 JS.For visitForStatement(ForStatement node) {
3143 var init = _visit(node.initialization);
3144 if (init == null) init = _visit(node.variables);
3145 var update = _visitListToBinary(node.updaters, ',');
3146 if (update != null) update = update.toVoidExpression();
3147 return new JS.For(
3148 init, notNull(node.condition), update, _visitScope(node.body));
3149 }
3150
3151 @override
3152 JS.While visitWhileStatement(WhileStatement node) {
3153 return new JS.While(notNull(node.condition), _visitScope(node.body));
3154 }
3155
3156 @override
3157 JS.Do visitDoStatement(DoStatement node) {
3158 return new JS.Do(_visitScope(node.body), notNull(node.condition));
3159 }
3160
3161 @override
3162 JS.Statement visitForEachStatement(ForEachStatement node) {
3163 if (node.awaitKeyword != null) {
3164 return _emitAwaitFor(node);
3165 }
3166
3167 var init = _visit(node.identifier);
3168 if (init == null) {
3169 init = js.call('let #', node.loopVariable.identifier.name);
3170 }
3171 return new JS.ForOf(init, _visit(node.iterable), _visitScope(node.body));
3172 }
3173
3174 JS.Statement _emitAwaitFor(ForEachStatement node) {
3175 // Emits `await for (var value in stream) ...`, which desugars as:
3176 //
3177 // var iter = new StreamIterator(stream);
3178 // try {
3179 // while (await iter.moveNext()) {
3180 // var value = iter.current;
3181 // ...
3182 // }
3183 // } finally {
3184 // await iter.cancel();
3185 // }
3186 //
3187 // Like the Dart VM, we call cancel() always, as it's safe to call if the
3188 // stream has already been cancelled.
3189 //
3190 // TODO(jmesserly): we may want a helper if these become common. For now the
3191 // full desugaring seems okay.
3192 var context = compiler.context;
3193 var dart_async = context
3194 .computeLibraryElement(context.sourceFactory.forUri('dart:async'));
3195 var _streamIteratorType =
3196 rules.instantiateToBounds(dart_async.getType('StreamIterator').type);
3197
3198 var createStreamIter = _emitInstanceCreationExpression(
3199 _streamIteratorType.element.unnamedConstructor,
3200 _streamIteratorType,
3201 null,
3202 AstBuilder.argumentList([node.iterable]),
3203 false);
3204 var iter =
3205 _visit(_createTemporary('it', _streamIteratorType, nullable: false));
3206
3207 var init = _visit(node.identifier);
3208 if (init == null) {
3209 init = js
3210 .call('let # = #.current', [node.loopVariable.identifier.name, iter]);
3211 } else {
3212 init = js.call('# = #.current', [init, iter]);
3213 }
3214 return js.statement(
3215 '{'
3216 ' let # = #;'
3217 ' try {'
3218 ' while (#) { #; #; }'
3219 ' } finally { #; }'
3220 '}',
3221 [
3222 iter,
3223 createStreamIter,
3224 new JS.Yield(js.call('#.moveNext()', iter)),
3225 init,
3226 _visit(node.body),
3227 new JS.Yield(js.call('#.cancel()', iter))
3228 ]);
3229 }
3230
3231 @override
3232 visitBreakStatement(BreakStatement node) {
3233 var label = node.label;
3234 return new JS.Break(label?.name);
3235 }
3236
3237 @override
3238 visitContinueStatement(ContinueStatement node) {
3239 var label = node.label;
3240 return new JS.Continue(label?.name);
3241 }
3242
3243 @override
3244 visitTryStatement(TryStatement node) {
3245 return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses),
3246 _visit(node.finallyBlock));
3247 }
3248
3249 _visitCatch(NodeList<CatchClause> clauses) {
3250 if (clauses == null || clauses.isEmpty) return null;
3251
3252 // TODO(jmesserly): need a better way to get a temporary variable.
3253 // This could incorrectly shadow a user's name.
3254 var savedCatch = _catchParameter;
3255
3256 if (clauses.length == 1 && clauses.single.exceptionParameter != null) {
3257 // Special case for a single catch.
3258 _catchParameter = clauses.single.exceptionParameter;
3259 } else {
3260 _catchParameter = _createTemporary('e', types.dynamicType);
3261 }
3262
3263 JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter));
3264 for (var clause in clauses.reversed) {
3265 catchBody = _catchClauseGuard(clause, catchBody);
3266 }
3267
3268 var catchVarDecl = _visit(_catchParameter);
3269 _catchParameter = savedCatch;
3270 return new JS.Catch(catchVarDecl, new JS.Block([catchBody]));
3271 }
3272
3273 JS.Statement _catchClauseGuard(CatchClause clause, JS.Statement otherwise) {
3274 var then = visitCatchClause(clause);
3275
3276 // Discard following clauses, if any, as they are unreachable.
3277 if (clause.exceptionType == null) return then;
3278
3279 // TODO(jmesserly): this is inconsistent with [visitIsExpression], which
3280 // has special case for typeof.
3281 return new JS.If(
3282 js.call('dart.is(#, #)', [
3283 _visit(_catchParameter),
3284 _emitTypeName(clause.exceptionType.type),
3285 ]),
3286 then,
3287 otherwise);
3288 }
3289
3290 JS.Statement _statement(List<JS.Statement> statements) {
3291 // TODO(jmesserly): empty block singleton?
3292 if (statements.length == 0) return new JS.Block([]);
3293 if (statements.length == 1) return statements[0];
3294 return new JS.Block(statements);
3295 }
3296
3297 /// Visits the catch clause body. This skips the exception type guard, if any.
3298 /// That is handled in [_visitCatch].
3299 @override
3300 JS.Statement visitCatchClause(CatchClause node) {
3301 var body = <JS.Statement>[];
3302
3303 var savedCatch = _catchParameter;
3304 if (node.catchKeyword != null) {
3305 var name = node.exceptionParameter;
3306 if (name != null && name != _catchParameter) {
3307 body.add(js
3308 .statement('let # = #;', [_visit(name), _visit(_catchParameter)]));
3309 _catchParameter = name;
3310 }
3311 if (node.stackTraceParameter != null) {
3312 var stackVar = node.stackTraceParameter.name;
3313 body.add(js.statement(
3314 'let # = dart.stackTrace(#);', [stackVar, _visit(name)]));
3315 }
3316 }
3317
3318 body.add(
3319 new JS.Block(_visitList(node.body.statements) as List<JS.Statement>));
3320 _catchParameter = savedCatch;
3321 return _statement(body);
3322 }
3323
3324 @override
3325 JS.Case visitSwitchCase(SwitchCase node) {
3326 var expr = _visit(node.expression);
3327 var body = _visitList(node.statements) as List<JS.Statement>;
3328 if (node.labels.isNotEmpty) {
3329 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
3330 }
3331 // TODO(jmesserly): make sure we are statically checking fall through
3332 return new JS.Case(expr, new JS.Block(body));
3333 }
3334
3335 @override
3336 JS.Default visitSwitchDefault(SwitchDefault node) {
3337 var body = _visitList(node.statements) as List<JS.Statement>;
3338 if (node.labels.isNotEmpty) {
3339 body.insert(0, js.comment('Unimplemented case labels: ${node.labels}'));
3340 }
3341 // TODO(jmesserly): make sure we are statically checking fall through
3342 return new JS.Default(new JS.Block(body));
3343 }
3344
3345 @override
3346 JS.Switch visitSwitchStatement(SwitchStatement node) => new JS.Switch(
3347 _visit(node.expression),
3348 _visitList(node.members) as List<JS.SwitchClause>);
3349
3350 @override
3351 JS.Statement visitLabeledStatement(LabeledStatement node) {
3352 var result = _visit(node.statement);
3353 for (var label in node.labels.reversed) {
3354 result = new JS.LabeledStatement(label.label.name, result);
3355 }
3356 return result;
3357 }
3358
3359 @override
3360 visitIntegerLiteral(IntegerLiteral node) => js.number(node.value);
3361
3362 @override
3363 visitDoubleLiteral(DoubleLiteral node) => js.number(node.value);
3364
3365 @override
3366 visitNullLiteral(NullLiteral node) => new JS.LiteralNull();
3367
3368 @override
3369 visitSymbolLiteral(SymbolLiteral node) {
3370 JS.New emitSymbol() {
3371 // TODO(vsm): When we canonicalize, we need to treat private symbols
3372 // correctly.
3373 var name = js.string(node.components.join('.'), "'");
3374 return new JS.New(_emitTypeName(types.symbolType), [name]);
3375 }
3376 return _emitConst(emitSymbol);
3377 }
3378
3379 @override
3380 visitListLiteral(ListLiteral node) {
3381 JS.Expression emitList() {
3382 JS.Expression list = new JS.ArrayInitializer(
3383 _visitList(node.elements) as List<JS.Expression>);
3384 ParameterizedType type = node.staticType;
3385 var elementType = type.typeArguments.single;
3386 // TODO(jmesserly): analyzer will usually infer `List<Object>` because
3387 // that is the least upper bound of the element types. So we rarely
3388 // generate a plain `List<dynamic>` anymore.
3389 if (!elementType.isDynamic) {
3390 // dart.list helper internally depends on _interceptors.JSArray.
3391 _loader.declareBeforeUse(_jsArray);
3392 list = js.call('dart.list(#, #)', [list, _emitTypeName(elementType)]);
3393 }
3394 return list;
3395 }
3396 if (node.constKeyword != null) return _emitConst(emitList);
3397 return emitList();
3398 }
3399
3400 @override
3401 visitMapLiteral(MapLiteral node) {
3402 // TODO(jmesserly): we can likely make these faster.
3403 JS.Expression emitMap() {
3404 var entries = node.entries;
3405 var mapArguments = null;
3406 var typeArgs = node.typeArguments;
3407 if (entries.isEmpty && typeArgs == null) {
3408 mapArguments = [];
3409 } else if (entries.every((e) => e.key is StringLiteral)) {
3410 // Use JS object literal notation if possible, otherwise use an array.
3411 // We could do this any time all keys are non-nullable String type.
3412 // For now, support StringLiteral as the common non-nullable String case .
3413 var props = <JS.Property>[];
3414 for (var e in entries) {
3415 props.add(new JS.Property(_visit(e.key), _visit(e.value)));
3416 }
3417 mapArguments = new JS.ObjectInitializer(props);
3418 } else {
3419 var values = <JS.Expression>[];
3420 for (var e in entries) {
3421 values.add(_visit(e.key));
3422 values.add(_visit(e.value));
3423 }
3424 mapArguments = new JS.ArrayInitializer(values);
3425 }
3426 var types = <JS.Expression>[];
3427 if (typeArgs != null) {
3428 types.addAll(typeArgs.arguments.map((e) => _emitTypeName(e.type)));
3429 }
3430 return js.call('dart.map(#, #)', [mapArguments, types]);
3431 }
3432 if (node.constKeyword != null) return _emitConst(emitMap);
3433 return emitMap();
3434 }
3435
3436 @override
3437 JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) =>
3438 js.escapedString(node.value, node.isSingleQuoted ? "'" : '"');
3439
3440 @override
3441 JS.Expression visitAdjacentStrings(AdjacentStrings node) =>
3442 _visitListToBinary(node.strings, '+');
3443
3444 @override
3445 JS.TemplateString visitStringInterpolation(StringInterpolation node) {
3446 // Assuming we implement toString() on our objects, we can avoid calling it
3447 // in most cases. Builtin types may differ though. We could handle this with
3448 // a tagged template.
3449 return new JS.TemplateString(_visitList(node.elements));
3450 }
3451
3452 @override
3453 String visitInterpolationString(InterpolationString node) {
3454 // TODO(jmesserly): this call adds quotes, and then we strip them off.
3455 var str = js.escapedString(node.value, '`').value;
3456 return str.substring(1, str.length - 1);
3457 }
3458
3459 @override
3460 visitInterpolationExpression(InterpolationExpression node) =>
3461 _visit(node.expression);
3462
3463 @override
3464 visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value);
3465
3466 @override
3467 JS.Expression visitExpression(Expression node) =>
3468 _unimplementedCall('Unimplemented ${node.runtimeType}: $node');
3469
3470 JS.Expression _unimplementedCall(String comment) {
3471 return js.call('dart.throw(#)', [js.escapedString(comment)]);
3472 }
3473
3474 @override
3475 visitNode(AstNode node) {
3476 // TODO(jmesserly): verify this is unreachable.
3477 throw 'Unimplemented ${node.runtimeType}: $node';
3478 }
3479
3480 _visit(AstNode node) {
3481 if (node == null) return null;
3482 var result = node.accept(this);
3483 if (result is JS.Node) result = annotate(result, node);
3484 return result;
3485 }
3486
3487 // TODO(jmesserly): this will need to be a generic method, if we ever want to
3488 // self-host strong mode.
3489 List/*<T>*/ _visitList/*<T>*/(Iterable<AstNode> nodes) {
3490 if (nodes == null) return null;
3491 var result = /*<T>*/ [];
3492 for (var node in nodes) result.add(_visit(node));
3493 return result;
3494 }
3495
3496 /// Visits a list of expressions, creating a comma expression if needed in JS.
3497 JS.Expression _visitListToBinary(List<Expression> nodes, String operator) {
3498 if (nodes == null || nodes.isEmpty) return null;
3499 return new JS.Expression.binary(
3500 _visitList(nodes) as List<JS.Expression>, operator);
3501 }
3502
3503 /// Return the bound type parameters for a ParameterizedType
3504 List<TypeParameterElement> _typeFormalsOf(ParameterizedType type) {
3505 return type is FunctionType ? type.typeFormals : type.typeParameters;
3506 }
3507
3508 /// Like [_emitMemberName], but for declaration sites.
3509 ///
3510 /// Unlike call sites, we always have an element available, so we can use it
3511 /// directly rather than computing the relevant options for [_emitMemberName].
3512 JS.Expression _elementMemberName(ExecutableElement e,
3513 {bool allowExtensions: true}) {
3514 String name;
3515 if (e is PropertyAccessorElement) {
3516 name = e.variable.name;
3517 } else {
3518 name = e.name;
3519 }
3520 return _emitMemberName(name,
3521 type: (e.enclosingElement as ClassElement).type,
3522 unary: e.parameters.isEmpty,
3523 isStatic: e.isStatic,
3524 allowExtensions: allowExtensions);
3525 }
3526
3527 /// This handles member renaming for private names and operators.
3528 ///
3529 /// Private names are generated using ES6 symbols:
3530 ///
3531 /// // At the top of the module:
3532 /// let _x = Symbol('_x');
3533 /// let _y = Symbol('_y');
3534 /// ...
3535 ///
3536 /// class Point {
3537 /// Point(x, y) {
3538 /// this[_x] = x;
3539 /// this[_y] = y;
3540 /// }
3541 /// get x() { return this[_x]; }
3542 /// get y() { return this[_y]; }
3543 /// }
3544 ///
3545 /// For user-defined operators the following names are allowed:
3546 ///
3547 /// <, >, <=, >=, ==, -, +, /, ~/, *, %, |, ^, &, <<, >>, []=, [], ~
3548 ///
3549 /// They generate code like:
3550 ///
3551 /// x['+'](y)
3552 ///
3553 /// There are three exceptions: [], []= and unary -.
3554 /// The indexing operators we use `get` and `set` instead:
3555 ///
3556 /// x.get('hi')
3557 /// x.set('hi', 123)
3558 ///
3559 /// This follows the same pattern as EcmaScript 6 Map:
3560 /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_ Objects/Map>
3561 ///
3562 /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed
3563 /// for this transformation to happen, otherwise binary minus is assumed.
3564 ///
3565 /// Equality is a bit special, it is generated via the Dart `equals` runtime
3566 /// helper, that checks for null. The user defined method is called '=='.
3567 ///
3568 JS.Expression _emitMemberName(String name,
3569 {DartType type,
3570 bool unary: false,
3571 bool isStatic: false,
3572 bool allowExtensions: true}) {
3573 // Static members skip the rename steps.
3574 if (isStatic) return _propertyName(name);
3575
3576 if (name.startsWith('_')) {
3577 return _privateNames.putIfAbsent(
3578 name, () => _initSymbol(new JS.TemporaryId(name)) as JS.TemporaryId);
3579 }
3580
3581 if (name == '[]') {
3582 name = 'get';
3583 } else if (name == '[]=') {
3584 name = 'set';
3585 } else if (name == '-' && unary) {
3586 name = 'unary-';
3587 } else if (name == 'constructor' || name == 'prototype') {
3588 // This uses an illegal (in Dart) character for a member, avoiding the
3589 // conflict. We could use practically any character for this.
3590 name = '+$name';
3591 }
3592
3593 // Dart "extension" methods. Used for JS Array, Boolean, Number, String.
3594 var baseType = type;
3595 while (baseType is TypeParameterType) {
3596 baseType = baseType.element.bound;
3597 }
3598 if (allowExtensions &&
3599 baseType != null &&
3600 _extensionTypes.contains(baseType.element) &&
3601 !isObjectProperty(name)) {
3602 return js.call('dartx.#', _propertyName(name));
3603 }
3604
3605 return _propertyName(name);
3606 }
3607
3608 bool _externalOrNative(node) =>
3609 node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
3610
3611 FunctionBody _functionBody(node) =>
3612 node is FunctionDeclaration ? node.functionExpression.body : node.body;
3613
3614 /// Choose a canonical name from the library element.
3615 /// This never uses the library's name (the identifier in the `library`
3616 /// declaration) as it doesn't have any meaningful rules enforced.
3617 JS.Identifier emitLibraryName(LibraryElement library) {
3618 if (library == currentLibrary) return _exportsVar;
3619 if (library.name == 'dart._runtime') return _runtimeLibVar;
3620 return _imports.putIfAbsent(
3621 library, () => new JS.TemporaryId(jsLibraryName(library)));
3622 }
3623
3624 JS.Node annotate(JS.Node node, AstNode original, [Element element]) {
3625 if (options.closure && element != null) {
3626 node = node.withClosureAnnotation(closureAnnotationFor(
3627 node, original, element, namedArgumentTemp.name));
3628 }
3629 return node..sourceInformation = original;
3630 }
3631
3632 /// Returns true if this is any kind of object represented by `Number` in JS.
3633 ///
3634 /// In practice, this is 4 types: num, int, double, and JSNumber.
3635 ///
3636 /// JSNumber is the type that actually "implements" all numbers, hence it's
3637 /// a subtype of int and double (and num). It's in our "dart:_interceptors".
3638 bool _isNumberInJS(DartType t) => rules.isSubtypeOf(t, _types.numType);
3639
3640 bool _isObjectGetter(String name) {
3641 PropertyAccessorElement element = _types.objectType.element.getGetter(name);
3642 return (element != null && !element.isStatic);
3643 }
3644
3645 bool _isObjectMethod(String name) {
3646 MethodElement element = _types.objectType.element.getMethod(name);
3647 return (element != null && !element.isStatic);
3648 }
3649
3650 bool isObjectProperty(String name) {
3651 return _isObjectGetter(name) || _isObjectMethod(name);
3652 }
3653
3654 // TODO(leafp): Various analyzer pieces computed similar things.
3655 // Share this logic somewhere?
3656 DartType _getExpectedReturnType(ExecutableElement element) {
3657 FunctionType functionType = element.type;
3658 if (functionType == null) {
3659 return DynamicTypeImpl.instance;
3660 }
3661 var type = functionType.returnType;
3662
3663 InterfaceType expectedType = null;
3664 if (element.isAsynchronous) {
3665 if (element.isGenerator) {
3666 // Stream<T> -> T
3667 expectedType = _types.streamType;
3668 } else {
3669 // Future<T> -> T
3670 // TODO(vsm): Revisit with issue #228.
3671 expectedType = _types.futureType;
3672 }
3673 } else {
3674 if (element.isGenerator) {
3675 // Iterable<T> -> T
3676 expectedType = _types.iterableType;
3677 } else {
3678 // T -> T
3679 return type;
3680 }
3681 }
3682 if (type.isDynamic) {
3683 return type;
3684 } else if (type is InterfaceType && type.element == expectedType.element) {
3685 return type.typeArguments[0];
3686 } else {
3687 // TODO(leafp): The above only handles the case where the return type
3688 // is exactly Future/Stream/Iterable. Handle the subtype case.
3689 return DynamicTypeImpl.instance;
3690 }
3691 }
3692 }
3693
3694 class ExtensionTypeSet extends GeneralizingElementVisitor {
3695 final AnalysisContext _context;
3696 final TypeProvider _types;
3697
3698 final _extensionTypes = new HashSet<ClassElement>();
3699 final _pendingLibraries = new HashSet<String>();
3700
3701 ExtensionTypeSet(AbstractCompiler compiler)
3702 : _context = compiler.context,
3703 _types = compiler.context.typeProvider;
3704
3705 visitClassElement(ClassElement element) {
3706 if (findAnnotation(element, isJsPeerInterface) != null ||
3707 findAnnotation(element, isNativeAnnotation) != null) {
3708 _addExtensionType(element.type);
3709 }
3710 }
3711
3712 void _addExtensionType(InterfaceType t) {
3713 if (t.isObject || !_extensionTypes.add(t.element)) return;
3714 t = fillDynamicTypeArgs(t, _types) as InterfaceType;
3715 t.interfaces.forEach(_addExtensionType);
3716 t.mixins.forEach(_addExtensionType);
3717 _addExtensionType(t.superclass);
3718 }
3719
3720 void _addExtensionTypesForLibrary(String libraryUri, List<String> typeNames) {
3721 var sourceFactory = _context.sourceFactory.forUri(libraryUri);
3722 var library = _context.computeLibraryElement(sourceFactory);
3723 for (var typeName in typeNames) {
3724 var element = library.getType(typeName);
3725 _addExtensionType(element.type);
3726 }
3727 }
3728
3729 void _addExtensionTypes(String libraryUri) {
3730 var sourceFactory = _context.sourceFactory.forUri(libraryUri);
3731 var library = _context.computeLibraryElement(sourceFactory);
3732 visitLibraryElement(library);
3733 }
3734
3735 void _addPendingExtensionTypes(String libraryUri) {
3736 _pendingLibraries.add(libraryUri);
3737 }
3738
3739 bool contains(Element element) {
3740 if (_extensionTypes.contains(element)) return true;
3741 if (_pendingLibraries.isEmpty) return false;
3742 if (element is ClassElement) {
3743 var uri = element.library.source.uri.toString();
3744 if (_pendingLibraries.contains(uri)) {
3745 // Load all pending libraries
3746 for (var libraryUri in _pendingLibraries) {
3747 _addExtensionTypes(libraryUri);
3748 }
3749 _pendingLibraries.clear();
3750 return _extensionTypes.contains(element);
3751 }
3752 }
3753 return false;
3754 }
3755 }
3756
3757 class JSGenerator {
3758 final AbstractCompiler compiler;
3759 final ExtensionTypeSet _extensionTypes;
3760 final TypeProvider _types;
3761
3762 JSGenerator(AbstractCompiler compiler)
3763 : compiler = compiler,
3764 _types = compiler.context.typeProvider,
3765 _extensionTypes = new ExtensionTypeSet(compiler) {
3766 // TODO(vsm): Eventually, we want to make this extensible - i.e., find
3767 // annotations in user code as well. It would need to be summarized in
3768 // the element model - not searched this way on every compile. To make this
3769 // a little more efficient now, we do this in two phases.
3770
3771 // First, core types:
3772 _extensionTypes._addExtensionTypes('dart:_interceptors');
3773 _extensionTypes._addExtensionTypes('dart:_native_typed_data');
3774 // TODO(vsm): If we're analyzing against the main SDK, those
3775 // types are not explicitly annotated.
3776 _extensionTypes._addExtensionType(_types.intType);
3777 _extensionTypes._addExtensionType(_types.doubleType);
3778 _extensionTypes._addExtensionType(_types.boolType);
3779 _extensionTypes._addExtensionType(_types.stringType);
3780 // These are used natively by dart:html but also not annotated.
3781 _extensionTypes
3782 ._addExtensionTypesForLibrary('dart:core', ['Comparable', 'Map']);
3783 _extensionTypes
3784 ._addExtensionTypesForLibrary('dart:collection', ['ListMixin']);
3785 _extensionTypes._addExtensionTypesForLibrary('dart:math', ['Rectangle']);
3786
3787 // Second, html types - these are only searched if we use dart:html, etc.:
3788 _extensionTypes._addPendingExtensionTypes('dart:html');
3789 _extensionTypes._addPendingExtensionTypes('dart:indexed_db');
3790 _extensionTypes._addPendingExtensionTypes('dart:svg');
3791 _extensionTypes._addPendingExtensionTypes('dart:web_audio');
3792 _extensionTypes._addPendingExtensionTypes('dart:web_gl');
3793 _extensionTypes._addPendingExtensionTypes('dart:web_sql');
3794 }
3795
3796 void generateLibrary(List<CompilationUnit> units) {
3797 var library = units.first.element.library;
3798 var fields =
3799 findFieldsNeedingStorage(units.map((c) => c.element), _extensionTypes);
3800 var rules = new StrongTypeSystemImpl();
3801 var codegen =
3802 new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields);
3803 var module = codegen.emitLibrary(units);
3804 var out = compiler.getOutputPath(library.source.uri);
3805 var options = compiler.options.codegenOptions;
3806 writeJsLibrary(module, out, compiler.inputBaseDir,
3807 emitTypes: options.closure,
3808 emitSourceMaps: options.emitSourceMaps,
3809 fileSystem: compiler.fileSystem);
3810 }
3811 }
3812
3813 /// Choose a canonical name from the library element.
3814 /// This never uses the library's name (the identifier in the `library`
3815 /// declaration) as it doesn't have any meaningful rules enforced.
3816 String jsLibraryName(LibraryElement library) => canonicalLibraryName(library);
3817
3818 /// Shorthand for identifier-like property names.
3819 /// For now, we emit them as strings and the printer restores them to
3820 /// identifiers if it can.
3821 // TODO(jmesserly): avoid the round tripping through quoted form.
3822 JS.LiteralString _propertyName(String name) => js.string(name, "'");
3823
3824 // TODO(jacobr): we would like to do something like the following
3825 // but we don't have summary support yet.
3826 // bool _supportJsExtensionMethod(AnnotatedNode node) =>
3827 // _getAnnotation(node, "SupportJsExtensionMethod") != null;
3828
3829 /// A special kind of element created by the compiler, signifying a temporary
3830 /// variable. These objects use instance equality, and should be shared
3831 /// everywhere in the tree where they are treated as the same variable.
3832 class TemporaryVariableElement extends LocalVariableElementImpl {
3833 TemporaryVariableElement.forNode(Identifier name) : super.forNode(name);
3834
3835 int get hashCode => identityHashCode(this);
3836 bool operator ==(Object other) => identical(this, other);
3837 }
OLDNEW
« no previous file with comments | « lib/src/codegen/html_codegen.dart ('k') | lib/src/codegen/js_field_storage.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698