| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of native; | |
| 6 | |
| 7 /// This class is a temporary work-around until we get a more powerful DartType. | |
| 8 class SpecialType { | |
| 9 final String name; | |
| 10 const SpecialType._(this.name); | |
| 11 | |
| 12 /// The type Object, but no subtypes: | |
| 13 static const JsObject = const SpecialType._('=Object'); | |
| 14 | |
| 15 int get hashCode => name.hashCode; | |
| 16 } | |
| 17 | |
| 18 /** | |
| 19 * A summary of the behavior of a native element. | |
| 20 * | |
| 21 * Native code can return values of one type and cause native subtypes of | |
| 22 * another type to be instantiated. By default, we compute both from the | |
| 23 * declared type. | |
| 24 * | |
| 25 * A field might yield any native type that 'is' the field type. | |
| 26 * | |
| 27 * A method might create and return instances of native subclasses of its | |
| 28 * declared return type, and a callback argument may be called with instances of | |
| 29 * the callback parameter type (e.g. Event). | |
| 30 * | |
| 31 * If there is one or more `@Creates` annotations, the union of the named types | |
| 32 * replaces the inferred instantiated type, and the return type is ignored for | |
| 33 * the purpose of inferring instantiated types. | |
| 34 * | |
| 35 * @Creates('IDBCursor') // Created asynchronously. | |
| 36 * @Creates('IDBRequest') // Created synchronously (for return value). | |
| 37 * IDBRequest openCursor(); | |
| 38 * | |
| 39 * If there is one or more `@Returns` annotations, the union of the named types | |
| 40 * replaces the declared return type. | |
| 41 * | |
| 42 * @Returns('IDBRequest') | |
| 43 * IDBRequest openCursor(); | |
| 44 * | |
| 45 * Types in annotations are non-nullable, so include `@Returns('Null')` if | |
| 46 * `null` may be returned. | |
| 47 */ | |
| 48 class NativeBehavior { | |
| 49 | |
| 50 /// [DartType]s or [SpecialType]s returned or yielded by the native element. | |
| 51 final List typesReturned = []; | |
| 52 | |
| 53 /// [DartType]s or [SpecialType]s instantiated by the native element. | |
| 54 final List typesInstantiated = []; | |
| 55 | |
| 56 // If this behavior is for a JS expression, [codeTemplate] contains the | |
| 57 // parsed tree. | |
| 58 js.Template codeTemplate; | |
| 59 | |
| 60 final SideEffects sideEffects = new SideEffects.empty(); | |
| 61 | |
| 62 static NativeBehavior NONE = new NativeBehavior(); | |
| 63 | |
| 64 /// Processes the type specification string of a call to JS and stores the | |
| 65 /// result in the [typesReturned] and [typesInstantiated]. | |
| 66 /// | |
| 67 /// Two forms of the string is supported: | |
| 68 /// 1) A single type string of the form 'void', '', 'var' or 'T1|...|Tn' | |
| 69 /// which defines the types returned and for the later form also created by | |
| 70 /// the call to JS. | |
| 71 /// 2) A sequence of the form '<tag>:<type-string>;' where <tag> is either | |
| 72 /// 'returns' or 'creates' and where <type-string> is a type string like in | |
| 73 /// 1). The type string marked by 'returns' defines the types returned and | |
| 74 /// 'creates' defines the types created by the call to JS. Each tag kind | |
| 75 /// can only occur once in the sequence. | |
| 76 /// | |
| 77 /// [specString] is the specification string, [resolveType] resolves named | |
| 78 /// types into type values, [typesReturned] and [typesInstantiated] collects | |
| 79 /// the types defined by the specification string, and [objectType] and | |
| 80 /// [nullType] define the types for `Object` and `Null`, respectively. The | |
| 81 /// latter is used for the type strings of the form '' and 'var'. | |
| 82 // TODO(johnniwinther): Use ';' as a separator instead of a terminator. | |
| 83 static void processSpecString( | |
| 84 DiagnosticListener listener, | |
| 85 Spannable spannable, | |
| 86 String specString, | |
| 87 {dynamic resolveType(String typeString), | |
| 88 List typesReturned, List typesInstantiated, | |
| 89 objectType, nullType}) { | |
| 90 | |
| 91 /// Resolve a type string of one of the three forms: | |
| 92 /// * 'void' - in which case [onVoid] is called, | |
| 93 /// * '' or 'var' - in which case [onVar] is called, | |
| 94 /// * 'T1|...|Tn' - in which case [onType] is called for each Ti. | |
| 95 void resolveTypesString(String typesString, | |
| 96 {onVoid(), onVar(), onType(type)}) { | |
| 97 // Various things that are not in fact types. | |
| 98 if (typesString == 'void') { | |
| 99 if (onVoid != null) { | |
| 100 onVoid(); | |
| 101 } | |
| 102 return; | |
| 103 } | |
| 104 if (typesString == '' || typesString == 'var') { | |
| 105 if (onVar != null) { | |
| 106 onVar(); | |
| 107 } | |
| 108 return; | |
| 109 } | |
| 110 for (final typeString in typesString.split('|')) { | |
| 111 onType(resolveType(typeString)); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 if (specString.contains(':')) { | |
| 116 /// Find and remove a substring of the form 'tag:<type-string>;' from | |
| 117 /// [specString]. | |
| 118 String getTypesString(String tag) { | |
| 119 String marker = '$tag:'; | |
| 120 int startPos = specString.indexOf(marker); | |
| 121 if (startPos == -1) return null; | |
| 122 int endPos = specString.indexOf(';', startPos); | |
| 123 if (endPos == -1) return null; | |
| 124 String typeString = | |
| 125 specString.substring(startPos + marker.length, endPos); | |
| 126 specString = '${specString.substring(0, startPos)}' | |
| 127 '${specString.substring(endPos + 1)}'.trim(); | |
| 128 return typeString; | |
| 129 } | |
| 130 | |
| 131 String returns = getTypesString('returns'); | |
| 132 if (returns != null) { | |
| 133 resolveTypesString(returns, onVar: () { | |
| 134 typesReturned.add(objectType); | |
| 135 typesReturned.add(nullType); | |
| 136 }, onType: (type) { | |
| 137 typesReturned.add(type); | |
| 138 }); | |
| 139 } | |
| 140 | |
| 141 String creates = getTypesString('creates'); | |
| 142 if (creates != null) { | |
| 143 resolveTypesString(creates, onVoid: () { | |
| 144 listener.internalError(spannable, | |
| 145 "Invalid type string 'creates:$creates'"); | |
| 146 }, onVar: () { | |
| 147 listener.internalError(spannable, | |
| 148 "Invalid type string 'creates:$creates'"); | |
| 149 }, onType: (type) { | |
| 150 typesInstantiated.add(type); | |
| 151 }); | |
| 152 } | |
| 153 | |
| 154 if (!specString.isEmpty) { | |
| 155 listener.internalError(spannable, "Invalid JS type string."); | |
| 156 } | |
| 157 } else { | |
| 158 resolveTypesString(specString, onVar: () { | |
| 159 typesReturned.add(objectType); | |
| 160 typesReturned.add(nullType); | |
| 161 }, onType: (type) { | |
| 162 typesInstantiated.add(type); | |
| 163 typesReturned.add(type); | |
| 164 }); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { | |
| 169 // The first argument of a JS-call is a string encoding various attributes | |
| 170 // of the code. | |
| 171 // | |
| 172 // 'Type1|Type2'. A union type. | |
| 173 // '=Object'. A JavaScript Object, no subtype. | |
| 174 | |
| 175 var argNodes = jsCall.arguments; | |
| 176 if (argNodes.isEmpty) { | |
| 177 compiler.internalError(jsCall, "JS expression has no type."); | |
| 178 } | |
| 179 | |
| 180 var code = argNodes.tail.head; | |
| 181 if (code is !StringNode || code.isInterpolation) { | |
| 182 compiler.internalError(code, 'JS code must be a string literal.'); | |
| 183 } | |
| 184 | |
| 185 LiteralString specLiteral = argNodes.head.asLiteralString(); | |
| 186 if (specLiteral == null) { | |
| 187 // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It | |
| 188 // is not very satisfactory because it does not work for void, dynamic. | |
| 189 compiler.internalError(argNodes.head, "Unexpected JS first argument."); | |
| 190 } | |
| 191 | |
| 192 NativeBehavior behavior = new NativeBehavior(); | |
| 193 behavior.codeTemplate = | |
| 194 js.js.parseForeignJS(code.dartString.slowToString()); | |
| 195 new SideEffectsVisitor(behavior.sideEffects) | |
| 196 .visit(behavior.codeTemplate.ast); | |
| 197 | |
| 198 String specString = specLiteral.dartString.slowToString(); | |
| 199 | |
| 200 resolveType(String typeString) { | |
| 201 return _parseType( | |
| 202 typeString, | |
| 203 compiler, | |
| 204 (name) => resolver.resolveTypeFromString(specLiteral, name), | |
| 205 jsCall); | |
| 206 } | |
| 207 | |
| 208 processSpecString(compiler, jsCall, | |
| 209 specString, | |
| 210 resolveType: resolveType, | |
| 211 typesReturned: behavior.typesReturned, | |
| 212 typesInstantiated: behavior.typesInstantiated, | |
| 213 objectType: compiler.objectClass.computeType(compiler), | |
| 214 nullType: compiler.nullClass.computeType(compiler)); | |
| 215 | |
| 216 return behavior; | |
| 217 } | |
| 218 | |
| 219 static NativeBehavior ofJsEmbeddedGlobalCall(Send jsGlobalCall, | |
| 220 Compiler compiler, | |
| 221 resolver) { | |
| 222 // The first argument of a JS-embedded global call is a string encoding | |
| 223 // the type of the code. | |
| 224 // | |
| 225 // 'Type1|Type2'. A union type. | |
| 226 // '=Object'. A JavaScript Object, no subtype. | |
| 227 | |
| 228 Link<Node> argNodes = jsGlobalCall.arguments; | |
| 229 if (argNodes.isEmpty) { | |
| 230 compiler.internalError(jsGlobalCall, | |
| 231 "JS embedded global expression has no type."); | |
| 232 } | |
| 233 | |
| 234 // We don't check the given name. That needs to be done at a later point. | |
| 235 // This is, because we want to allow non-literals as names. | |
| 236 if (argNodes.tail.isEmpty) { | |
| 237 compiler.internalError(jsGlobalCall, 'Embedded Global is missing name'); | |
| 238 } | |
| 239 | |
| 240 if (!argNodes.tail.tail.isEmpty) { | |
| 241 compiler.internalError(argNodes.tail.tail.head, | |
| 242 'Embedded Global has more than 2 arguments'); | |
| 243 } | |
| 244 | |
| 245 LiteralString specLiteral = argNodes.head.asLiteralString(); | |
| 246 if (specLiteral == null) { | |
| 247 // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It | |
| 248 // is not very satisfactory because it does not work for void, dynamic. | |
| 249 compiler.internalError(argNodes.head, "Unexpected first argument."); | |
| 250 } | |
| 251 | |
| 252 NativeBehavior behavior = new NativeBehavior(); | |
| 253 | |
| 254 String specString = specLiteral.dartString.slowToString(); | |
| 255 | |
| 256 resolveType(String typeString) { | |
| 257 return _parseType( | |
| 258 typeString, | |
| 259 compiler, | |
| 260 (name) => resolver.resolveTypeFromString(specLiteral, name), | |
| 261 jsGlobalCall); | |
| 262 } | |
| 263 | |
| 264 processSpecString(compiler, jsGlobalCall, | |
| 265 specString, | |
| 266 resolveType: resolveType, | |
| 267 typesReturned: behavior.typesReturned, | |
| 268 typesInstantiated: behavior.typesInstantiated, | |
| 269 objectType: compiler.objectClass.computeType(compiler), | |
| 270 nullType: compiler.nullClass.computeType(compiler)); | |
| 271 | |
| 272 return behavior; | |
| 273 } | |
| 274 | |
| 275 static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) { | |
| 276 FunctionType type = method.computeType(compiler); | |
| 277 var behavior = new NativeBehavior(); | |
| 278 behavior.typesReturned.add(type.returnType); | |
| 279 if (!type.returnType.isVoid) { | |
| 280 // Declared types are nullable. | |
| 281 behavior.typesReturned.add(compiler.nullClass.computeType(compiler)); | |
| 282 } | |
| 283 behavior._capture(type, compiler); | |
| 284 | |
| 285 // TODO(sra): Optional arguments are currently missing from the | |
| 286 // DartType. This should be fixed so the following work-around can be | |
| 287 // removed. | |
| 288 method.functionSignature.forEachOptionalParameter( | |
| 289 (ParameterElement parameter) { | |
| 290 behavior._escape(parameter.type, compiler); | |
| 291 }); | |
| 292 | |
| 293 behavior._overrideWithAnnotations(method, compiler); | |
| 294 return behavior; | |
| 295 } | |
| 296 | |
| 297 static NativeBehavior ofFieldLoad(Element field, Compiler compiler) { | |
| 298 DartType type = field.computeType(compiler); | |
| 299 var behavior = new NativeBehavior(); | |
| 300 behavior.typesReturned.add(type); | |
| 301 // Declared types are nullable. | |
| 302 behavior.typesReturned.add(compiler.nullClass.computeType(compiler)); | |
| 303 behavior._capture(type, compiler); | |
| 304 behavior._overrideWithAnnotations(field, compiler); | |
| 305 return behavior; | |
| 306 } | |
| 307 | |
| 308 static NativeBehavior ofFieldStore(Element field, Compiler compiler) { | |
| 309 DartType type = field.computeType(compiler); | |
| 310 var behavior = new NativeBehavior(); | |
| 311 behavior._escape(type, compiler); | |
| 312 // We don't override the default behaviour - the annotations apply to | |
| 313 // loading the field. | |
| 314 return behavior; | |
| 315 } | |
| 316 | |
| 317 void _overrideWithAnnotations(Element element, Compiler compiler) { | |
| 318 if (element.metadata.isEmpty) return; | |
| 319 | |
| 320 DartType lookup(String name) { | |
| 321 Element e = element.buildScope().lookup(name); | |
| 322 if (e == null) return null; | |
| 323 if (e is! ClassElement) return null; | |
| 324 ClassElement cls = e; | |
| 325 cls.ensureResolved(compiler); | |
| 326 return cls.thisType; | |
| 327 } | |
| 328 | |
| 329 NativeEnqueuer enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; | |
| 330 var creates = _collect(element, compiler, enqueuer.annotationCreatesClass, | |
| 331 lookup); | |
| 332 var returns = _collect(element, compiler, enqueuer.annotationReturnsClass, | |
| 333 lookup); | |
| 334 | |
| 335 if (creates != null) { | |
| 336 typesInstantiated..clear()..addAll(creates); | |
| 337 } | |
| 338 if (returns != null) { | |
| 339 typesReturned..clear()..addAll(returns); | |
| 340 } | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Returns a list of type constraints from the annotations of | |
| 345 * [annotationClass]. | |
| 346 * Returns `null` if no constraints. | |
| 347 */ | |
| 348 static _collect(Element element, Compiler compiler, Element annotationClass, | |
| 349 lookup(str)) { | |
| 350 var types = null; | |
| 351 for (Link<MetadataAnnotation> link = element.metadata; | |
| 352 !link.isEmpty; | |
| 353 link = link.tail) { | |
| 354 MetadataAnnotation annotation = link.head.ensureResolved(compiler); | |
| 355 ConstantValue value = annotation.constant.value; | |
| 356 if (!value.isConstructedObject) continue; | |
| 357 ConstructedConstantValue constructedObject = value; | |
| 358 if (constructedObject.type.element != annotationClass) continue; | |
| 359 | |
| 360 List<ConstantValue> fields = constructedObject.fields; | |
| 361 // TODO(sra): Better validation of the constant. | |
| 362 if (fields.length != 1 || !fields[0].isString) { | |
| 363 PartialMetadataAnnotation partial = annotation; | |
| 364 compiler.internalError(annotation, | |
| 365 'Annotations needs one string: ${partial.parseNode(compiler)}'); | |
| 366 } | |
| 367 StringConstantValue specStringConstant = fields[0]; | |
| 368 String specString = specStringConstant.toDartString().slowToString(); | |
| 369 for (final typeString in specString.split('|')) { | |
| 370 var type = _parseType(typeString, compiler, lookup, annotation); | |
| 371 if (types == null) types = []; | |
| 372 types.add(type); | |
| 373 } | |
| 374 } | |
| 375 return types; | |
| 376 } | |
| 377 | |
| 378 /// Models the behavior of having intances of [type] escape from Dart code | |
| 379 /// into native code. | |
| 380 void _escape(DartType type, Compiler compiler) { | |
| 381 type = type.unalias(compiler); | |
| 382 if (type is FunctionType) { | |
| 383 FunctionType functionType = type; | |
| 384 // A function might be called from native code, passing us novel | |
| 385 // parameters. | |
| 386 _escape(functionType.returnType, compiler); | |
| 387 for (DartType parameter in functionType.parameterTypes) { | |
| 388 _capture(parameter, compiler); | |
| 389 } | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 /// Models the behavior of Dart code receiving instances and methods of [type] | |
| 394 /// from native code. We usually start the analysis by capturing a native | |
| 395 /// method that has been used. | |
| 396 void _capture(DartType type, Compiler compiler) { | |
| 397 type = type.unalias(compiler); | |
| 398 if (type is FunctionType) { | |
| 399 FunctionType functionType = type; | |
| 400 _capture(functionType.returnType, compiler); | |
| 401 for (DartType parameter in functionType.parameterTypes) { | |
| 402 _escape(parameter, compiler); | |
| 403 } | |
| 404 } else { | |
| 405 typesInstantiated.add(type); | |
| 406 } | |
| 407 } | |
| 408 | |
| 409 static _parseType(String typeString, Compiler compiler, | |
| 410 lookup(name), locationNodeOrElement) { | |
| 411 if (typeString == '=Object') return SpecialType.JsObject; | |
| 412 if (typeString == 'dynamic') { | |
| 413 return const DynamicType(); | |
| 414 } | |
| 415 DartType type = lookup(typeString); | |
| 416 if (type != null) return type; | |
| 417 | |
| 418 int index = typeString.indexOf('<'); | |
| 419 if (index < 1) { | |
| 420 compiler.internalError( | |
| 421 _errorNode(locationNodeOrElement, compiler), | |
| 422 "Type '$typeString' not found."); | |
| 423 } | |
| 424 type = lookup(typeString.substring(0, index)); | |
| 425 if (type != null) { | |
| 426 // TODO(sra): Parse type parameters. | |
| 427 return type; | |
| 428 } | |
| 429 compiler.internalError( | |
| 430 _errorNode(locationNodeOrElement, compiler), | |
| 431 "Type '$typeString' not found."); | |
| 432 } | |
| 433 | |
| 434 static _errorNode(locationNodeOrElement, compiler) { | |
| 435 if (locationNodeOrElement is Node) return locationNodeOrElement; | |
| 436 return locationNodeOrElement.parseNode(compiler); | |
| 437 } | |
| 438 } | |
| OLD | NEW |