| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 library fasta.source_library_builder; | 5 library fasta.source_library_builder; |
| 6 | 6 |
| 7 import 'package:kernel/ast.dart' show AsyncMarker, ProcedureKind; | 7 import 'package:kernel/ast.dart' show AsyncMarker, ProcedureKind; |
| 8 | 8 |
| 9 import '../combinator.dart' show Combinator; | 9 import '../combinator.dart' show Combinator; |
| 10 | 10 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 Scope, | 31 Scope, |
| 32 TypeBuilder, | 32 TypeBuilder, |
| 33 TypeDeclarationBuilder, | 33 TypeDeclarationBuilder, |
| 34 TypeVariableBuilder, | 34 TypeVariableBuilder, |
| 35 Unhandled; | 35 Unhandled; |
| 36 | 36 |
| 37 abstract class SourceLibraryBuilder<T extends TypeBuilder, R> | 37 abstract class SourceLibraryBuilder<T extends TypeBuilder, R> |
| 38 extends LibraryBuilder<T, R> { | 38 extends LibraryBuilder<T, R> { |
| 39 final SourceLoader loader; | 39 final SourceLoader loader; |
| 40 | 40 |
| 41 final DeclarationBuilder<T> libraryDeclaration = | 41 final DeclarationBuilder<T> libraryDeclaration; |
| 42 new DeclarationBuilder<T>(<String, Builder>{}, null); | |
| 43 | 42 |
| 44 final List<ConstructorReferenceBuilder> constructorReferences = | 43 final List<ConstructorReferenceBuilder> constructorReferences = |
| 45 <ConstructorReferenceBuilder>[]; | 44 <ConstructorReferenceBuilder>[]; |
| 46 | 45 |
| 47 final List<SourceLibraryBuilder<T, R>> parts = <SourceLibraryBuilder<T, R>>[]; | 46 final List<SourceLibraryBuilder<T, R>> parts = <SourceLibraryBuilder<T, R>>[]; |
| 48 | 47 |
| 49 final List<Import> imports = <Import>[]; | 48 final List<Import> imports = <Import>[]; |
| 50 | 49 |
| 51 final Map<String, Builder> exports = <String, Builder>{}; | 50 final Scope importScope; |
| 52 | |
| 53 final Scope scope = new Scope.top(); | |
| 54 | 51 |
| 55 final Uri fileUri; | 52 final Uri fileUri; |
| 56 | 53 |
| 57 final List<List> implementationBuilders = <List<List>>[]; | 54 final List<List> implementationBuilders = <List<List>>[]; |
| 58 | 55 |
| 59 String name; | 56 String name; |
| 60 | 57 |
| 61 String partOfName; | 58 String partOfName; |
| 62 | 59 |
| 63 Uri partOfUri; | 60 Uri partOfUri; |
| 64 | 61 |
| 65 List<MetadataBuilder> metadata; | 62 List<MetadataBuilder> metadata; |
| 66 | 63 |
| 67 /// The current declaration that is being built. When we start parsing a | 64 /// The current declaration that is being built. When we start parsing a |
| 68 /// declaration (class, method, and so on), we don't have enough information | 65 /// declaration (class, method, and so on), we don't have enough information |
| 69 /// to create a builder and this object records its members and types until, | 66 /// to create a builder and this object records its members and types until, |
| 70 /// for example, [addClass] is called. | 67 /// for example, [addClass] is called. |
| 71 DeclarationBuilder<T> currentDeclaration; | 68 DeclarationBuilder<T> currentDeclaration; |
| 72 | 69 |
| 73 bool canAddImplementationBuilders = false; | 70 bool canAddImplementationBuilders = false; |
| 74 | 71 |
| 75 SourceLibraryBuilder(this.loader, Uri fileUri) | 72 SourceLibraryBuilder(SourceLoader loader, Uri fileUri) |
| 76 : fileUri = fileUri, | 73 : this.fromScopes(loader, fileUri, new DeclarationBuilder<T>.library(), |
| 77 super(fileUri) { | 74 new Scope.top()); |
| 78 currentDeclaration = libraryDeclaration; | 75 |
| 79 } | 76 SourceLibraryBuilder.fromScopes( |
| 77 this.loader, this.fileUri, this.libraryDeclaration, this.importScope) |
| 78 : currentDeclaration = libraryDeclaration, |
| 79 super( |
| 80 fileUri, libraryDeclaration.toScope(importScope), new Scope.top()); |
| 80 | 81 |
| 81 Uri get uri; | 82 Uri get uri; |
| 82 | 83 |
| 83 bool get isPart => partOfName != null || partOfUri != null; | 84 bool get isPart => partOfName != null || partOfUri != null; |
| 84 | 85 |
| 85 Map<String, Builder> get members => libraryDeclaration.members; | |
| 86 | |
| 87 List<T> get types => libraryDeclaration.types; | 86 List<T> get types => libraryDeclaration.types; |
| 88 | 87 |
| 89 T addNamedType(String name, List<T> arguments, int charOffset); | 88 T addNamedType(String name, List<T> arguments, int charOffset); |
| 90 | 89 |
| 91 T addMixinApplication(T supertype, List<T> mixins, int charOffset); | 90 T addMixinApplication(T supertype, List<T> mixins, int charOffset); |
| 92 | 91 |
| 93 T addType(T type) { | 92 T addType(T type) { |
| 94 currentDeclaration.addType(type); | 93 currentDeclaration.addType(type); |
| 95 return type; | 94 return type; |
| 96 } | 95 } |
| 97 | 96 |
| 98 T addVoidType(int charOffset); | 97 T addVoidType(int charOffset); |
| 99 | 98 |
| 100 ConstructorReferenceBuilder addConstructorReference( | 99 ConstructorReferenceBuilder addConstructorReference( |
| 101 String name, List<T> typeArguments, String suffix, int charOffset) { | 100 String name, List<T> typeArguments, String suffix, int charOffset) { |
| 102 ConstructorReferenceBuilder ref = new ConstructorReferenceBuilder( | 101 ConstructorReferenceBuilder ref = new ConstructorReferenceBuilder( |
| 103 name, typeArguments, suffix, this, charOffset); | 102 name, typeArguments, suffix, this, charOffset); |
| 104 constructorReferences.add(ref); | 103 constructorReferences.add(ref); |
| 105 return ref; | 104 return ref; |
| 106 } | 105 } |
| 107 | 106 |
| 108 void beginNestedDeclaration(String name, {bool hasMembers}) { | 107 void beginNestedDeclaration(String name, {bool hasMembers: true}) { |
| 109 currentDeclaration = new DeclarationBuilder( | 108 currentDeclaration = currentDeclaration.createNested(name, hasMembers); |
| 110 <String, MemberBuilder>{}, name, currentDeclaration); | |
| 111 } | 109 } |
| 112 | 110 |
| 113 DeclarationBuilder<T> endNestedDeclaration() { | 111 DeclarationBuilder<T> endNestedDeclaration() { |
| 114 DeclarationBuilder<T> previous = currentDeclaration; | 112 DeclarationBuilder<T> previous = currentDeclaration; |
| 115 currentDeclaration = currentDeclaration.parent; | 113 currentDeclaration = currentDeclaration.parent; |
| 116 return previous; | 114 return previous; |
| 117 } | 115 } |
| 118 | 116 |
| 119 Uri resolve(String path) => uri.resolve(path); | 117 Uri resolve(String path) => uri.resolve(path); |
| 120 | 118 |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 227 int charOpenParenOffset, | 225 int charOpenParenOffset, |
| 228 int charEndOffset, | 226 int charEndOffset, |
| 229 String nativeMethodName); | 227 String nativeMethodName); |
| 230 | 228 |
| 231 FormalParameterBuilder addFormalParameter(List<MetadataBuilder> metadata, | 229 FormalParameterBuilder addFormalParameter(List<MetadataBuilder> metadata, |
| 232 int modifiers, T type, String name, bool hasThis, int charOffset); | 230 int modifiers, T type, String name, bool hasThis, int charOffset); |
| 233 | 231 |
| 234 TypeVariableBuilder addTypeVariable(String name, T bound, int charOffset); | 232 TypeVariableBuilder addTypeVariable(String name, T bound, int charOffset); |
| 235 | 233 |
| 236 Builder addBuilder(String name, Builder builder, int charOffset) { | 234 Builder addBuilder(String name, Builder builder, int charOffset) { |
| 237 if (name.indexOf(".") != -1 && name.indexOf("&") == -1) { | |
| 238 addCompileTimeError( | |
| 239 charOffset, | |
| 240 "Only constructors and factories can have" | |
| 241 " names containing a period ('.'): $name"); | |
| 242 } | |
| 243 // TODO(ahe): Set the parent correctly here. Could then change the | 235 // TODO(ahe): Set the parent correctly here. Could then change the |
| 244 // implementation of MemberBuilder.isTopLevel to test explicitly for a | 236 // implementation of MemberBuilder.isTopLevel to test explicitly for a |
| 245 // LibraryBuilder. | 237 // LibraryBuilder. |
| 246 if (currentDeclaration == libraryDeclaration) { | 238 if (currentDeclaration == libraryDeclaration) { |
| 247 if (builder is MemberBuilder) { | 239 if (builder is MemberBuilder) { |
| 248 builder.parent = this; | 240 builder.parent = this; |
| 249 } else if (builder is TypeDeclarationBuilder) { | 241 } else if (builder is TypeDeclarationBuilder) { |
| 250 builder.parent = this; | 242 builder.parent = this; |
| 251 } else if (builder is PrefixBuilder) { | 243 } else if (builder is PrefixBuilder) { |
| 252 assert(builder.parent == this); | 244 assert(builder.parent == this); |
| 253 } else { | 245 } else { |
| 254 return internalError("Unhandled: ${builder.runtimeType}"); | 246 return internalError("Unhandled: ${builder.runtimeType}"); |
| 255 } | 247 } |
| 256 } else { | 248 } else { |
| 257 assert(currentDeclaration.parent == libraryDeclaration); | 249 assert(currentDeclaration.parent == libraryDeclaration); |
| 258 } | 250 } |
| 259 Map<String, Builder> members = currentDeclaration.members; | 251 bool isConstructor = builder is ProcedureBuilder && |
| 252 (builder.isConstructor || builder.isFactory); |
| 253 Map<String, Builder> members = isConstructor |
| 254 ? currentDeclaration.constructors |
| 255 : (builder.isSetter |
| 256 ? currentDeclaration.setters |
| 257 : currentDeclaration.members); |
| 260 Builder existing = members[name]; | 258 Builder existing = members[name]; |
| 261 builder.next = existing; | 259 builder.next = existing; |
| 262 if (builder is PrefixBuilder && existing is PrefixBuilder) { | 260 if (builder is PrefixBuilder && existing is PrefixBuilder) { |
| 263 assert(existing.next == null); | 261 assert(existing.next == null); |
| 264 builder.exports.forEach((String name, Builder builder) { | 262 return existing |
| 265 Builder other = existing.exports.putIfAbsent(name, () => builder); | 263 ..exports.merge(builder.exports, |
| 266 if (other != builder) { | 264 (String name, Builder existing, Builder member) { |
| 267 existing.exports[name] = | 265 return buildAmbiguousBuilder(name, existing, member, charOffset); |
| 268 buildAmbiguousBuilder(name, other, builder, charOffset); | 266 }); |
| 269 } | |
| 270 }); | |
| 271 return existing; | |
| 272 } else if (isDuplicatedDefinition(existing, builder)) { | 267 } else if (isDuplicatedDefinition(existing, builder)) { |
| 273 addCompileTimeError(charOffset, "Duplicated definition of '$name'."); | 268 addCompileTimeError(charOffset, "Duplicated definition of '$name'."); |
| 274 } | 269 } |
| 275 return members[name] = builder; | 270 return members[name] = builder; |
| 276 } | 271 } |
| 277 | 272 |
| 278 bool isDuplicatedDefinition(Builder existing, Builder other) { | 273 bool isDuplicatedDefinition(Builder existing, Builder other) { |
| 279 if (existing == null) return false; | 274 if (existing == null) return false; |
| 280 Builder next = existing.next; | 275 Builder next = existing.next; |
| 281 if (next == null) { | 276 if (next == null) { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 306 } while (builder != null); | 301 } while (builder != null); |
| 307 }); | 302 }); |
| 308 for (List list in implementationBuilders) { | 303 for (List list in implementationBuilders) { |
| 309 String name = list[0]; | 304 String name = list[0]; |
| 310 Builder builder = list[1]; | 305 Builder builder = list[1]; |
| 311 int charOffset = list[2]; | 306 int charOffset = list[2]; |
| 312 addBuilder(name, builder, charOffset); | 307 addBuilder(name, builder, charOffset); |
| 313 buildBuilder(builder); | 308 buildBuilder(builder); |
| 314 } | 309 } |
| 315 canAddImplementationBuilders = false; | 310 canAddImplementationBuilders = false; |
| 311 |
| 312 scope.setters.forEach((String name, Builder setter) { |
| 313 Builder member = scopeBuilder[name]; |
| 314 if (member == null || !member.isField || member.isFinal) return; |
| 315 // TODO(ahe): charOffset is missing. |
| 316 addCompileTimeError( |
| 317 setter.charOffset, "Conflicts with member '${name}'."); |
| 318 addCompileTimeError( |
| 319 member.charOffset, "Conflicts with setter '${name}'."); |
| 320 }); |
| 321 |
| 316 return null; | 322 return null; |
| 317 } | 323 } |
| 318 | 324 |
| 319 /// Used to add implementation builder during the call to [build] above. | 325 /// Used to add implementation builder during the call to [build] above. |
| 320 /// Currently, only anonymous mixins are using implementation builders (see | 326 /// Currently, only anonymous mixins are using implementation builders (see |
| 321 /// [KernelMixinApplicationBuilder] | 327 /// [KernelMixinApplicationBuilder] |
| 322 /// (../kernel/kernel_mixin_application_builder.dart)). | 328 /// (../kernel/kernel_mixin_application_builder.dart)). |
| 323 void addImplementationBuilder(String name, Builder builder, int charOffset) { | 329 void addImplementationBuilder(String name, Builder builder, int charOffset) { |
| 324 assert(canAddImplementationBuilders, "$uri"); | 330 assert(canAddImplementationBuilders, "$uri"); |
| 325 implementationBuilders.add([name, builder, charOffset]); | 331 implementationBuilders.add([name, builder, charOffset]); |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 374 part.forEach((String name, Builder builder) { | 380 part.forEach((String name, Builder builder) { |
| 375 if (builder.next != null) { | 381 if (builder.next != null) { |
| 376 assert(builder.next.next == null); | 382 assert(builder.next.next == null); |
| 377 addBuilder(name, builder.next, builder.next.charOffset); | 383 addBuilder(name, builder.next, builder.next.charOffset); |
| 378 } | 384 } |
| 379 addBuilder(name, builder, builder.charOffset); | 385 addBuilder(name, builder, builder.charOffset); |
| 380 }); | 386 }); |
| 381 types.addAll(part.types); | 387 types.addAll(part.types); |
| 382 constructorReferences.addAll(part.constructorReferences); | 388 constructorReferences.addAll(part.constructorReferences); |
| 383 part.partOfLibrary = this; | 389 part.partOfLibrary = this; |
| 390 part.scope.becomePartOf(scope); |
| 384 // TODO(ahe): Include metadata from part? | 391 // TODO(ahe): Include metadata from part? |
| 385 } | 392 } |
| 386 | 393 |
| 387 void buildInitialScopes() { | 394 void buildInitialScopes() { |
| 388 forEach(addToExportScope); | 395 forEach(addToExportScope); |
| 389 forEach((String name, Builder member) { | |
| 390 addToScope(name, member, member.charOffset, false); | |
| 391 }); | |
| 392 } | 396 } |
| 393 | 397 |
| 394 void addImportsToScope() { | 398 void addImportsToScope() { |
| 395 bool explicitCoreImport = this == loader.coreLibrary; | 399 bool explicitCoreImport = this == loader.coreLibrary; |
| 396 for (Import import in imports) { | 400 for (Import import in imports) { |
| 397 if (import.imported == loader.coreLibrary) { | 401 if (import.imported == loader.coreLibrary) { |
| 398 explicitCoreImport = true; | 402 explicitCoreImport = true; |
| 399 } | 403 } |
| 400 import.finalizeImports(this); | 404 import.finalizeImports(this); |
| 401 } | 405 } |
| 402 if (!explicitCoreImport) { | 406 if (!explicitCoreImport) { |
| 403 loader.coreLibrary.exports.forEach((String name, Builder member) { | 407 loader.coreLibrary.exports.forEach((String name, Builder member) { |
| 404 addToScope(name, member, -1, true); | 408 addToScope(name, member, -1, true); |
| 405 }); | 409 }); |
| 406 } | 410 } |
| 407 } | 411 } |
| 408 | 412 |
| 409 @override | 413 @override |
| 410 void addToScope(String name, Builder member, int charOffset, bool isImport) { | 414 void addToScope(String name, Builder member, int charOffset, bool isImport) { |
| 411 Builder existing = scope.lookup(name, member.charOffset, fileUri); | 415 Map<String, Builder> map = |
| 416 member.isSetter ? importScope.setters : importScope.local; |
| 417 Builder existing = map[name]; |
| 412 if (existing != null) { | 418 if (existing != null) { |
| 413 if (existing != member) { | 419 if (existing != member) { |
| 414 scope.local[name] = buildAmbiguousBuilder( | 420 map[name] = buildAmbiguousBuilder(name, existing, member, charOffset, |
| 415 name, existing, member, charOffset, | |
| 416 isImport: isImport); | 421 isImport: isImport); |
| 417 } | 422 } |
| 418 } else { | 423 } else { |
| 419 scope.local[name] = member; | 424 map[name] = member; |
| 420 } | 425 } |
| 421 } | 426 } |
| 422 | 427 |
| 423 /// Returns true if the export scope was modified. | 428 /// Returns true if the export scope was modified. |
| 424 bool addToExportScope(String name, Builder member) { | 429 bool addToExportScope(String name, Builder member) { |
| 425 if (name.startsWith("_")) return false; | 430 if (name.startsWith("_")) return false; |
| 426 if (member is PrefixBuilder) return false; | 431 if (member is PrefixBuilder) return false; |
| 427 Builder existing = exports[name]; | 432 Map<String, Builder> map = |
| 433 member.isSetter ? exports.setters : exports.local; |
| 434 Builder existing = map[name]; |
| 428 if (existing == member) return false; | 435 if (existing == member) return false; |
| 429 if (existing != null) { | 436 if (existing != null) { |
| 430 Builder result = | 437 Builder result = |
| 431 buildAmbiguousBuilder(name, existing, member, -1, isExport: true); | 438 buildAmbiguousBuilder(name, existing, member, -1, isExport: true); |
| 432 exports[name] = result; | 439 map[name] = result; |
| 433 return result != existing; | 440 return result != existing; |
| 434 } else { | 441 } else { |
| 435 exports[name] = member; | 442 map[name] = member; |
| 436 } | 443 } |
| 437 return true; | 444 return true; |
| 438 } | 445 } |
| 439 | 446 |
| 440 int resolveTypes(_) { | 447 int resolveTypes(_) { |
| 441 int typeCount = types.length; | 448 int typeCount = types.length; |
| 442 for (T t in types) { | 449 for (T t in types) { |
| 443 t.resolveIn(scope); | 450 t.resolveIn(scope); |
| 444 } | 451 } |
| 445 forEach((String name, Builder member) { | 452 forEach((String name, Builder member) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 464 String get fullNameForErrors => name ?? "<library '$relativeFileUri'>"; | 471 String get fullNameForErrors => name ?? "<library '$relativeFileUri'>"; |
| 465 } | 472 } |
| 466 | 473 |
| 467 /// Unlike [Scope], this scope is used during construction of builders to | 474 /// Unlike [Scope], this scope is used during construction of builders to |
| 468 /// ensure types and members are added to and resolved in the correct location. | 475 /// ensure types and members are added to and resolved in the correct location. |
| 469 class DeclarationBuilder<T extends TypeBuilder> { | 476 class DeclarationBuilder<T extends TypeBuilder> { |
| 470 final DeclarationBuilder<T> parent; | 477 final DeclarationBuilder<T> parent; |
| 471 | 478 |
| 472 final Map<String, Builder> members; | 479 final Map<String, Builder> members; |
| 473 | 480 |
| 481 final Map<String, Builder> constructors; |
| 482 |
| 483 final Map<String, Builder> setters; |
| 484 |
| 474 final List<T> types = <T>[]; | 485 final List<T> types = <T>[]; |
| 475 | 486 |
| 476 final String name; | 487 final String name; |
| 477 | 488 |
| 478 final Map<ProcedureBuilder, DeclarationBuilder<T>> factoryDeclarations = | 489 final Map<ProcedureBuilder, DeclarationBuilder<T>> factoryDeclarations; |
| 479 <ProcedureBuilder, DeclarationBuilder<T>>{}; | |
| 480 | 490 |
| 481 DeclarationBuilder(this.members, this.name, [this.parent]); | 491 DeclarationBuilder(this.members, this.setters, this.constructors, |
| 492 this.factoryDeclarations, this.name, this.parent); |
| 482 | 493 |
| 483 void addMember(String name, MemberBuilder builder) { | 494 DeclarationBuilder.library() |
| 484 if (members == null) { | 495 : this(<String, Builder>{}, <String, Builder>{}, null, null, null, null); |
| 485 parent.addMember(name, builder); | |
| 486 } else { | |
| 487 members[name] = builder; | |
| 488 } | |
| 489 } | |
| 490 | 496 |
| 491 MemberBuilder lookupMember(String name) { | 497 DeclarationBuilder createNested(String name, bool hasMembers) { |
| 492 return members == null ? parent.lookupMember(name) : members[name]; | 498 return new DeclarationBuilder<T>( |
| 499 hasMembers ? <String, MemberBuilder>{} : null, |
| 500 hasMembers ? <String, MemberBuilder>{} : null, |
| 501 hasMembers ? <String, MemberBuilder>{} : null, |
| 502 <ProcedureBuilder, DeclarationBuilder<T>>{}, |
| 503 name, |
| 504 this); |
| 493 } | 505 } |
| 494 | 506 |
| 495 void addType(T type) { | 507 void addType(T type) { |
| 496 types.add(type); | 508 types.add(type); |
| 497 } | 509 } |
| 498 | 510 |
| 499 /// Resolves type variables in [types] and propagate other types to [parent]. | 511 /// Resolves type variables in [types] and propagate other types to [parent]. |
| 500 void resolveTypes( | 512 void resolveTypes( |
| 501 List<TypeVariableBuilder> typeVariables, SourceLibraryBuilder library) { | 513 List<TypeVariableBuilder> typeVariables, SourceLibraryBuilder library) { |
| 502 // TODO(ahe): The input to this method, [typeVariables], shouldn't be just | 514 // TODO(ahe): The input to this method, [typeVariables], shouldn't be just |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 539 types.clear(); | 551 types.clear(); |
| 540 } | 552 } |
| 541 | 553 |
| 542 /// Called to register [procedure] as a factory whose types are collected in | 554 /// Called to register [procedure] as a factory whose types are collected in |
| 543 /// [factoryDeclaration]. Later, once the class has been built, we can | 555 /// [factoryDeclaration]. Later, once the class has been built, we can |
| 544 /// synthesize type variables on the factory matching the class'. | 556 /// synthesize type variables on the factory matching the class'. |
| 545 void addFactoryDeclaration( | 557 void addFactoryDeclaration( |
| 546 ProcedureBuilder procedure, DeclarationBuilder<T> factoryDeclaration) { | 558 ProcedureBuilder procedure, DeclarationBuilder<T> factoryDeclaration) { |
| 547 factoryDeclarations[procedure] = factoryDeclaration; | 559 factoryDeclarations[procedure] = factoryDeclaration; |
| 548 } | 560 } |
| 561 |
| 562 Scope toScope(Scope parent) { |
| 563 return new Scope(members, setters, parent, isModifiable: false); |
| 564 } |
| 549 } | 565 } |
| OLD | NEW |