| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'package:front_end/src/fasta/scanner.dart' | 5 import 'package:front_end/src/fasta/scanner.dart' |
| 6 show BeginGroupToken, StringToken, Token; | 6 show BeginGroupToken, StringToken, Token; |
| 7 import 'package:front_end/src/fasta/scanner.dart' as Tokens show EOF_TOKEN; | 7 import 'package:front_end/src/fasta/scanner.dart' as Tokens show EOF_TOKEN; |
| 8 | 8 |
| 9 import '../common.dart'; | 9 import '../common.dart'; |
| 10 import '../common_elements.dart' show CommonElements, ElementEnvironment; | 10 import '../common_elements.dart' show CommonElements, ElementEnvironment; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 MethodElement; | 22 MethodElement; |
| 23 import '../elements/entities.dart'; | 23 import '../elements/entities.dart'; |
| 24 import '../elements/modelx.dart' show FunctionElementX, MetadataAnnotationX; | 24 import '../elements/modelx.dart' show FunctionElementX, MetadataAnnotationX; |
| 25 import '../elements/resolution_types.dart' show ResolutionDartType; | 25 import '../elements/resolution_types.dart' show ResolutionDartType; |
| 26 import '../js_backend/js_backend.dart'; | 26 import '../js_backend/js_backend.dart'; |
| 27 import '../js_backend/native_data.dart'; | 27 import '../js_backend/native_data.dart'; |
| 28 import '../patch_parser.dart'; | 28 import '../patch_parser.dart'; |
| 29 import '../tree/tree.dart'; | 29 import '../tree/tree.dart'; |
| 30 import 'behavior.dart'; | 30 import 'behavior.dart'; |
| 31 | 31 |
| 32 /// Interface for computing native members. |
| 33 abstract class NativeMemberResolver { |
| 34 /// Computes whether [element] is native or JsInterop and, if so, registers |
| 35 /// its [NativeBehavior]s to [registry]. |
| 36 void resolveNativeMember(MemberEntity element, [NativeRegistry registry]); |
| 37 } |
| 38 |
| 32 /// Interface for computing native members and [NativeBehavior]s in member code | 39 /// Interface for computing native members and [NativeBehavior]s in member code |
| 33 /// based on the AST. | 40 /// based on the AST. |
| 34 abstract class NativeDataResolver { | 41 abstract class NativeDataResolver implements NativeMemberResolver { |
| 35 /// Returns `true` if [element] is a JsInterop member. | 42 /// Returns `true` if [element] is a JsInterop member. |
| 36 bool isJsInteropMember(MemberElement element); | 43 bool isJsInteropMember(MemberElement element); |
| 37 | 44 |
| 38 /// Computes whether [element] is native or JsInterop and, if so, registers | |
| 39 /// its [NativeBehavior]s to [registry]. | |
| 40 void resolveNativeMember(MemberElement element, NativeRegistry registry); | |
| 41 | |
| 42 /// Computes the [NativeBehavior] for a `JS` call, which can be an | 45 /// Computes the [NativeBehavior] for a `JS` call, which can be an |
| 43 /// instantiation point for types. | 46 /// instantiation point for types. |
| 44 /// | 47 /// |
| 45 /// For example, the following code instantiates and returns native classes | 48 /// For example, the following code instantiates and returns native classes |
| 46 /// that are `_DOMWindowImpl` or a subtype. | 49 /// that are `_DOMWindowImpl` or a subtype. |
| 47 /// | 50 /// |
| 48 /// JS('_DOMWindowImpl', 'window') | 51 /// JS('_DOMWindowImpl', 'window') |
| 49 /// | 52 /// |
| 50 NativeBehavior resolveJsCall(Send node, ForeignResolver resolver); | 53 NativeBehavior resolveJsCall(Send node, ForeignResolver resolver); |
| 51 | 54 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 62 /// Computes the [NativeBehavior] for a `JS_BUILTIN` call, which can be an | 65 /// Computes the [NativeBehavior] for a `JS_BUILTIN` call, which can be an |
| 63 /// instantiation point for types. | 66 /// instantiation point for types. |
| 64 /// | 67 /// |
| 65 /// For example, the following code instantiates and returns a String class | 68 /// For example, the following code instantiates and returns a String class |
| 66 /// | 69 /// |
| 67 /// JS_BUILTIN('String', 'int2string', 0) | 70 /// JS_BUILTIN('String', 'int2string', 0) |
| 68 /// | 71 /// |
| 69 NativeBehavior resolveJsBuiltinCall(Send node, ForeignResolver resolver); | 72 NativeBehavior resolveJsBuiltinCall(Send node, ForeignResolver resolver); |
| 70 } | 73 } |
| 71 | 74 |
| 72 class NativeDataResolverImpl implements NativeDataResolver { | 75 abstract class NativeMemberResolverBase implements NativeMemberResolver { |
| 73 static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'); | 76 static final RegExp _identifier = new RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'); |
| 74 | 77 |
| 75 final Compiler _compiler; | 78 ElementEnvironment get elementEnvironment; |
| 79 CommonElements get commonElements; |
| 80 NativeBasicData get nativeBasicData; |
| 81 NativeDataBuilder get nativeDataBuilder; |
| 76 | 82 |
| 77 NativeDataResolverImpl(this._compiler); | 83 bool isJsInteropMember(MemberEntity element); |
| 84 bool isNativeMethod(FunctionEntity element); |
| 78 | 85 |
| 79 JavaScriptBackend get _backend => _compiler.backend; | 86 NativeBehavior computeNativeMethodBehavior(FunctionEntity function, |
| 80 DiagnosticReporter get _reporter => _compiler.reporter; | 87 {bool isJsInterop}); |
| 81 NativeBasicData get _nativeBasicData => _backend.nativeBasicData; | 88 NativeBehavior computeNativeFieldLoadBehavior(FieldEntity field, |
| 82 NativeDataBuilder get _nativeDataBuilder => _backend.nativeDataBuilder; | 89 {bool isJsInterop}); |
| 90 NativeBehavior computeNativeFieldStoreBehavior(FieldEntity field); |
| 83 | 91 |
| 84 @override | 92 @override |
| 85 bool isJsInteropMember(MemberElement element) { | 93 void resolveNativeMember(MemberEntity element, [NativeRegistry registry]) { |
| 86 // TODO(johnniwinther): Avoid computing this twice for external function; | |
| 87 // once from JavaScriptBackendTarget.resolveExternalFunction and once | |
| 88 // through JavaScriptBackendTarget.resolveNativeMember. | |
| 89 bool isJsInterop = | |
| 90 checkJsInteropMemberAnnotations(_compiler, element, _nativeDataBuilder); | |
| 91 // TODO(johnniwinther): Avoid this duplication of logic from | |
| 92 // NativeData.isJsInterop. | |
| 93 if (!isJsInterop && element is MethodElement && element.isExternal) { | |
| 94 if (element.enclosingClass != null) { | |
| 95 isJsInterop = _nativeBasicData.isJsInteropClass(element.enclosingClass); | |
| 96 } else { | |
| 97 isJsInterop = _nativeBasicData.isJsInteropLibrary(element.library); | |
| 98 } | |
| 99 } | |
| 100 return isJsInterop; | |
| 101 } | |
| 102 | |
| 103 void resolveNativeMember(MemberElement element, NativeRegistry registry) { | |
| 104 bool isJsInterop = isJsInteropMember(element); | 94 bool isJsInterop = isJsInteropMember(element); |
| 105 if (element.isFunction || | 95 if (element.isFunction || |
| 106 element.isConstructor || | 96 element.isConstructor || |
| 107 element.isGetter || | 97 element.isGetter || |
| 108 element.isSetter) { | 98 element.isSetter) { |
| 109 MethodElement method = element; | 99 FunctionEntity method = element; |
| 110 bool isNative = _processMethodAnnotations(method); | 100 bool isNative = _processMethodAnnotations(method); |
| 111 if (isNative || isJsInterop) { | 101 if (isNative || isJsInterop) { |
| 112 NativeBehavior behavior = NativeBehavior | 102 NativeBehavior behavior = |
| 113 .ofMethodElement(method, _compiler, isJsInterop: isJsInterop); | 103 computeNativeMethodBehavior(method, isJsInterop: isJsInterop); |
| 114 _nativeDataBuilder.setNativeMethodBehavior(method, behavior); | 104 nativeDataBuilder.setNativeMethodBehavior(method, behavior); |
| 115 registry.registerNativeData(behavior); | 105 registry?.registerNativeData(behavior); |
| 116 } | 106 } |
| 117 } else if (element.isField) { | 107 } else if (element.isField) { |
| 118 FieldElement field = element; | 108 FieldEntity field = element; |
| 119 bool isNative = _processFieldAnnotations(field); | 109 bool isNative = _processFieldAnnotations(field); |
| 120 if (isNative || isJsInterop) { | 110 if (isNative || isJsInterop) { |
| 121 NativeBehavior fieldLoadBehavior = NativeBehavior | 111 NativeBehavior fieldLoadBehavior = |
| 122 .ofFieldElementLoad(field, _compiler, isJsInterop: isJsInterop); | 112 computeNativeFieldLoadBehavior(field, isJsInterop: isJsInterop); |
| 123 NativeBehavior fieldStoreBehavior = | 113 NativeBehavior fieldStoreBehavior = |
| 124 NativeBehavior.ofFieldElementStore(field, _compiler); | 114 computeNativeFieldStoreBehavior(field); |
| 125 _nativeDataBuilder.setNativeFieldLoadBehavior(field, fieldLoadBehavior); | 115 nativeDataBuilder.setNativeFieldLoadBehavior(field, fieldLoadBehavior); |
| 126 _nativeDataBuilder.setNativeFieldStoreBehavior( | 116 nativeDataBuilder.setNativeFieldStoreBehavior( |
| 127 field, fieldStoreBehavior); | 117 field, fieldStoreBehavior); |
| 128 | 118 |
| 129 // TODO(sra): Process fields for storing separately. | 119 // TODO(sra): Process fields for storing separately. |
| 130 // We have to handle both loading and storing to the field because we | 120 // We have to handle both loading and storing to the field because we |
| 131 // only get one look at each member and there might be a load or store | 121 // only get one look at each member and there might be a load or store |
| 132 // we have not seen yet. | 122 // we have not seen yet. |
| 133 registry.registerNativeData(fieldLoadBehavior); | 123 registry?.registerNativeData(fieldLoadBehavior); |
| 134 registry.registerNativeData(fieldStoreBehavior); | 124 registry?.registerNativeData(fieldStoreBehavior); |
| 135 } | 125 } |
| 136 } | 126 } |
| 137 } | 127 } |
| 138 | 128 |
| 139 /// Process the potentially native [field]. Adds information from metadata | 129 /// Process the potentially native [field]. Adds information from metadata |
| 140 /// attributes. Returns `true` of [method] is native. | 130 /// attributes. Returns `true` of [method] is native. |
| 141 bool _processFieldAnnotations(Element element) { | 131 bool _processFieldAnnotations(FieldEntity element) { |
| 142 if (_compiler.serialization.isDeserialized(element)) { | |
| 143 return false; | |
| 144 } | |
| 145 if (element.isInstanceMember && | 132 if (element.isInstanceMember && |
| 146 _backend.nativeBasicData.isNativeClass(element.enclosingClass)) { | 133 nativeBasicData.isNativeClass(element.enclosingClass)) { |
| 147 // Exclude non-instance (static) fields - they are not really native and | 134 // Exclude non-instance (static) fields - they are not really native and |
| 148 // are compiled as isolate globals. Access of a property of a constructor | 135 // are compiled as isolate globals. Access of a property of a constructor |
| 149 // function or a non-method property in the prototype chain, must be coded | 136 // function or a non-method property in the prototype chain, must be coded |
| 150 // using a JS-call. | 137 // using a JS-call. |
| 151 _setNativeName(element); | 138 _setNativeName(element); |
| 152 return true; | 139 return true; |
| 153 } | 140 } |
| 154 return false; | 141 return false; |
| 155 } | 142 } |
| 156 | 143 |
| 157 /// Process the potentially native [method]. Adds information from metadata | 144 /// Process the potentially native [method]. Adds information from metadata |
| 158 /// attributes. Returns `true` of [method] is native. | 145 /// attributes. Returns `true` of [method] is native. |
| 159 bool _processMethodAnnotations(Element method) { | 146 bool _processMethodAnnotations(FunctionEntity method) { |
| 160 if (_compiler.serialization.isDeserialized(method)) { | 147 if (isNativeMethod(method)) { |
| 161 return false; | |
| 162 } | |
| 163 if (_isNativeMethod(method)) { | |
| 164 if (method.isStatic) { | 148 if (method.isStatic) { |
| 165 _setNativeNameForStaticMethod(method); | 149 _setNativeNameForStaticMethod(method); |
| 166 } else { | 150 } else { |
| 167 _setNativeName(method); | 151 _setNativeName(method); |
| 168 } | 152 } |
| 169 return true; | 153 return true; |
| 170 } | 154 } |
| 171 return false; | 155 return false; |
| 172 } | 156 } |
| 173 | 157 |
| 174 /// Sets the native name of [element], either from an annotation, or | 158 /// Sets the native name of [element], either from an annotation, or |
| 175 /// defaulting to the Dart name. | 159 /// defaulting to the Dart name. |
| 176 void _setNativeName(MemberElement element) { | 160 void _setNativeName(MemberEntity element) { |
| 177 String name = _findJsNameFromAnnotation(element); | 161 String name = _findJsNameFromAnnotation(element); |
| 178 if (name == null) name = element.name; | 162 if (name == null) name = element.name; |
| 179 _nativeDataBuilder.setNativeMemberName(element, name); | 163 nativeDataBuilder.setNativeMemberName(element, name); |
| 180 } | 164 } |
| 181 | 165 |
| 182 /// Sets the native name of the static native method [element], using the | 166 /// Sets the native name of the static native method [element], using the |
| 183 /// following rules: | 167 /// following rules: |
| 184 /// 1. If [element] has a @JSName annotation that is an identifier, qualify | 168 /// 1. If [element] has a @JSName annotation that is an identifier, qualify |
| 185 /// that identifier to the @Native name of the enclosing class | 169 /// that identifier to the @Native name of the enclosing class |
| 186 /// 2. If [element] has a @JSName annotation that is not an identifier, | 170 /// 2. If [element] has a @JSName annotation that is not an identifier, |
| 187 /// use the declared @JSName as the expression | 171 /// use the declared @JSName as the expression |
| 188 /// 3. If [element] does not have a @JSName annotation, qualify the name of | 172 /// 3. If [element] does not have a @JSName annotation, qualify the name of |
| 189 /// the method with the @Native name of the enclosing class. | 173 /// the method with the @Native name of the enclosing class. |
| 190 void _setNativeNameForStaticMethod(MethodElement element) { | 174 void _setNativeNameForStaticMethod(FunctionEntity element) { |
| 191 String name = _findJsNameFromAnnotation(element); | 175 String name = _findJsNameFromAnnotation(element); |
| 192 if (name == null) name = element.name; | 176 if (name == null) name = element.name; |
| 193 if (_isIdentifier(name)) { | 177 if (_isIdentifier(name)) { |
| 194 List<String> nativeNames = | 178 List<String> nativeNames = |
| 195 _nativeBasicData.getNativeTagsOfClass(element.enclosingClass); | 179 nativeBasicData.getNativeTagsOfClass(element.enclosingClass); |
| 196 if (nativeNames.length != 1) { | 180 if (nativeNames.length != 1) { |
| 197 _reporter.internalError( | 181 throw new SpannableAssertionFailure( |
| 198 element, | 182 element, |
| 199 'Unable to determine a native name for the enclosing class, ' | 183 'Unable to determine a native name for the enclosing class, ' |
| 200 'options: $nativeNames'); | 184 'options: $nativeNames'); |
| 201 } | 185 } |
| 202 _nativeDataBuilder.setNativeMemberName( | 186 nativeDataBuilder.setNativeMemberName(element, '${nativeNames[0]}.$name'); |
| 203 element, '${nativeNames[0]}.$name'); | |
| 204 } else { | 187 } else { |
| 205 _nativeDataBuilder.setNativeMemberName(element, name); | 188 nativeDataBuilder.setNativeMemberName(element, name); |
| 206 } | 189 } |
| 207 } | 190 } |
| 208 | 191 |
| 209 bool _isIdentifier(String s) => _identifier.hasMatch(s); | 192 bool _isIdentifier(String s) => _identifier.hasMatch(s); |
| 210 | 193 |
| 211 bool _isNativeMethod(FunctionElementX element) { | 194 /// Returns the JSName annotation string or `null` if no JSName annotation is |
| 195 /// present. |
| 196 String _findJsNameFromAnnotation(MemberEntity element) { |
| 197 String jsName = null; |
| 198 for (ConstantValue value in elementEnvironment.getMemberMetadata(element)) { |
| 199 String name = readAnnotationName( |
| 200 element, value, commonElements.annotationJSNameClass); |
| 201 if (jsName == null) { |
| 202 jsName = name; |
| 203 } else if (name != null) { |
| 204 throw new SpannableAssertionFailure( |
| 205 element, 'Too many JSName annotations: ${value.toDartText()}'); |
| 206 } |
| 207 } |
| 208 return jsName; |
| 209 } |
| 210 } |
| 211 |
| 212 class NativeDataResolverImpl extends NativeMemberResolverBase |
| 213 implements NativeDataResolver { |
| 214 final Compiler _compiler; |
| 215 |
| 216 NativeDataResolverImpl(this._compiler); |
| 217 |
| 218 JavaScriptBackend get _backend => _compiler.backend; |
| 219 DiagnosticReporter get _reporter => _compiler.reporter; |
| 220 ElementEnvironment get elementEnvironment => _compiler.elementEnvironment; |
| 221 CommonElements get commonElements => _compiler.commonElements; |
| 222 NativeBasicData get nativeBasicData => _backend.nativeBasicData; |
| 223 NativeDataBuilder get nativeDataBuilder => _backend.nativeDataBuilder; |
| 224 |
| 225 @override |
| 226 bool isJsInteropMember(MemberElement element) { |
| 227 // TODO(johnniwinther): Avoid computing this twice for external function; |
| 228 // once from JavaScriptBackendTarget.resolveExternalFunction and once |
| 229 // through JavaScriptBackendTarget.resolveNativeMember. |
| 230 bool isJsInterop = |
| 231 checkJsInteropMemberAnnotations(_compiler, element, nativeDataBuilder); |
| 232 // TODO(johnniwinther): Avoid this duplication of logic from |
| 233 // NativeData.isJsInterop. |
| 234 if (!isJsInterop && element is MethodElement && element.isExternal) { |
| 235 if (element.enclosingClass != null) { |
| 236 isJsInterop = nativeBasicData.isJsInteropClass(element.enclosingClass); |
| 237 } else { |
| 238 isJsInterop = nativeBasicData.isJsInteropLibrary(element.library); |
| 239 } |
| 240 } |
| 241 return isJsInterop; |
| 242 } |
| 243 |
| 244 @override |
| 245 NativeBehavior computeNativeMethodBehavior(MethodElement function, |
| 246 {bool isJsInterop}) { |
| 247 return NativeBehavior.ofMethodElement(function, _compiler, |
| 248 isJsInterop: isJsInterop); |
| 249 } |
| 250 |
| 251 @override |
| 252 NativeBehavior computeNativeFieldLoadBehavior(FieldElement field, |
| 253 {bool isJsInterop}) { |
| 254 return NativeBehavior.ofFieldElementLoad(field, _compiler, |
| 255 isJsInterop: isJsInterop); |
| 256 } |
| 257 |
| 258 @override |
| 259 NativeBehavior computeNativeFieldStoreBehavior(FieldElement field) { |
| 260 return NativeBehavior.ofFieldElementStore(field, _compiler); |
| 261 } |
| 262 |
| 263 @override |
| 264 bool isNativeMethod(FunctionElementX element) { |
| 212 if (!_backend.canLibraryUseNative(element.library)) return false; | 265 if (!_backend.canLibraryUseNative(element.library)) return false; |
| 213 // Native method? | 266 // Native method? |
| 214 return _reporter.withCurrentElement(element, () { | 267 return _reporter.withCurrentElement(element, () { |
| 215 Node node = element.parseNode(_compiler.resolution.parsingContext); | 268 Node node = element.parseNode(_compiler.resolution.parsingContext); |
| 216 if (node is! FunctionExpression) return false; | 269 if (node is! FunctionExpression) return false; |
| 217 FunctionExpression functionExpression = node; | 270 FunctionExpression functionExpression = node; |
| 218 node = functionExpression.body; | 271 node = functionExpression.body; |
| 219 Token token = node.getBeginToken(); | 272 Token token = node.getBeginToken(); |
| 220 if (identical(token.stringValue, 'native')) return true; | 273 if (identical(token.stringValue, 'native')) return true; |
| 221 return false; | 274 return false; |
| 222 }); | 275 }); |
| 223 } | 276 } |
| 224 | 277 |
| 225 /// Returns the JSName annotation string or `null` if no JSName annotation is | 278 @override |
| 226 /// present. | 279 bool _processMethodAnnotations(MethodElement method) { |
| 227 String _findJsNameFromAnnotation(Element element) { | 280 if (_compiler.serialization.isDeserialized(method)) { |
| 228 String jsName = null; | 281 return false; |
| 229 for (MetadataAnnotation annotation in element.implementation.metadata) { | |
| 230 annotation.ensureResolved(_compiler.resolution); | |
| 231 ConstantValue value = | |
| 232 _compiler.constants.getConstantValue(annotation.constant); | |
| 233 String name = readAnnotationName( | |
| 234 annotation, value, _compiler.commonElements.annotationJSNameClass); | |
| 235 if (jsName == null) { | |
| 236 jsName = name; | |
| 237 } else if (name != null) { | |
| 238 throw new SpannableAssertionFailure( | |
| 239 annotation, 'Too many JSName annotations: ${annotation}'); | |
| 240 } | |
| 241 } | 282 } |
| 242 return jsName; | 283 return super._processMethodAnnotations(method); |
| 243 } | 284 } |
| 244 | 285 |
| 245 @override | 286 @override |
| 287 bool _processFieldAnnotations(FieldElement element) { |
| 288 if (_compiler.serialization.isDeserialized(element)) { |
| 289 return false; |
| 290 } |
| 291 return super._processFieldAnnotations(element); |
| 292 } |
| 293 |
| 294 @override |
| 246 NativeBehavior resolveJsCall(Send node, ForeignResolver resolver) { | 295 NativeBehavior resolveJsCall(Send node, ForeignResolver resolver) { |
| 247 return NativeBehavior.ofJsCallSend(node, _reporter, | 296 return NativeBehavior.ofJsCallSend(node, _reporter, |
| 248 _compiler.parsingContext, _compiler.commonElements, resolver); | 297 _compiler.parsingContext, _compiler.commonElements, resolver); |
| 249 } | 298 } |
| 250 | 299 |
| 251 @override | 300 @override |
| 252 NativeBehavior resolveJsEmbeddedGlobalCall( | 301 NativeBehavior resolveJsEmbeddedGlobalCall( |
| 253 Send node, ForeignResolver resolver) { | 302 Send node, ForeignResolver resolver) { |
| 254 return NativeBehavior.ofJsEmbeddedGlobalCallSend( | 303 return NativeBehavior.ofJsEmbeddedGlobalCallSend( |
| 255 node, _reporter, _compiler.commonElements, resolver); | 304 node, _reporter, _compiler.commonElements, resolver); |
| (...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 566 | 615 |
| 567 Iterable<ConstantValue> fields = constructedObject.fields.values; | 616 Iterable<ConstantValue> fields = constructedObject.fields.values; |
| 568 // TODO(sra): Better validation of the constant. | 617 // TODO(sra): Better validation of the constant. |
| 569 if (fields.length != 1 || fields.single is! StringConstantValue) { | 618 if (fields.length != 1 || fields.single is! StringConstantValue) { |
| 570 throw new SpannableAssertionFailure( | 619 throw new SpannableAssertionFailure( |
| 571 spannable, 'Annotations needs one string: ${value.toStructuredText()}'); | 620 spannable, 'Annotations needs one string: ${value.toStructuredText()}'); |
| 572 } | 621 } |
| 573 StringConstantValue specStringConstant = fields.single; | 622 StringConstantValue specStringConstant = fields.single; |
| 574 return specStringConstant.primitiveValue; | 623 return specStringConstant.primitiveValue; |
| 575 } | 624 } |
| OLD | NEW |