Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 /// Data produced by dart2js when run with the `--dump-info` flag. | 5 /// Data produced by dart2js when run with the `--dump-info` flag. |
| 6 library dart2js_info.info; | 6 library dart2js_info.info; |
| 7 | 7 |
| 8 /// Common interface to many pieces of information generated by the dart2js | 8 /// Common interface to many pieces of information generated by the dart2js |
| 9 /// compiler. | 9 /// compiler. |
| 10 abstract class Info { | 10 abstract class Info { |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 104 | 104 |
| 105 /// Information about type defs in the program. | 105 /// Information about type defs in the program. |
| 106 List<TypedefInfo> typedefs = <TypedefInfo>[]; | 106 List<TypedefInfo> typedefs = <TypedefInfo>[]; |
| 107 | 107 |
| 108 /// Information about each class (in any library). | 108 /// Information about each class (in any library). |
| 109 List<ClassInfo> classes = <ClassInfo>[]; | 109 List<ClassInfo> classes = <ClassInfo>[]; |
| 110 | 110 |
| 111 /// Information about fields (in any class). | 111 /// Information about fields (in any class). |
| 112 List<FieldInfo> fields = <FieldInfo>[]; | 112 List<FieldInfo> fields = <FieldInfo>[]; |
| 113 | 113 |
| 114 /// Information about constants anywhere in the program. | |
| 115 // TODO(sigmund): expand docs about canonicalization. We don't put these | |
| 116 // inside library because a single constant can be used in more than one lib, | |
| 117 // and we'll include it only once in the output. | |
| 118 List<ConstantInfo> constants = <ConstantInfo>[]; | |
| 119 | |
| 114 /// Information about output units (should be just one entry if not using | 120 /// Information about output units (should be just one entry if not using |
| 115 /// deferred loading). | 121 /// deferred loading). |
| 116 List<OutputUnitInfo> outputUnits = <OutputUnitInfo>[]; | 122 List<OutputUnitInfo> outputUnits = <OutputUnitInfo>[]; |
| 117 | 123 |
| 118 /// Details about all deferred imports and what files would be loaded when the | 124 /// Details about all deferred imports and what files would be loaded when the |
| 119 /// import is resolved. | 125 /// import is resolved. |
| 120 // TODO(sigmund): use a different format for dump-info. This currently emits | 126 // TODO(sigmund): use a different format for dump-info. This currently emits |
| 121 // the same map that is created for the `--deferred-map` flag. | 127 // the same map that is created for the `--deferred-map` flag. |
| 122 Map<String, Map<String, dynamic>> deferredFiles; | 128 Map<String, Map<String, dynamic>> deferredFiles; |
| 123 | 129 |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 170 return map; | 176 return map; |
| 171 } | 177 } |
| 172 | 178 |
| 173 Map toJson() => { | 179 Map toJson() => { |
| 174 'elements': { | 180 'elements': { |
| 175 'library': _listAsJsonMap(libraries), | 181 'library': _listAsJsonMap(libraries), |
| 176 'class': _listAsJsonMap(classes), | 182 'class': _listAsJsonMap(classes), |
| 177 'function': _listAsJsonMap(functions), | 183 'function': _listAsJsonMap(functions), |
| 178 'typedef': _listAsJsonMap(typedefs), | 184 'typedef': _listAsJsonMap(typedefs), |
| 179 'field': _listAsJsonMap(fields), | 185 'field': _listAsJsonMap(fields), |
| 186 'constant': _listAsJsonMap(constants), | |
| 180 }, | 187 }, |
| 181 'holding': _extractHoldingInfo(), | 188 'holding': _extractHoldingInfo(), |
| 182 'dependencies': _extractDependencies(), | 189 'dependencies': _extractDependencies(), |
| 183 'outputUnits': outputUnits.map((u) => u.toJson()).toList(), | 190 'outputUnits': outputUnits.map((u) => u.toJson()).toList(), |
| 184 'dump_version': version, | 191 'dump_version': version, |
| 185 'deferredFiles': deferredFiles, | 192 'deferredFiles': deferredFiles, |
| 186 'dump_minor_version': '$minorVersion', | 193 'dump_minor_version': '$minorVersion', |
| 187 // TODO(sigmund): change viewer to accept an int? | 194 // TODO(sigmund): change viewer to accept an int? |
| 188 'program': program.toJson(), | 195 'program': program.toJson(), |
| 189 }; | 196 }; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 232 | 239 |
| 233 AllInfo parseAll(Map json) { | 240 AllInfo parseAll(Map json) { |
| 234 var result = new AllInfo(); | 241 var result = new AllInfo(); |
| 235 var elements = json['elements']; | 242 var elements = json['elements']; |
| 236 result.libraries.addAll(elements['library'].values.map(parseLibrary)); | 243 result.libraries.addAll(elements['library'].values.map(parseLibrary)); |
| 237 result.classes.addAll(elements['class'].values.map(parseClass)); | 244 result.classes.addAll(elements['class'].values.map(parseClass)); |
| 238 result.functions.addAll(elements['function'].values.map(parseFunction)); | 245 result.functions.addAll(elements['function'].values.map(parseFunction)); |
| 239 result.fields.addAll(elements['field'].values.map(parseField)); | 246 result.fields.addAll(elements['field'].values.map(parseField)); |
| 240 result.typedefs.addAll(elements['typedef'].values.map(parseTypedef)); | 247 result.typedefs.addAll(elements['typedef'].values.map(parseTypedef)); |
| 241 | 248 |
| 249 // TODO(sigmund): remove null check on next breaking version | |
| 250 var constants = elements['constant']; | |
| 251 if (constants != null) { | |
| 252 result.constants.addAll(constants.values.map(parseConstant)); | |
| 253 } | |
| 254 | |
| 242 var idMap = {}; | 255 var idMap = {}; |
| 243 for (var f in result.functions) { | 256 for (var f in result.functions) { |
| 244 idMap[f.serializedId] = f; | 257 idMap[f.serializedId] = f; |
| 245 } | 258 } |
| 246 for (var f in result.fields) { | 259 for (var f in result.fields) { |
| 247 idMap[f.serializedId] = f; | 260 idMap[f.serializedId] = f; |
| 248 } | 261 } |
| 249 | 262 |
| 250 json['holding'].forEach((k, deps) { | 263 json['holding'].forEach((k, deps) { |
| 251 var src = idMap[k]; | 264 var src = idMap[k]; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 309 FieldInfo parseField(Map json) { | 322 FieldInfo parseField(Map json) { |
| 310 FieldInfo result = parseId(json['id']); | 323 FieldInfo result = parseId(json['id']); |
| 311 return result..name = json['name'] | 324 return result..name = json['name'] |
| 312 ..parent = parseId(json['parent']) | 325 ..parent = parseId(json['parent']) |
| 313 ..coverageId = json['coverageId'] | 326 ..coverageId = json['coverageId'] |
| 314 ..outputUnit = parseId(json['outputUnit']) | 327 ..outputUnit = parseId(json['outputUnit']) |
| 315 ..size = json['size'] | 328 ..size = json['size'] |
| 316 ..type = json['type'] | 329 ..type = json['type'] |
| 317 ..inferredType = json['inferredType'] | 330 ..inferredType = json['inferredType'] |
| 318 ..code = json['code'] | 331 ..code = json['code'] |
| 332 ..isConst = json['const'] ?? false | |
|
Harry Terkelsen
2015/08/19 17:41:31
also add a todo to remove '?? false' on next break
Siggi Cherem (dart-lang)
2015/08/19 19:31:15
Since it was a boolean, I was thinking that we wou
Harry Terkelsen
2015/08/19 19:34:57
sgtm
| |
| 333 ..initializer = parseId(json['initializer']) | |
| 319 ..closures = json['children'].map(parseId).toList(); | 334 ..closures = json['children'].map(parseId).toList(); |
| 320 } | 335 } |
| 321 | 336 |
| 337 ConstantInfo parseConstant(Map json) { | |
| 338 ConstantInfo result = parseId(json['id']); | |
| 339 return result | |
| 340 ..code = json['code'] | |
| 341 ..size = json['size']; | |
| 342 } | |
| 343 | |
| 322 TypedefInfo parseTypedef(Map json) { | 344 TypedefInfo parseTypedef(Map json) { |
| 323 TypedefInfo result = parseId(json['id']); | 345 TypedefInfo result = parseId(json['id']); |
| 324 return result..name = json['name'] | 346 return result..name = json['name'] |
| 325 ..parent = parseId(json['parent']) | 347 ..parent = parseId(json['parent']) |
| 326 ..type = json['type'] | 348 ..type = json['type'] |
| 327 ..size = 0; | 349 ..size = 0; |
| 328 } | 350 } |
| 329 | 351 |
| 330 ProgramInfo parseProgram(Map json) => | 352 ProgramInfo parseProgram(Map json) => |
| 331 new ProgramInfo()..size = json['size']; | 353 new ProgramInfo()..size = json['size']; |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 362 if (serializedId == null) { | 384 if (serializedId == null) { |
| 363 return null; | 385 return null; |
| 364 } else if (serializedId.startsWith('function/')) { | 386 } else if (serializedId.startsWith('function/')) { |
| 365 return new FunctionInfo._(serializedId); | 387 return new FunctionInfo._(serializedId); |
| 366 } else if (serializedId.startsWith('library/')) { | 388 } else if (serializedId.startsWith('library/')) { |
| 367 return new LibraryInfo._(serializedId); | 389 return new LibraryInfo._(serializedId); |
| 368 } else if (serializedId.startsWith('class/')) { | 390 } else if (serializedId.startsWith('class/')) { |
| 369 return new ClassInfo._(serializedId); | 391 return new ClassInfo._(serializedId); |
| 370 } else if (serializedId.startsWith('field/')) { | 392 } else if (serializedId.startsWith('field/')) { |
| 371 return new FieldInfo._(serializedId); | 393 return new FieldInfo._(serializedId); |
| 394 } else if (serializedId.startsWith('constant/')) { | |
| 395 return new ConstantInfo._(serializedId); | |
| 372 } else if (serializedId.startsWith('typedef/')) { | 396 } else if (serializedId.startsWith('typedef/')) { |
| 373 return new TypedefInfo._(serializedId); | 397 return new TypedefInfo._(serializedId); |
| 374 } else if (serializedId.startsWith('outputUnit/')) { | 398 } else if (serializedId.startsWith('outputUnit/')) { |
| 375 return new OutputUnitInfo._(serializedId); | 399 return new OutputUnitInfo._(serializedId); |
| 376 } | 400 } |
| 377 assert(false); | 401 assert(false); |
| 378 }); | 402 }); |
| 379 } | 403 } |
| 380 | 404 |
| 381 /// Info associated with a library element. | 405 /// Info associated with a library element. |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 458 // TODO(sigmund): change format, include only when abstract is true. | 482 // TODO(sigmund): change format, include only when abstract is true. |
| 459 'modifiers': {'abstract': isAbstract}, | 483 'modifiers': {'abstract': isAbstract}, |
| 460 'children': [] | 484 'children': [] |
| 461 ..addAll(fields.map((f) => f.serializedId)) | 485 ..addAll(fields.map((f) => f.serializedId)) |
| 462 ..addAll(functions.map((m) => m.serializedId)) | 486 ..addAll(functions.map((m) => m.serializedId)) |
| 463 }); | 487 }); |
| 464 | 488 |
| 465 void accept(InfoVisitor visitor) => visitor.visitClass(this); | 489 void accept(InfoVisitor visitor) => visitor.visitClass(this); |
| 466 } | 490 } |
| 467 | 491 |
| 492 /// Information about a constant value. | |
| 493 // TODO(sigmund): add dependency data for ConstantInfo | |
| 494 class ConstantInfo extends BasicInfo { | |
| 495 /// The actual generated code for the field. | |
| 496 String code; | |
| 497 | |
| 498 static int _ids = 0; | |
|
Harry Terkelsen
2015/08/19 17:41:31
I'm not clear on why constants get their own ids s
Siggi Cherem (dart-lang)
2015/08/19 19:31:15
Note that every info kind has their own separate i
Harry Terkelsen
2015/08/19 19:34:57
Acknowledged.
| |
| 499 // TODO(sigmund): Add coverage support to constants? | |
| 500 ConstantInfo( | |
| 501 {int size: 0, | |
| 502 this.code, | |
| 503 OutputUnitInfo outputUnit}) | |
| 504 : super(InfoKind.constant, _ids++, null, outputUnit, size, null); | |
| 505 | |
| 506 ConstantInfo._(String serializedId) : super._fromId(serializedId); | |
| 507 | |
| 508 Map toJson() { | |
| 509 var result = super.toJson() | |
| 510 ..addAll({ | |
|
Harry Terkelsen
2015/08/19 17:41:31
weird formatting, is this what dartfmt outputs?
Siggi Cherem (dart-lang)
2015/08/19 19:31:15
sorry, my faull. I've fixed this line for now, but
Siggi Cherem (dart-lang)
2015/08/19 19:53:40
FYI - I pushed the formatter changes directly (her
| |
| 511 'code': code, | |
| 512 }); | |
| 513 return result; | |
| 514 } | |
| 515 | |
| 516 void accept(InfoVisitor visitor) => visitor.visitConstant(this); | |
| 517 } | |
| 518 | |
| 468 /// Information about a field element. | 519 /// Information about a field element. |
| 469 class FieldInfo extends BasicInfo with CodeInfo { | 520 class FieldInfo extends BasicInfo with CodeInfo { |
| 470 /// The type of the field. | 521 /// The type of the field. |
| 471 String type; | 522 String type; |
| 472 | 523 |
| 473 /// The type inferred by dart2js's whole program analysis | 524 /// The type inferred by dart2js's whole program analysis |
| 474 String inferredType; | 525 String inferredType; |
| 475 | 526 |
| 476 /// Nested closures seen in the field initializer. | 527 /// Nested closures seen in the field initializer. |
| 477 List<FunctionInfo> closures; | 528 List<FunctionInfo> closures; |
| 478 | 529 |
| 479 /// The actual generated code for the field. | 530 /// The actual generated code for the field. |
| 480 String code; | 531 String code; |
| 481 | 532 |
| 533 /// Whether this corresponds to a const field declaration. | |
| 534 bool isConst; | |
| 535 | |
| 536 /// When [isConst] is true, the constant initializer expression. | |
| 537 ConstantInfo initializer; | |
| 538 | |
| 482 static int _ids = 0; | 539 static int _ids = 0; |
| 483 FieldInfo( | 540 FieldInfo( |
| 484 {String name, | 541 {String name, |
| 485 String coverageId, | 542 String coverageId, |
| 486 int size: 0, | 543 int size: 0, |
| 487 this.type, | 544 this.type, |
| 488 this.inferredType, | 545 this.inferredType, |
| 489 this.closures, | 546 this.closures, |
| 490 this.code, | 547 this.code, |
| 491 OutputUnitInfo outputUnit}) | 548 OutputUnitInfo outputUnit, |
| 549 this.isConst}) | |
| 492 : super(InfoKind.field, _ids++, name, outputUnit, size, coverageId); | 550 : super(InfoKind.field, _ids++, name, outputUnit, size, coverageId); |
| 493 | 551 |
| 494 FieldInfo._(String serializedId) : super._fromId(serializedId); | 552 FieldInfo._(String serializedId) : super._fromId(serializedId); |
| 495 | 553 |
| 496 Map toJson() => super.toJson() | 554 Map toJson() { |
| 555 var result = super.toJson() | |
| 497 ..addAll({ | 556 ..addAll({ |
| 498 'children': closures.map((i) => i.serializedId).toList(), | 557 'children': closures.map((i) => i.serializedId).toList(), |
| 499 'inferredType': inferredType, | 558 'inferredType': inferredType, |
| 500 'code': code, | 559 'code': code, |
| 501 'type': type, | 560 'type': type, |
| 502 }); | 561 }); |
| 562 if (isConst) { | |
| 563 result['const'] = true; | |
| 564 if (initializer != null) result['initializer'] = initializer.serializedId; | |
| 565 } | |
| 566 return result; | |
| 567 } | |
| 503 | 568 |
| 504 void accept(InfoVisitor visitor) => visitor.visitField(this); | 569 void accept(InfoVisitor visitor) => visitor.visitField(this); |
| 505 } | 570 } |
| 506 | 571 |
| 507 /// Information about a typedef declaration. | 572 /// Information about a typedef declaration. |
| 508 class TypedefInfo extends BasicInfo { | 573 class TypedefInfo extends BasicInfo { |
| 509 /// The declared type. | 574 /// The declared type. |
| 510 String type; | 575 String type; |
| 511 | 576 |
| 512 static int _ids = 0; | 577 static int _ids = 0; |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 651 'external': isExternal, | 716 'external': isExternal, |
| 652 }; | 717 }; |
| 653 } | 718 } |
| 654 | 719 |
| 655 /// Possible values of the `kind` field in the serialied infos. | 720 /// Possible values of the `kind` field in the serialied infos. |
| 656 enum InfoKind { | 721 enum InfoKind { |
| 657 library, | 722 library, |
| 658 clazz, | 723 clazz, |
| 659 function, | 724 function, |
| 660 field, | 725 field, |
| 726 constant, | |
| 661 outputUnit, | 727 outputUnit, |
| 662 typedef, | 728 typedef, |
| 663 } | 729 } |
| 664 | 730 |
| 665 String _kindToString(InfoKind kind) { | 731 String _kindToString(InfoKind kind) { |
| 666 switch(kind) { | 732 switch(kind) { |
| 667 case InfoKind.library: return 'library'; | 733 case InfoKind.library: return 'library'; |
| 668 case InfoKind.clazz: return 'class'; | 734 case InfoKind.clazz: return 'class'; |
| 669 case InfoKind.function: return 'function'; | 735 case InfoKind.function: return 'function'; |
| 670 case InfoKind.field: return 'field'; | 736 case InfoKind.field: return 'field'; |
| 737 case InfoKind.constant: return 'constant'; | |
| 671 case InfoKind.outputUnit: return 'outputUnit'; | 738 case InfoKind.outputUnit: return 'outputUnit'; |
| 672 case InfoKind.typedef: return 'typedef'; | 739 case InfoKind.typedef: return 'typedef'; |
| 673 default: return null; | 740 default: return null; |
| 674 } | 741 } |
| 675 } | 742 } |
| 676 | 743 |
| 677 int _idFromSerializedId(String serializedId) => | 744 int _idFromSerializedId(String serializedId) => |
| 678 int.parse(serializedId.substring(serializedId.indexOf('/') + 1)); | 745 int.parse(serializedId.substring(serializedId.indexOf('/') + 1)); |
| 679 | 746 |
| 680 InfoKind _kindFromSerializedId(String serializedId) => | 747 InfoKind _kindFromSerializedId(String serializedId) => |
| 681 _kindFromString(serializedId.substring(0, serializedId.indexOf('/'))); | 748 _kindFromString(serializedId.substring(0, serializedId.indexOf('/'))); |
| 682 | 749 |
| 683 InfoKind _kindFromString(String kind) { | 750 InfoKind _kindFromString(String kind) { |
| 684 switch(kind) { | 751 switch(kind) { |
| 685 case 'library': return InfoKind.library; | 752 case 'library': return InfoKind.library; |
| 686 case 'class': return InfoKind.clazz; | 753 case 'class': return InfoKind.clazz; |
| 687 case 'function': return InfoKind.function; | 754 case 'function': return InfoKind.function; |
| 688 case 'field': return InfoKind.field; | 755 case 'field': return InfoKind.field; |
| 756 case 'constant': return InfoKind.constant; | |
| 689 case 'outputUnit': return InfoKind.outputUnit; | 757 case 'outputUnit': return InfoKind.outputUnit; |
| 690 case 'typedef': return InfoKind.typedef; | 758 case 'typedef': return InfoKind.typedef; |
| 691 default: return null; | 759 default: return null; |
| 692 } | 760 } |
| 693 } | 761 } |
| 694 | 762 |
| 695 /// A simple visitor for information produced by the dart2js compiler. | 763 /// A simple visitor for information produced by the dart2js compiler. |
| 696 class InfoVisitor { | 764 class InfoVisitor { |
| 697 visitAll(AllInfo info) {} | 765 visitAll(AllInfo info) {} |
| 698 visitProgram(ProgramInfo info) {} | 766 visitProgram(ProgramInfo info) {} |
| 699 visitLibrary(LibraryInfo info) {} | 767 visitLibrary(LibraryInfo info) {} |
| 700 visitClass(ClassInfo info) {} | 768 visitClass(ClassInfo info) {} |
| 701 visitField(FieldInfo info) {} | 769 visitField(FieldInfo info) {} |
| 770 visitConstant(ConstantInfo info) {} | |
| 702 visitFunction(FunctionInfo info) {} | 771 visitFunction(FunctionInfo info) {} |
| 703 visitTypedef(TypedefInfo info) {} | 772 visitTypedef(TypedefInfo info) {} |
| 704 visitOutput(OutputUnitInfo info) {} | 773 visitOutput(OutputUnitInfo info) {} |
| 705 } | 774 } |
| 706 | 775 |
| 707 /// A visitor that recursively walks each portion of the program. Because the | 776 /// A visitor that recursively walks each portion of the program. Because the |
| 708 /// info representation is redundant, this visitor only walks the structure of | 777 /// info representation is redundant, this visitor only walks the structure of |
| 709 /// the program and skips some redundant links. For example, even though | 778 /// the program and skips some redundant links. For example, even though |
| 710 /// visitAll contains references to functions, this visitor only recurses to | 779 /// visitAll contains references to functions, this visitor only recurses to |
| 711 /// visit libraries, then from each library we visit functions and classes, and | 780 /// visit libraries, then from each library we visit functions and classes, and |
| 712 /// so on. | 781 /// so on. |
| 713 class RecursiveInfoVisitor extends InfoVisitor { | 782 class RecursiveInfoVisitor extends InfoVisitor { |
| 714 visitAll(AllInfo info) { | 783 visitAll(AllInfo info) { |
| 715 // Note: we don't visit functions, fields, classes, and typedefs because | 784 // Note: we don't visit functions, fields, classes, and typedefs because |
| 716 // they are reachable from the library info. | 785 // they are reachable from the library info. |
| 717 info.libraries.forEach(visitLibrary); | 786 info.libraries.forEach(visitLibrary); |
| 787 info.constants.forEach(visitConstant); | |
| 718 } | 788 } |
| 719 | 789 |
| 720 visitLibrary(LibraryInfo info) { | 790 visitLibrary(LibraryInfo info) { |
| 721 info.topLevelFunctions.forEach(visitFunction); | 791 info.topLevelFunctions.forEach(visitFunction); |
| 722 info.topLevelVariables.forEach(visitField); | 792 info.topLevelVariables.forEach(visitField); |
| 723 info.classes.forEach(visitClass); | 793 info.classes.forEach(visitClass); |
| 724 info.typedefs.forEach(visitTypedef); | 794 info.typedefs.forEach(visitTypedef); |
| 725 } | 795 } |
| 726 | 796 |
| 727 visitClass(ClassInfo info) { | 797 visitClass(ClassInfo info) { |
| 728 info.functions.forEach(visitFunction); | 798 info.functions.forEach(visitFunction); |
| 729 info.fields.forEach(visitField); | 799 info.fields.forEach(visitField); |
| 730 } | 800 } |
| 731 | 801 |
| 732 visitField(FieldInfo info) { | 802 visitField(FieldInfo info) { |
| 733 info.closures.forEach(visitFunction); | 803 info.closures.forEach(visitFunction); |
| 734 } | 804 } |
| 735 | 805 |
| 736 visitFunction(FunctionInfo info) { | 806 visitFunction(FunctionInfo info) { |
| 737 info.closures.forEach(visitFunction); | 807 info.closures.forEach(visitFunction); |
| 738 } | 808 } |
| 739 } | 809 } |
| OLD | NEW |