| 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 part of native; | 5 part of native; |
| 6 | 6 |
| 7 /// This class is a temporary work-around until we get a more powerful DartType. | 7 /// This class is a temporary work-around until we get a more powerful DartType. |
| 8 class SpecialType { | 8 class SpecialType { |
| 9 final String name; | 9 final String name; |
| 10 const SpecialType._(this.name); | 10 const SpecialType._(this.name); |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 169 {Iterable<String> validTags, | 169 {Iterable<String> validTags, |
| 170 void setSideEffects(SideEffects newEffects), | 170 void setSideEffects(SideEffects newEffects), |
| 171 void setThrows(NativeThrowBehavior throwKind), | 171 void setThrows(NativeThrowBehavior throwKind), |
| 172 void setIsAllocation(bool isAllocation), | 172 void setIsAllocation(bool isAllocation), |
| 173 void setUseGvn(bool useGvn), | 173 void setUseGvn(bool useGvn), |
| 174 dynamic resolveType(String typeString), | 174 dynamic resolveType(String typeString), |
| 175 List typesReturned, | 175 List typesReturned, |
| 176 List typesInstantiated, | 176 List typesInstantiated, |
| 177 objectType, nullType}) { | 177 objectType, nullType}) { |
| 178 | 178 |
| 179 |
| 180 bool seenError = false; |
| 181 |
| 182 void reportError(String message) { |
| 183 seenError = true; |
| 184 listener.reportError(spannable, MessageKind.GENERIC, {'text': message}); |
| 185 } |
| 186 |
| 179 const List<String> knownTags = const [ | 187 const List<String> knownTags = const [ |
| 180 'creates', 'returns', 'depends', 'effects', | 188 'creates', 'returns', 'depends', 'effects', |
| 181 'throws', 'gvn', 'new']; | 189 'throws', 'gvn', 'new']; |
| 182 | 190 |
| 183 /// Resolve a type string of one of the three forms: | 191 /// Resolve a type string of one of the three forms: |
| 184 /// * 'void' - in which case [onVoid] is called, | 192 /// * 'void' - in which case [onVoid] is called, |
| 185 /// * '' or 'var' - in which case [onVar] is called, | 193 /// * '' or 'var' - in which case [onVar] is called, |
| 186 /// * 'T1|...|Tn' - in which case [onType] is called for each resolved Ti. | 194 /// * 'T1|...|Tn' - in which case [onType] is called for each resolved Ti. |
| 187 void resolveTypesString(String typesString, | 195 void resolveTypesString(String typesString, |
| 188 {onVoid(), onVar(), onType(type)}) { | 196 {onVoid(), onVar(), onType(type)}) { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 | 231 |
| 224 assert(validTags == null || | 232 assert(validTags == null || |
| 225 (validTags.toSet()..removeAll(validTags)).isEmpty); | 233 (validTags.toSet()..removeAll(validTags)).isEmpty); |
| 226 if (validTags == null) validTags = knownTags; | 234 if (validTags == null) validTags = knownTags; |
| 227 | 235 |
| 228 Map<String, String> values = <String, String>{}; | 236 Map<String, String> values = <String, String>{}; |
| 229 | 237 |
| 230 for (String spec in specs) { | 238 for (String spec in specs) { |
| 231 List<String> tagAndValue = spec.split(':'); | 239 List<String> tagAndValue = spec.split(':'); |
| 232 if (tagAndValue.length != 2) { | 240 if (tagAndValue.length != 2) { |
| 233 listener.internalError(spannable, | 241 reportError("Invalid <tag>:<value> pair '$spec'."); |
| 234 "Invalid <tag>:<value> pair '$spec'."); | 242 continue; |
| 235 } | 243 } |
| 236 String tag = tagAndValue[0].trim(); | 244 String tag = tagAndValue[0].trim(); |
| 237 String value = tagAndValue[1].trim(); | 245 String value = tagAndValue[1].trim(); |
| 238 | 246 |
| 239 if (validTags.contains(tag)) { | 247 if (validTags.contains(tag)) { |
| 240 if (values[tag] == null) { | 248 if (values[tag] == null) { |
| 241 values[tag] = value; | 249 values[tag] = value; |
| 242 } else { | 250 } else { |
| 243 listener.internalError(spannable, "Duplicate tag '$tag'."); | 251 reportError("Duplicate tag '$tag'."); |
| 244 } | 252 } |
| 245 } else { | 253 } else { |
| 246 if (knownTags.contains(tag)) { | 254 if (knownTags.contains(tag)) { |
| 247 listener.internalError(spannable, "Tag '$tag' is not valid here."); | 255 reportError("Tag '$tag' is not valid here."); |
| 248 } else { | 256 } else { |
| 249 listener.internalError(spannable, "Unknown tag '$tag'."); | 257 reportError("Unknown tag '$tag'."); |
| 250 } | 258 } |
| 251 } | 259 } |
| 252 } | 260 } |
| 253 | 261 |
| 254 // Enum-like tags are looked up in a map. True signature is: | 262 // Enum-like tags are looked up in a map. True signature is: |
| 255 // | 263 // |
| 256 // T tagValueLookup<T>(String tag, Map<String, T> map); | 264 // T tagValueLookup<T>(String tag, Map<String, T> map); |
| 257 // | 265 // |
| 258 dynamic tagValueLookup(String tag, Map<String, dynamic> map) { | 266 dynamic tagValueLookup(String tag, Map<String, dynamic> map) { |
| 259 String tagString = values[tag]; | 267 String tagString = values[tag]; |
| 260 if (tagString == null) return null; | 268 if (tagString == null) return null; |
| 261 var value = map[tagString]; | 269 var value = map[tagString]; |
| 262 if (value == null) { | 270 if (value == null) { |
| 263 listener.internalError(spannable, | 271 reportError("Unknown '$tag' specification: '$tagString'."); |
| 264 "Unknown '$tag' specification: '$tagString'"); | |
| 265 } | 272 } |
| 266 return value; | 273 return value; |
| 267 } | 274 } |
| 268 | 275 |
| 269 String returns = values['returns']; | 276 String returns = values['returns']; |
| 270 if (returns != null) { | 277 if (returns != null) { |
| 271 resolveTypesString(returns, onVar: () { | 278 resolveTypesString(returns, onVar: () { |
| 272 typesReturned.add(objectType); | 279 typesReturned.add(objectType); |
| 273 typesReturned.add(nullType); | 280 typesReturned.add(nullType); |
| 274 }, onType: (type) { | 281 }, onType: (type) { |
| 275 typesReturned.add(type); | 282 typesReturned.add(type); |
| 276 }); | 283 }); |
| 277 } | 284 } |
| 278 | 285 |
| 279 String creates = values['creates']; | 286 String creates = values['creates']; |
| 280 if (creates != null) { | 287 if (creates != null) { |
| 281 resolveTypesString(creates, onVoid: () { | 288 resolveTypesString(creates, onVoid: () { |
| 282 listener.internalError(spannable, | 289 reportError("Invalid type string 'creates:$creates'"); |
| 283 "Invalid type string 'creates:$creates'"); | |
| 284 }, onVar: () { | 290 }, onVar: () { |
| 285 listener.internalError(spannable, | 291 reportError("Invalid type string 'creates:$creates'"); |
| 286 "Invalid type string 'creates:$creates'"); | |
| 287 }, onType: (type) { | 292 }, onType: (type) { |
| 288 typesInstantiated.add(type); | 293 typesInstantiated.add(type); |
| 289 }); | 294 }); |
| 290 } | 295 } |
| 291 | 296 |
| 292 const throwsOption = const <String, NativeThrowBehavior>{ | 297 const throwsOption = const <String, NativeThrowBehavior>{ |
| 293 'never': NativeThrowBehavior.NEVER, | 298 'never': NativeThrowBehavior.NEVER, |
| 294 'null(1)': NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS, | 299 'null(1)': NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS, |
| 295 'may': NativeThrowBehavior.MAY, | 300 'may': NativeThrowBehavior.MAY, |
| 296 'must': NativeThrowBehavior.MUST }; | 301 'must': NativeThrowBehavior.MUST }; |
| 297 | 302 |
| 298 const boolOptions = const<String, bool>{'true': true, 'false': false}; | 303 const boolOptions = const<String, bool>{'true': true, 'false': false}; |
| 299 | 304 |
| 300 SideEffects sideEffects = processEffects(listener, spannable, | 305 SideEffects sideEffects = processEffects(reportError, |
| 301 values['effects'], values['depends']); | 306 values['effects'], values['depends']); |
| 302 NativeThrowBehavior throwsKind = tagValueLookup('throws', throwsOption); | 307 NativeThrowBehavior throwsKind = tagValueLookup('throws', throwsOption); |
| 303 bool isAllocation = tagValueLookup('new', boolOptions); | 308 bool isAllocation = tagValueLookup('new', boolOptions); |
| 304 bool useGvn = tagValueLookup('gvn', boolOptions); | 309 bool useGvn = tagValueLookup('gvn', boolOptions); |
| 305 | 310 |
| 306 if (isAllocation == true && useGvn == true) { | 311 if (isAllocation == true && useGvn == true) { |
| 307 listener.internalError(spannable, "'new' and 'gvn' are incompatible"); | 312 reportError("'new' and 'gvn' are incompatible"); |
| 308 } | 313 } |
| 309 | 314 |
| 315 if (seenError) return; // Avoid callbacks. |
| 316 |
| 317 // TODO(sra): Simplify [throwBehavior] using [sideEffects]. |
| 318 |
| 310 if (sideEffects != null) setSideEffects(sideEffects); | 319 if (sideEffects != null) setSideEffects(sideEffects); |
| 311 if (throwsKind != null) setThrows(throwsKind); | 320 if (throwsKind != null) setThrows(throwsKind); |
| 312 if (isAllocation != null) setIsAllocation(isAllocation); | 321 if (isAllocation != null) setIsAllocation(isAllocation); |
| 313 if (useGvn != null) setUseGvn(useGvn); | 322 if (useGvn != null) setUseGvn(useGvn); |
| 314 } | 323 } |
| 315 | 324 |
| 316 static SideEffects processEffects( | 325 static SideEffects processEffects( |
| 317 DiagnosticListener listener, | 326 void reportError(String message), |
| 318 Spannable spannable, | |
| 319 String effects, | 327 String effects, |
| 320 String depends) { | 328 String depends) { |
| 321 | 329 |
| 322 if (effects == null && depends == null) return null; | 330 if (effects == null && depends == null) return null; |
| 323 | 331 |
| 324 if (effects == null || depends == null) { | 332 if (effects == null || depends == null) { |
| 325 listener.internalError(spannable, | 333 reportError("'effects' and 'depends' must occur together."); |
| 326 "Invalid JS spec string. " | |
| 327 "'effects' and 'depends' must occur together."); | |
| 328 return null; | 334 return null; |
| 329 } | 335 } |
| 330 | 336 |
| 331 SideEffects sideEffects = new SideEffects(); | 337 SideEffects sideEffects = new SideEffects(); |
| 332 if (effects == "none") { | 338 if (effects == "none") { |
| 333 sideEffects.clearAllSideEffects(); | 339 sideEffects.clearAllSideEffects(); |
| 334 } else if (effects == "all") { | 340 } else if (effects == "all") { |
| 335 // Don't do anything. | 341 // Don't do anything. |
| 336 } else { | 342 } else { |
| 337 List<String> splitEffects = effects.split(","); | 343 List<String> splitEffects = effects.split(","); |
| 338 if (splitEffects.isEmpty) { | 344 if (splitEffects.isEmpty) { |
| 339 listener.internalError(spannable, "Missing side-effect flag."); | 345 reportError("Missing side-effect flag."); |
| 340 } | 346 } |
| 341 for (String effect in splitEffects) { | 347 for (String effect in splitEffects) { |
| 342 switch (effect) { | 348 switch (effect) { |
| 343 case "no-index": | 349 case "no-index": |
| 344 sideEffects.clearChangesIndex(); | 350 sideEffects.clearChangesIndex(); |
| 345 break; | 351 break; |
| 346 case "no-instance": | 352 case "no-instance": |
| 347 sideEffects.clearChangesInstanceProperty(); | 353 sideEffects.clearChangesInstanceProperty(); |
| 348 break; | 354 break; |
| 349 case "no-static": | 355 case "no-static": |
| 350 sideEffects.clearChangesStaticProperty(); | 356 sideEffects.clearChangesStaticProperty(); |
| 351 break; | 357 break; |
| 352 default: | 358 default: |
| 353 listener.internalError(spannable, | 359 reportError("Unrecognized side-effect flag: '$effect'."); |
| 354 "Unrecognized side-effect flag: '$effect'."); | |
| 355 } | 360 } |
| 356 } | 361 } |
| 357 } | 362 } |
| 358 | 363 |
| 359 if (depends == "none") { | 364 if (depends == "none") { |
| 360 sideEffects.clearAllDependencies(); | 365 sideEffects.clearAllDependencies(); |
| 361 } else if (depends == "all") { | 366 } else if (depends == "all") { |
| 362 // Don't do anything. | 367 // Don't do anything. |
| 363 } else { | 368 } else { |
| 364 List<String> splitDependencies = depends.split(","); | 369 List<String> splitDependencies = depends.split(","); |
| 365 if (splitDependencies.isEmpty) { | 370 if (splitDependencies.isEmpty) { |
| 366 listener.internalError(spannable, | 371 reportError("Missing side-effect dependency flag."); |
| 367 "Missing side-effect dependency flag."); | |
| 368 } | 372 } |
| 369 for (String dependency in splitDependencies) { | 373 for (String dependency in splitDependencies) { |
| 370 switch (dependency) { | 374 switch (dependency) { |
| 371 case "no-index": | 375 case "no-index": |
| 372 sideEffects.clearDependsOnIndexStore(); | 376 sideEffects.clearDependsOnIndexStore(); |
| 373 break; | 377 break; |
| 374 case "no-instance": | 378 case "no-instance": |
| 375 sideEffects.clearDependsOnInstancePropertyStore(); | 379 sideEffects.clearDependsOnInstancePropertyStore(); |
| 376 break; | 380 break; |
| 377 case "no-static": | 381 case "no-static": |
| 378 sideEffects.clearDependsOnStaticPropertyStore(); | 382 sideEffects.clearDependsOnStaticPropertyStore(); |
| 379 break; | 383 break; |
| 380 default: | 384 default: |
| 381 listener.internalError(spannable, | 385 reportError("Unrecognized side-effect flag: '$dependency'."); |
| 382 "Unrecognized side-effect flag: '$dependency'."); | |
| 383 } | 386 } |
| 384 } | 387 } |
| 385 } | 388 } |
| 386 | 389 |
| 387 return sideEffects; | 390 return sideEffects; |
| 388 } | 391 } |
| 389 | 392 |
| 390 static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { | 393 static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { |
| 391 // The first argument of a JS-call is a string encoding various attributes | 394 // The first argument of a JS-call is a string encoding various attributes |
| 392 // of the code. | 395 // of the code. |
| 393 // | 396 // |
| 394 // 'Type1|Type2'. A union type. | 397 // 'Type1|Type2'. A union type. |
| 395 // '=Object'. A JavaScript Object, no subtype. | 398 // '=Object'. A JavaScript Object, no subtype. |
| 396 | 399 |
| 400 NativeBehavior behavior = new NativeBehavior(); |
| 401 |
| 397 var argNodes = jsCall.arguments; | 402 var argNodes = jsCall.arguments; |
| 398 if (argNodes.isEmpty) { | 403 if (argNodes.isEmpty || argNodes.tail.isEmpty) { |
| 399 compiler.internalError(jsCall, "JS expression has no type."); | 404 compiler.reportError(jsCall, MessageKind.GENERIC, |
| 405 {'text': "JS expression takes two or more arguments."}); |
| 406 return behavior; |
| 400 } | 407 } |
| 401 | 408 |
| 402 var code = argNodes.tail.head; | 409 var specArgument = argNodes.head; |
| 403 if (code is !StringNode || code.isInterpolation) { | 410 if (specArgument is !StringNode || specArgument.isInterpolation) { |
| 404 compiler.internalError(code, 'JS code must be a string literal.'); | 411 compiler.reportError(specArgument, MessageKind.GENERIC, |
| 412 {'text': "JS first argument must be a string literal."}); |
| 413 return behavior; |
| 405 } | 414 } |
| 406 | 415 |
| 407 LiteralString specLiteral = argNodes.head.asLiteralString(); | 416 var codeArgument = argNodes.tail.head; |
| 408 if (specLiteral == null) { | 417 if (codeArgument is !StringNode || codeArgument.isInterpolation) { |
| 409 // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It | 418 compiler.reportError(codeArgument, MessageKind.GENERIC, |
| 410 // is not very satisfactory because it does not work for void, dynamic. | 419 {'text': "JS second argument must be a string literal."}); |
| 411 compiler.internalError(argNodes.head, "Unexpected JS first argument."); | 420 return behavior; |
| 412 } | 421 } |
| 413 | 422 |
| 414 NativeBehavior behavior = new NativeBehavior(); | |
| 415 behavior.codeTemplate = | 423 behavior.codeTemplate = |
| 416 js.js.parseForeignJS(code.dartString.slowToString()); | 424 js.js.parseForeignJS(codeArgument.dartString.slowToString()); |
| 417 | 425 |
| 418 String specString = specLiteral.dartString.slowToString(); | 426 String specString = specArgument.dartString.slowToString(); |
| 419 | 427 |
| 420 dynamic resolveType(String typeString) { | 428 dynamic resolveType(String typeString) { |
| 421 return _parseType( | 429 return _parseType( |
| 422 typeString, | 430 typeString, |
| 423 compiler, | 431 compiler, |
| 424 (name) => resolver.resolveTypeFromString(specLiteral, name), | 432 (name) => resolver.resolveTypeFromString(specArgument, name), |
| 425 jsCall); | 433 specArgument); |
| 426 } | 434 } |
| 427 | 435 |
| 428 bool sideEffectsAreEncodedInSpecString = false; | 436 bool sideEffectsAreEncodedInSpecString = false; |
| 429 | 437 |
| 430 void setSideEffects(SideEffects newEffects) { | 438 void setSideEffects(SideEffects newEffects) { |
| 431 sideEffectsAreEncodedInSpecString = true; | 439 sideEffectsAreEncodedInSpecString = true; |
| 432 behavior.sideEffects.setTo(newEffects); | 440 behavior.sideEffects.setTo(newEffects); |
| 433 } | 441 } |
| 434 | 442 |
| 435 bool throwBehaviorFromSpecString = false; | 443 bool throwBehaviorFromSpecString = false; |
| 436 void setThrows(NativeThrowBehavior throwBehavior) { | 444 void setThrows(NativeThrowBehavior throwBehavior) { |
| 437 throwBehaviorFromSpecString = true; | 445 throwBehaviorFromSpecString = true; |
| 438 behavior.throwBehavior = throwBehavior; | 446 behavior.throwBehavior = throwBehavior; |
| 439 } | 447 } |
| 440 | 448 |
| 441 void setIsAllocation(bool isAllocation) { | 449 void setIsAllocation(bool isAllocation) { |
| 442 behavior.isAllocation = isAllocation; | 450 behavior.isAllocation = isAllocation; |
| 443 } | 451 } |
| 444 | 452 |
| 445 void setUseGvn(bool useGvn) { | 453 void setUseGvn(bool useGvn) { |
| 446 behavior.useGvn = useGvn; | 454 behavior.useGvn = useGvn; |
| 447 } | 455 } |
| 448 | 456 |
| 449 processSpecString(compiler, jsCall, | 457 processSpecString(compiler, specArgument, |
| 450 specString, | 458 specString, |
| 451 setSideEffects: setSideEffects, | 459 setSideEffects: setSideEffects, |
| 452 setThrows: setThrows, | 460 setThrows: setThrows, |
| 453 setIsAllocation: setIsAllocation, | 461 setIsAllocation: setIsAllocation, |
| 454 setUseGvn: setUseGvn, | 462 setUseGvn: setUseGvn, |
| 455 resolveType: resolveType, | 463 resolveType: resolveType, |
| 456 typesReturned: behavior.typesReturned, | 464 typesReturned: behavior.typesReturned, |
| 457 typesInstantiated: behavior.typesInstantiated, | 465 typesInstantiated: behavior.typesInstantiated, |
| 458 objectType: compiler.objectClass.computeType(compiler), | 466 objectType: compiler.objectClass.computeType(compiler), |
| 459 nullType: compiler.nullClass.computeType(compiler)); | 467 nullType: compiler.nullClass.computeType(compiler)); |
| 460 | 468 |
| 461 if (!sideEffectsAreEncodedInSpecString) { | 469 if (!sideEffectsAreEncodedInSpecString) { |
| 462 new SideEffectsVisitor(behavior.sideEffects) | 470 new SideEffectsVisitor(behavior.sideEffects) |
| 463 .visit(behavior.codeTemplate.ast); | 471 .visit(behavior.codeTemplate.ast); |
| 464 } | 472 } |
| 465 | 473 |
| 466 // TODO(sra): Simplify [throwBehavior] using [sideEffects]. | |
| 467 | |
| 468 return behavior; | 474 return behavior; |
| 469 } | 475 } |
| 470 | 476 |
| 471 static NativeBehavior ofJsEmbeddedGlobalCall(Send jsGlobalCall, | 477 static NativeBehavior ofJsEmbeddedGlobalCall(Send jsGlobalCall, |
| 472 Compiler compiler, | 478 Compiler compiler, |
| 473 resolver) { | 479 resolver) { |
| 474 // The first argument of a JS-embedded global call is a string encoding | 480 // The first argument of a JS-embedded global call is a string encoding |
| 475 // the type of the code. | 481 // the type of the code. |
| 476 // | 482 // |
| 477 // 'Type1|Type2'. A union type. | 483 // 'Type1|Type2'. A union type. |
| (...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 663 lookup(name), locationNodeOrElement) { | 669 lookup(name), locationNodeOrElement) { |
| 664 if (typeString == '=Object') return SpecialType.JsObject; | 670 if (typeString == '=Object') return SpecialType.JsObject; |
| 665 if (typeString == 'dynamic') { | 671 if (typeString == 'dynamic') { |
| 666 return const DynamicType(); | 672 return const DynamicType(); |
| 667 } | 673 } |
| 668 var type = lookup(typeString); | 674 var type = lookup(typeString); |
| 669 if (type != null) return type; | 675 if (type != null) return type; |
| 670 | 676 |
| 671 int index = typeString.indexOf('<'); | 677 int index = typeString.indexOf('<'); |
| 672 if (index < 1) { | 678 if (index < 1) { |
| 673 compiler.internalError( | 679 compiler.reportError( |
| 674 _errorNode(locationNodeOrElement, compiler), | 680 _errorNode(locationNodeOrElement, compiler), |
| 675 "Type '$typeString' not found."); | 681 MessageKind.GENERIC, |
| 682 {'text': "Type '$typeString' not found."}); |
| 683 return const DynamicType(); |
| 676 } | 684 } |
| 677 type = lookup(typeString.substring(0, index)); | 685 type = lookup(typeString.substring(0, index)); |
| 678 if (type != null) { | 686 if (type != null) { |
| 679 // TODO(sra): Parse type parameters. | 687 // TODO(sra): Parse type parameters. |
| 680 return type; | 688 return type; |
| 681 } | 689 } |
| 682 compiler.internalError( | 690 compiler.reportError( |
| 683 _errorNode(locationNodeOrElement, compiler), | 691 _errorNode(locationNodeOrElement, compiler), |
| 684 "Type '$typeString' not found."); | 692 MessageKind.GENERIC, |
| 685 return null; | 693 {'text': "Type '$typeString' not found."}); |
| 694 return const DynamicType(); |
| 686 } | 695 } |
| 687 | 696 |
| 688 static _errorNode(locationNodeOrElement, compiler) { | 697 static _errorNode(locationNodeOrElement, compiler) { |
| 689 if (locationNodeOrElement is Node) return locationNodeOrElement; | 698 if (locationNodeOrElement is Node) return locationNodeOrElement; |
| 690 return locationNodeOrElement.parseNode(compiler); | 699 return locationNodeOrElement.parseNode(compiler); |
| 691 } | 700 } |
| 692 } | 701 } |
| OLD | NEW |