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 |