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 import 'package:front_end/src/fasta/scanner.dart' show StringToken, Token; |
| 6 |
| 7 import '../common.dart'; |
| 8 import '../common/backend_api.dart'; |
| 9 import '../compiler.dart' show Compiler; |
| 10 import '../constants/values.dart'; |
| 11 import '../elements/elements.dart' |
| 12 show |
| 13 ClassElement, |
| 14 Element, |
| 15 LibraryElement, |
| 16 MemberElement, |
| 17 MetadataAnnotation, |
| 18 MethodElement; |
| 19 import '../elements/modelx.dart' show FunctionElementX, MetadataAnnotationX; |
| 20 import '../elements/resolution_types.dart' show ResolutionDartType; |
| 21 import '../js_backend/js_backend.dart'; |
| 22 import '../js_backend/native_data.dart'; |
| 23 import '../patch_parser.dart'; |
| 24 import '../tree/tree.dart'; |
| 25 import 'behavior.dart'; |
| 26 |
| 27 abstract class NativeDataResolver { |
| 28 /// Returns `true` if [element] is a JsInterop member. |
| 29 bool isJsInteropMember(MemberElement element); |
| 30 |
| 31 /// Computes whether [element] is native or JsInterop and, if so, registers |
| 32 /// its [NativeBehavior]s to [registry]. |
| 33 void resolveNativeMember(MemberElement element, NativeRegistry registry); |
| 34 } |
| 35 |
| 36 class NativeDataResolverImpl implements NativeDataResolver { |
| 37 static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'); |
| 38 |
| 39 final Compiler _compiler; |
| 40 |
| 41 NativeDataResolverImpl(this._compiler); |
| 42 |
| 43 JavaScriptBackend get _backend => _compiler.backend; |
| 44 DiagnosticReporter get _reporter => _compiler.reporter; |
| 45 NativeClassData get _nativeClassData => _backend.nativeClassData; |
| 46 NativeDataBuilder get _nativeDataBuilder => _backend.nativeDataBuilder; |
| 47 |
| 48 @override |
| 49 bool isJsInteropMember(MemberElement element) { |
| 50 // TODO(johnniwinther): Avoid computing this twice for external function; |
| 51 // once from JavaScriptBackendTarget.resolveExternalFunction and once |
| 52 // through JavaScriptBackendTarget.resolveNativeMember. |
| 53 bool isJsInterop = |
| 54 checkJsInteropMemberAnnotations(_compiler, element, _nativeDataBuilder); |
| 55 if (!isJsInterop) { |
| 56 if (element.enclosingClass != null) { |
| 57 isJsInterop = _nativeClassData.isJsInteropClass(element.enclosingClass); |
| 58 } else { |
| 59 isJsInterop = _nativeClassData.isJsInteropLibrary(element.library); |
| 60 } |
| 61 } |
| 62 return isJsInterop; |
| 63 } |
| 64 |
| 65 void resolveNativeMember(MemberElement element, NativeRegistry registry) { |
| 66 bool isJsInterop = isJsInteropMember(element); |
| 67 if (element.isFunction || |
| 68 element.isConstructor || |
| 69 element.isGetter || |
| 70 element.isSetter) { |
| 71 bool isNative = _processMethodAnnotations(element); |
| 72 if (isNative || isJsInterop) { |
| 73 NativeBehavior behavior = NativeBehavior |
| 74 .ofMethodElement(element, _compiler, isJsInterop: isJsInterop); |
| 75 _nativeDataBuilder.setNativeMethodBehavior(element, behavior); |
| 76 registry.registerNativeData(behavior); |
| 77 } |
| 78 } else if (element.isField) { |
| 79 bool isNative = _processFieldAnnotations(element); |
| 80 if (isNative || isJsInterop) { |
| 81 NativeBehavior fieldLoadBehavior = NativeBehavior |
| 82 .ofFieldElementLoad(element, _compiler, isJsInterop: isJsInterop); |
| 83 NativeBehavior fieldStoreBehavior = |
| 84 NativeBehavior.ofFieldElementStore(element, _compiler); |
| 85 _nativeDataBuilder.setNativeFieldLoadBehavior( |
| 86 element, fieldLoadBehavior); |
| 87 _nativeDataBuilder.setNativeFieldStoreBehavior( |
| 88 element, fieldStoreBehavior); |
| 89 |
| 90 // TODO(sra): Process fields for storing separately. |
| 91 // We have to handle both loading and storing to the field because we |
| 92 // only get one look at each member and there might be a load or store |
| 93 // we have not seen yet. |
| 94 registry.registerNativeData(fieldLoadBehavior); |
| 95 registry.registerNativeData(fieldStoreBehavior); |
| 96 } |
| 97 } |
| 98 } |
| 99 |
| 100 /// Process the potentially native [field]. Adds information from metadata |
| 101 /// attributes. Returns `true` of [method] is native. |
| 102 bool _processFieldAnnotations(Element element) { |
| 103 if (_compiler.serialization.isDeserialized(element)) { |
| 104 return false; |
| 105 } |
| 106 if (element.isInstanceMember && |
| 107 _backend.nativeClassData.isNativeClass(element.enclosingClass)) { |
| 108 // Exclude non-instance (static) fields - they are not really native and |
| 109 // are compiled as isolate globals. Access of a property of a constructor |
| 110 // function or a non-method property in the prototype chain, must be coded |
| 111 // using a JS-call. |
| 112 _setNativeName(element); |
| 113 return true; |
| 114 } |
| 115 return false; |
| 116 } |
| 117 |
| 118 /// Process the potentially native [method]. Adds information from metadata |
| 119 /// attributes. Returns `true` of [method] is native. |
| 120 bool _processMethodAnnotations(Element method) { |
| 121 if (_compiler.serialization.isDeserialized(method)) { |
| 122 return false; |
| 123 } |
| 124 if (_isNativeMethod(method)) { |
| 125 if (method.isStatic) { |
| 126 _setNativeNameForStaticMethod(method); |
| 127 } else { |
| 128 _setNativeName(method); |
| 129 } |
| 130 return true; |
| 131 } |
| 132 return false; |
| 133 } |
| 134 |
| 135 /// Sets the native name of [element], either from an annotation, or |
| 136 /// defaulting to the Dart name. |
| 137 void _setNativeName(MemberElement element) { |
| 138 String name = _findJsNameFromAnnotation(element); |
| 139 if (name == null) name = element.name; |
| 140 _nativeDataBuilder.setNativeMemberName(element, name); |
| 141 } |
| 142 |
| 143 /// Sets the native name of the static native method [element], using the |
| 144 /// following rules: |
| 145 /// 1. If [element] has a @JSName annotation that is an identifier, qualify |
| 146 /// that identifier to the @Native name of the enclosing class |
| 147 /// 2. If [element] has a @JSName annotation that is not an identifier, |
| 148 /// use the declared @JSName as the expression |
| 149 /// 3. If [element] does not have a @JSName annotation, qualify the name of |
| 150 /// the method with the @Native name of the enclosing class. |
| 151 void _setNativeNameForStaticMethod(MethodElement element) { |
| 152 String name = _findJsNameFromAnnotation(element); |
| 153 if (name == null) name = element.name; |
| 154 if (_isIdentifier(name)) { |
| 155 List<String> nativeNames = |
| 156 _nativeDataBuilder.getNativeTagsOfClassRaw(element.enclosingClass); |
| 157 if (nativeNames.length != 1) { |
| 158 _reporter.internalError( |
| 159 element, |
| 160 'Unable to determine a native name for the enclosing class, ' |
| 161 'options: $nativeNames'); |
| 162 } |
| 163 _nativeDataBuilder.setNativeMemberName( |
| 164 element, '${nativeNames[0]}.$name'); |
| 165 } else { |
| 166 _nativeDataBuilder.setNativeMemberName(element, name); |
| 167 } |
| 168 } |
| 169 |
| 170 bool _isIdentifier(String s) => _identifier.hasMatch(s); |
| 171 |
| 172 bool _isNativeMethod(FunctionElementX element) { |
| 173 if (!_backend.canLibraryUseNative(element.library)) return false; |
| 174 // Native method? |
| 175 return _reporter.withCurrentElement(element, () { |
| 176 Node node = element.parseNode(_compiler.resolution.parsingContext); |
| 177 if (node is! FunctionExpression) return false; |
| 178 FunctionExpression functionExpression = node; |
| 179 node = functionExpression.body; |
| 180 Token token = node.getBeginToken(); |
| 181 if (identical(token.stringValue, 'native')) return true; |
| 182 return false; |
| 183 }); |
| 184 } |
| 185 |
| 186 /// Returns the JSName annotation string or `null` if no JSName annotation is |
| 187 /// present. |
| 188 String _findJsNameFromAnnotation(Element element) { |
| 189 String name = null; |
| 190 ClassElement annotationClass = _backend.helpers.annotationJSNameClass; |
| 191 for (MetadataAnnotation annotation in element.implementation.metadata) { |
| 192 annotation.ensureResolved(_compiler.resolution); |
| 193 ConstantValue value = |
| 194 _compiler.constants.getConstantValue(annotation.constant); |
| 195 if (!value.isConstructedObject) continue; |
| 196 ConstructedConstantValue constructedObject = value; |
| 197 if (constructedObject.type.element != annotationClass) continue; |
| 198 |
| 199 Iterable<ConstantValue> fields = constructedObject.fields.values; |
| 200 // TODO(sra): Better validation of the constant. |
| 201 if (fields.length != 1 || fields.single is! StringConstantValue) { |
| 202 _reporter.internalError( |
| 203 annotation, 'Annotations needs one string: ${annotation}'); |
| 204 } |
| 205 StringConstantValue specStringConstant = fields.single; |
| 206 String specString = specStringConstant.toDartString().slowToString(); |
| 207 if (name == null) { |
| 208 name = specString; |
| 209 } else { |
| 210 _reporter.internalError( |
| 211 annotation, 'Too many JSName annotations: ${annotation}'); |
| 212 } |
| 213 } |
| 214 return name; |
| 215 } |
| 216 } |
| 217 |
| 218 /// Check whether [cls] has a `@Native(...)` annotation, and if so, set its |
| 219 /// native name from the annotation. |
| 220 checkNativeAnnotation(Compiler compiler, ClassElement cls, |
| 221 NativeClassDataBuilder nativeClassDataBuilder) { |
| 222 EagerAnnotationHandler.checkAnnotation( |
| 223 compiler, cls, new NativeAnnotationHandler(nativeClassDataBuilder)); |
| 224 } |
| 225 |
| 226 /// Annotation handler for pre-resolution detection of `@Native(...)` |
| 227 /// annotations. |
| 228 class NativeAnnotationHandler extends EagerAnnotationHandler<String> { |
| 229 final NativeClassDataBuilder _nativeClassDataBuilder; |
| 230 |
| 231 NativeAnnotationHandler(this._nativeClassDataBuilder); |
| 232 |
| 233 String getNativeAnnotation(MetadataAnnotationX annotation) { |
| 234 if (annotation.beginToken != null && |
| 235 annotation.beginToken.next.value == 'Native') { |
| 236 // Skipping '@', 'Native', and '('. |
| 237 Token argument = annotation.beginToken.next.next.next; |
| 238 if (argument is StringToken) { |
| 239 return argument.value; |
| 240 } |
| 241 } |
| 242 return null; |
| 243 } |
| 244 |
| 245 String apply( |
| 246 Compiler compiler, Element element, MetadataAnnotation annotation) { |
| 247 if (element.isClass) { |
| 248 String native = getNativeAnnotation(annotation); |
| 249 if (native != null) { |
| 250 _nativeClassDataBuilder.setNativeClassTagInfo(element, native); |
| 251 return native; |
| 252 } |
| 253 } |
| 254 return null; |
| 255 } |
| 256 |
| 257 void validate(Compiler compiler, Element element, |
| 258 MetadataAnnotation annotation, ConstantValue constant) { |
| 259 ResolutionDartType annotationType = |
| 260 constant.getType(compiler.commonElements); |
| 261 if (annotationType.element != |
| 262 compiler.backend.helpers.nativeAnnotationClass) { |
| 263 DiagnosticReporter reporter = compiler.reporter; |
| 264 reporter.internalError(annotation, 'Invalid @Native(...) annotation.'); |
| 265 } |
| 266 } |
| 267 } |
| 268 |
| 269 void checkJsInteropClassAnnotations(Compiler compiler, LibraryElement library, |
| 270 NativeClassDataBuilder nativeClassDataBuilder) { |
| 271 bool checkJsInteropAnnotation(Element element) { |
| 272 return EagerAnnotationHandler.checkAnnotation( |
| 273 compiler, element, const JsInteropAnnotationHandler()); |
| 274 } |
| 275 |
| 276 if (checkJsInteropAnnotation(library)) { |
| 277 nativeClassDataBuilder.markAsJsInteropLibrary(library); |
| 278 } |
| 279 library.forEachLocalMember((Element element) { |
| 280 if (element.isClass) { |
| 281 if (checkJsInteropAnnotation(element)) { |
| 282 nativeClassDataBuilder.markAsJsInteropClass(element); |
| 283 } |
| 284 } |
| 285 }); |
| 286 } |
| 287 |
| 288 bool checkJsInteropMemberAnnotations(Compiler compiler, MemberElement element, |
| 289 NativeDataBuilder nativeDataBuilder) { |
| 290 bool isJsInterop = EagerAnnotationHandler.checkAnnotation( |
| 291 compiler, element, const JsInteropAnnotationHandler()); |
| 292 if (isJsInterop) { |
| 293 nativeDataBuilder.markAsJsInteropMember(element); |
| 294 } |
| 295 return isJsInterop; |
| 296 } |
| 297 |
| 298 /// Annotation handler for pre-resolution detection of `@JS(...)` |
| 299 /// annotations. |
| 300 class JsInteropAnnotationHandler implements EagerAnnotationHandler<bool> { |
| 301 const JsInteropAnnotationHandler(); |
| 302 |
| 303 bool hasJsNameAnnotation(MetadataAnnotationX annotation) => |
| 304 annotation.beginToken != null && annotation.beginToken.next.value == 'JS'; |
| 305 |
| 306 bool apply( |
| 307 Compiler compiler, Element element, MetadataAnnotation annotation) { |
| 308 return hasJsNameAnnotation(annotation); |
| 309 } |
| 310 |
| 311 @override |
| 312 void validate(Compiler compiler, Element element, |
| 313 MetadataAnnotation annotation, ConstantValue constant) { |
| 314 JavaScriptBackend backend = compiler.backend; |
| 315 ResolutionDartType type = constant.getType(compiler.commonElements); |
| 316 if (type.element != backend.helpers.jsAnnotationClass) { |
| 317 compiler.reporter |
| 318 .internalError(annotation, 'Invalid @JS(...) annotation.'); |
| 319 } |
| 320 } |
| 321 |
| 322 bool get defaultResult => false; |
| 323 } |
OLD | NEW |