| 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 |