OLD | NEW |
| (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 } | |
OLD | NEW |