Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 2 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 3 # for details. All rights reserved. Use of this source code is governed by a | 3 # for details. All rights reserved. Use of this source code is governed by a |
| 4 # BSD-style license that can be found in the LICENSE file. | 4 # BSD-style license that can be found in the LICENSE file. |
| 5 | 5 |
| 6 """This module provides shared functionality for the system to generate | 6 """This module provides shared functionality for the system to generate |
| 7 Dart:html APIs from the IDL database.""" | 7 Dart:html APIs from the IDL database.""" |
| 8 | 8 |
| 9 from systemfrog import * | 9 from systemfrog import * |
| 10 from systeminterface import * | 10 from systeminterface import * |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 66 'NodeSelector.querySelectorAll', | 66 'NodeSelector.querySelectorAll', |
| 67 'Storage.length', | 67 'Storage.length', |
| 68 'Storage.clear', | 68 'Storage.clear', |
| 69 'Storage.getItem', | 69 'Storage.getItem', |
| 70 'Storage.key', | 70 'Storage.key', |
| 71 'Storage.removeItem', | 71 'Storage.removeItem', |
| 72 'Storage.setItem', | 72 'Storage.setItem', |
| 73 'Window.getComputedStyle', | 73 'Window.getComputedStyle', |
| 74 ]) | 74 ]) |
| 75 | 75 |
| 76 _manually_generated_html_members = set([ | |
| 77 'Document.querySelectorAll', | |
|
podivilov
2012/05/24 08:29:04
This one is in _private_html_members list already.
| |
| 78 'Document.querySelector', | |
|
podivilov
2012/05/24 08:29:04
Moved to _html_library_custom.
| |
| 79 ]) | |
| 80 | |
| 81 # Members from the standard dom that exist in the dart:html library with | 76 # Members from the standard dom that exist in the dart:html library with |
| 82 # identical functionality but with cleaner names. | 77 # identical functionality but with cleaner names. |
| 83 _html_library_renames = { | 78 _html_library_renames = { |
| 84 'Document.defaultView': 'window', | 79 'Document.defaultView': 'window', |
| 85 'DocumentFragment.querySelector': 'query', | 80 'DocumentFragment.querySelector': 'query', |
| 86 'NodeSelector.querySelector': 'query', | 81 'NodeSelector.querySelector': 'query', |
| 87 'Element.querySelector': 'query', | 82 'Element.querySelector': 'query', |
| 88 'Element.webkitMatchesSelector' : 'matchesSelector', | 83 'Element.webkitMatchesSelector' : 'matchesSelector', |
| 89 'Element.scrollIntoViewIfNeeded': 'scrollIntoView', | 84 'Element.scrollIntoViewIfNeeded': 'scrollIntoView', |
| 90 'Node.cloneNode': 'clone', | 85 'Node.cloneNode': 'clone', |
| 91 'Node.nextSibling': 'nextNode', | 86 'Node.nextSibling': 'nextNode', |
| 92 'Node.ownerDocument': 'document', | 87 'Node.ownerDocument': 'document', |
| 93 'Node.parentNode': 'parent', | 88 'Node.parentNode': 'parent', |
| 94 'Node.previousSibling': 'previousNode', | 89 'Node.previousSibling': 'previousNode', |
| 95 'Node.textContent': 'text', | 90 'Node.textContent': 'text', |
| 96 'SVGElement.className': '$dom_svgClassName', | 91 'SVGElement.className': '$dom_svgClassName', |
| 97 'SVGAnimatedString.className': '$dom_svgClassName', | 92 'SVGAnimatedString.className': '$dom_svgClassName', |
| 98 'SVGStylable.className': '$dom_svgClassName', | 93 'SVGStylable.className': '$dom_svgClassName', |
| 99 } | 94 } |
| 100 | 95 |
| 101 #TODO(jacobr): inject annotations into the interfaces based on this table and | |
| 102 # on _html_library_renames. | |
| 103 _injected_doc_fragments = { | |
| 104 'Element.query': ' /** @domName Element.querySelector, Document.getElementB yId */', | |
| 105 } | |
| 106 # Members and classes from the dom that should be removed completelly from | 96 # Members and classes from the dom that should be removed completelly from |
| 107 # dart:html. These could be expressed in the IDL instead but expressing this | 97 # dart:html. These could be expressed in the IDL instead but expressing this |
| 108 # as a simple table instead is more concise. | 98 # as a simple table instead is more concise. |
| 109 # Syntax is: ClassName.(get\.|set\.)?MemberName | 99 # Syntax is: ClassName.(get\.|set\.)?MemberName |
| 110 # Using get: and set: is optional and should only be used when a getter needs | 100 # Using get: and set: is optional and should only be used when a getter needs |
| 111 # to be suppressed but not the setter, etc. | 101 # to be suppressed but not the setter, etc. |
| 112 # TODO(jacobr): cleanup and augment this list. | 102 # TODO(jacobr): cleanup and augment this list. |
| 113 _html_library_remove = set([ | 103 _html_library_remove = set([ |
| 114 'Window.get:document', # Removed as we have a custom implementation. | |
|
podivilov
2012/05/24 08:29:04
This is custom, not removed.
| |
| 115 'NodeList.item', | 104 'NodeList.item', |
| 116 "Attr.*", | 105 "Attr.*", |
| 117 # "BarProp.*", | 106 # "BarProp.*", |
| 118 # "BarInfo.*", | 107 # "BarInfo.*", |
| 119 # "Blob.webkitSlice", | 108 # "Blob.webkitSlice", |
| 120 # "CDATASection.*", | 109 # "CDATASection.*", |
| 121 # "Comment.*", | 110 # "Comment.*", |
| 122 # "DOMImplementation.*", | 111 # "DOMImplementation.*", |
| 123 "Document.get:forms", | 112 "Document.get:forms", |
| 124 # "Document.get:selectedStylesheetSet", | 113 # "Document.get:selectedStylesheetSet", |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 230 "Node.isEqualNode", | 219 "Node.isEqualNode", |
| 231 "Node.get:TEXT_NODE", | 220 "Node.get:TEXT_NODE", |
| 232 "Node.hasAttributes", | 221 "Node.hasAttributes", |
| 233 "Node.get:DOCUMENT_TYPE_NODE", | 222 "Node.get:DOCUMENT_TYPE_NODE", |
| 234 "Node.get:DOCUMENT_POSITION_FOLLOWING", | 223 "Node.get:DOCUMENT_POSITION_FOLLOWING", |
| 235 "Node.lookupNamespaceURI", | 224 "Node.lookupNamespaceURI", |
| 236 "Node.get:ELEMENT_NODE", | 225 "Node.get:ELEMENT_NODE", |
| 237 "Node.get:namespaceURI", | 226 "Node.get:namespaceURI", |
| 238 "Node.get:DOCUMENT_FRAGMENT_NODE", | 227 "Node.get:DOCUMENT_FRAGMENT_NODE", |
| 239 "Node.get:localName", | 228 "Node.get:localName", |
| 240 "Node.dispatchEvent", | |
| 241 "Node.isDefaultNamespace", | 229 "Node.isDefaultNamespace", |
| 242 "Node.compareDocumentPosition", | 230 "Node.compareDocumentPosition", |
| 243 "Node.get:baseURI", | 231 "Node.get:baseURI", |
| 244 "Node.isSameNode", | 232 "Node.isSameNode", |
| 245 "Node.get:DOCUMENT_POSITION_DISCONNECTED", | 233 "Node.get:DOCUMENT_POSITION_DISCONNECTED", |
| 246 "Node.get:DOCUMENT_NODE", | 234 "Node.get:DOCUMENT_NODE", |
| 247 "Node.get:DOCUMENT_POSITION_CONTAINS", | 235 "Node.get:DOCUMENT_POSITION_CONTAINS", |
| 248 "Node.get:COMMENT_NODE", | 236 "Node.get:COMMENT_NODE", |
| 249 "Node.get:ENTITY_REFERENCE_NODE", | 237 "Node.get:ENTITY_REFERENCE_NODE", |
| 250 "Node.isSupported", | 238 "Node.isSupported", |
| 251 "Node.get:DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC", | 239 "Node.get:DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC", |
| 252 "Node.get:NOTATION_NODE", | 240 "Node.get:NOTATION_NODE", |
| 253 "Node.normalize", | 241 "Node.normalize", |
| 254 "Node.get:parentElement", | 242 "Node.get:parentElement", |
| 255 "Node.get:ATTRIBUTE_NODE", | 243 "Node.get:ATTRIBUTE_NODE", |
| 256 "Node.get:ENTITY_NODE", | 244 "Node.get:ENTITY_NODE", |
| 257 "Node.get:DOCUMENT_POSITION_CONTAINED_BY", | 245 "Node.get:DOCUMENT_POSITION_CONTAINED_BY", |
| 258 "Node.get:prefix", | 246 "Node.get:prefix", |
| 259 "Node.set:prefix", | 247 "Node.set:prefix", |
| 260 "Node.get:DOCUMENT_POSITION_PRECEDING", | 248 "Node.get:DOCUMENT_POSITION_PRECEDING", |
| 261 "Node.removeEventListener", | |
| 262 "Node.get:nodeValue", | 249 "Node.get:nodeValue", |
| 263 "Node.set:nodeValue", | 250 "Node.set:nodeValue", |
| 264 "Node.get:CDATA_SECTION_NODE", | 251 "Node.get:CDATA_SECTION_NODE", |
| 265 "Node.get:nodeName", | 252 "Node.get:nodeName", |
| 266 "Node.addEventListener", | |
| 267 "Node.lookupPrefix", | 253 "Node.lookupPrefix", |
| 268 "Node.get:PROCESSING_INSTRUCTION_NODE", | 254 "Node.get:PROCESSING_INSTRUCTION_NODE", |
| 269 "Notification.dispatchEvent", | |
| 270 "Notification.addEventListener", | |
| 271 "Notification.removeEventListener", | |
|
podivilov
2012/05/24 08:29:04
Not sure why those were needed (we have another ~2
| |
| 272 "IFrameElement.get:contentDocument", | 255 "IFrameElement.get:contentDocument", |
| 273 "IFrameElement.get:contentWindow", | 256 "IFrameElement.get:contentWindow", |
| 274 "Window.get:frameElement", | 257 "Window.get:frameElement", |
| 275 "Window.get:top", | 258 ]) |
|
podivilov
2012/05/24 08:29:04
This is custom, not removed.
| |
| 259 | |
| 260 _html_library_custom = set([ | |
| 261 'Document.querySelector', | |
| 262 'Window.get:document', | |
| 263 'Window.get:top', | |
| 276 ]) | 264 ]) |
| 277 | 265 |
| 278 # Events without onEventName attributes in the IDL we want to support. | 266 # Events without onEventName attributes in the IDL we want to support. |
| 279 # We can automatically extract most event event names by checking for | 267 # We can automatically extract most event event names by checking for |
| 280 # onEventName methods in the IDL but some events aren't listed so we need | 268 # onEventName methods in the IDL but some events aren't listed so we need |
| 281 # to manually add them here so that they are easy for users to find. | 269 # to manually add them here so that they are easy for users to find. |
| 282 _html_manual_events = { | 270 _html_manual_events = { |
| 283 'Element': ['touchleave', 'touchenter', 'webkitTransitionEnd'], | 271 'Element': ['touchleave', 'touchenter', 'webkitTransitionEnd'], |
| 284 'Window': ['DOMContentLoaded'] | 272 'Window': ['DOMContentLoaded'] |
| 285 } | 273 } |
| (...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 502 if name in _html_library_renames: | 490 if name in _html_library_renames: |
| 503 target_name = _html_library_renames[name] | 491 target_name = _html_library_renames[name] |
| 504 name = interface.id + '.' + member_prefix + member | 492 name = interface.id + '.' + member_prefix + member |
| 505 if name in _html_library_renames: | 493 if name in _html_library_renames: |
| 506 target_name = _html_library_renames[name] | 494 target_name = _html_library_renames[name] |
| 507 | 495 |
| 508 if not target_name.startswith('_'): | 496 if not target_name.startswith('_'): |
| 509 if self._PrivateInHtmlLibrary(interface, member, member_prefix): | 497 if self._PrivateInHtmlLibrary(interface, member, member_prefix): |
| 510 if not target_name.startswith('$dom_'): # e.g. $dom_svgClassName | 498 if not target_name.startswith('$dom_'): # e.g. $dom_svgClassName |
| 511 target_name = '$dom_' + target_name | 499 target_name = '$dom_' + target_name |
| 512 elif implementation_class and self._ManuallyGeneratedInHtmlLibrary( | |
| 513 interface, member, member_prefix): | |
| 514 target_name = '_' + target_name | |
| 515 | 500 |
| 516 # No rename required | 501 # No rename required |
| 517 return target_name | 502 return target_name |
| 518 | 503 |
| 519 def _PrivateInHtmlLibrary(self, interface, member, member_prefix): | 504 def _PrivateInHtmlLibrary(self, interface, member, member_prefix): |
| 520 return self._Matches(interface, member, member_prefix, | 505 return self._Matches(interface, member, member_prefix, |
| 521 _private_html_members) | 506 _private_html_members) |
| 522 | 507 |
| 523 def _ManuallyGeneratedInHtmlLibrary(self, interface, member, member_prefix): | 508 def IsCustomInHtmlLibrary(self, interface, member, member_prefix=''): |
| 524 return self._Matches(interface, member, member_prefix, | 509 return self._Matches(interface, member, member_prefix, |
| 525 _manually_generated_html_members) | 510 _html_library_custom) |
| 526 | 511 |
| 527 # TODO(jacobr): this already exists | 512 # TODO(jacobr): this already exists |
| 528 def _TraverseParents(self, interface, callback): | 513 def _TraverseParents(self, interface, callback): |
| 529 for parent in interface.parents: | 514 for parent in interface.parents: |
| 530 parent_id = parent.type.id | 515 parent_id = parent.type.id |
| 531 if self._database.HasInterface(parent_id): | 516 if self._database.HasInterface(parent_id): |
| 532 parent_interface = self._database.GetInterface(parent_id) | 517 parent_interface = self._database.GetInterface(parent_id) |
| 533 callback(parent_interface) | 518 callback(parent_interface) |
| 534 self._TraverseParents(parent_interface, callback) | 519 self._TraverseParents(parent_interface, callback) |
| 535 | 520 |
| (...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 939 TYPE=self._NarrowInputType(element_type)) | 924 TYPE=self._NarrowInputType(element_type)) |
| 940 | 925 |
| 941 # TODO(sra): Use separate mixins for mutable implementations of List<T>. | 926 # TODO(sra): Use separate mixins for mutable implementations of List<T>. |
| 942 # TODO(sra): Use separate mixins for typed array implementations of List<T>. | 927 # TODO(sra): Use separate mixins for typed array implementations of List<T>. |
| 943 if self._interface.id != 'NodeList': | 928 if self._interface.id != 'NodeList': |
| 944 template_file = 'immutable_list_mixin.darttemplate' | 929 template_file = 'immutable_list_mixin.darttemplate' |
| 945 template = self._system._templates.Load(template_file) | 930 template = self._system._templates.Load(template_file) |
| 946 self._members_emitter.Emit(template, E=DartType(element_type)) | 931 self._members_emitter.Emit(template, E=DartType(element_type)) |
| 947 | 932 |
| 948 def AddAttribute(self, getter, setter): | 933 def AddAttribute(self, getter, setter): |
| 949 | 934 dom_name = DartDomNameOfAttribute(getter or setter) |
| 950 html_getter_name = self._shared.RenameInHtmlLibrary( | 935 html_getter_name = None |
| 951 self._interface, DartDomNameOfAttribute(getter), 'get:', | 936 if not self._shared.IsCustomInHtmlLibrary( |
| 937 self._interface, dom_name, 'get:'): | |
| 938 html_getter_name = self._shared.RenameInHtmlLibrary( | |
| 939 self._interface, dom_name, 'get:', | |
| 952 implementation_class=True) | 940 implementation_class=True) |
| 953 html_setter_name = self._shared.RenameInHtmlLibrary( | 941 html_setter_name = None |
| 954 self._interface, DartDomNameOfAttribute(getter), 'set:', | 942 if not self._shared.IsCustomInHtmlLibrary( |
| 943 self._interface, dom_name, 'set:'): | |
| 944 html_setter_name = self._shared.RenameInHtmlLibrary( | |
| 945 self._interface, dom_name, 'set:', | |
| 955 implementation_class=True) | 946 implementation_class=True) |
| 956 | 947 |
| 957 if not html_getter_name: | 948 if not html_getter_name: |
| 958 getter = None | 949 getter = None |
| 959 if not html_setter_name: | 950 if not html_setter_name: |
| 960 setter = None | 951 setter = None |
| 961 | 952 |
| 962 if not getter and not setter: | 953 if not getter and not setter: |
| 963 return | 954 return |
| 964 | 955 |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1035 ' native "this.$NAME = value;";\n', | 1026 ' native "this.$NAME = value;";\n', |
| 1036 HTML_NAME=html_name, | 1027 HTML_NAME=html_name, |
| 1037 NAME=attr.id, | 1028 NAME=attr.id, |
| 1038 TYPE=self._NarrowInputType(attr.type.id)) | 1029 TYPE=self._NarrowInputType(attr.type.id)) |
| 1039 | 1030 |
| 1040 def AddOperation(self, info): | 1031 def AddOperation(self, info): |
| 1041 """ | 1032 """ |
| 1042 Arguments: | 1033 Arguments: |
| 1043 info: An OperationInfo object. | 1034 info: An OperationInfo object. |
| 1044 """ | 1035 """ |
| 1036 if self._shared.IsCustomInHtmlLibrary(self._interface, info.name): | |
| 1037 return | |
| 1038 | |
| 1045 html_name = self._shared.RenameInHtmlLibrary( | 1039 html_name = self._shared.RenameInHtmlLibrary( |
| 1046 self._interface, info.name, implementation_class=True) | 1040 self._interface, info.name, implementation_class=True) |
| 1047 if not html_name: | 1041 if not html_name: |
| 1048 return | 1042 return |
| 1049 | 1043 |
| 1050 # Do we need a native body? | 1044 # Do we need a native body? |
| 1051 if html_name != info.declared_name: | 1045 if html_name != info.declared_name: |
| 1052 return_type = self._NarrowOutputType(info.type_name) | 1046 return_type = self._NarrowOutputType(info.type_name) |
| 1053 | 1047 |
| 1054 operation_emitter = self._members_emitter.Emit('$!SCOPE', | 1048 operation_emitter = self._members_emitter.Emit('$!SCOPE', |
| (...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1389 pass | 1383 pass |
| 1390 | 1384 |
| 1391 def _MethodName(self, prefix, name): | 1385 def _MethodName(self, prefix, name): |
| 1392 method_name = prefix + name | 1386 method_name = prefix + name |
| 1393 if name in self._base_members: # Avoid illegal Dart 'static override'. | 1387 if name in self._base_members: # Avoid illegal Dart 'static override'. |
| 1394 method_name = method_name + '_' + self._interface.id | 1388 method_name = method_name + '_' + self._interface.id |
| 1395 return method_name | 1389 return method_name |
| 1396 | 1390 |
| 1397 def AddAttribute(self, getter, setter): | 1391 def AddAttribute(self, getter, setter): |
| 1398 dom_name = DartDomNameOfAttribute(getter or setter) | 1392 dom_name = DartDomNameOfAttribute(getter or setter) |
| 1399 html_getter_name = self._shared.RenameInHtmlLibrary( | 1393 html_getter_name = None |
| 1400 self._interface, dom_name, 'get:', implementation_class=True) | 1394 if not self._shared.IsCustomInHtmlLibrary( |
| 1401 html_setter_name = self._shared.RenameInHtmlLibrary( | 1395 self._interface, dom_name, 'get:'): |
| 1402 self._interface, dom_name, 'set:', implementation_class=True) | 1396 html_getter_name = self._shared.RenameInHtmlLibrary( |
| 1397 self._interface, dom_name, 'get:', | |
| 1398 implementation_class=True) | |
| 1399 html_setter_name = None | |
| 1400 if not self._shared.IsCustomInHtmlLibrary( | |
| 1401 self._interface, dom_name, 'set:'): | |
| 1402 html_setter_name = self._shared.RenameInHtmlLibrary( | |
| 1403 self._interface, dom_name, 'set:', | |
| 1404 implementation_class=True) | |
| 1403 | 1405 |
| 1404 if getter and html_getter_name: | 1406 if getter and html_getter_name: |
| 1405 self._AddGetter(getter, html_getter_name) | 1407 self._AddGetter(getter, html_getter_name) |
| 1406 if setter and html_setter_name: | 1408 if setter and html_setter_name: |
| 1407 self._AddSetter(setter, html_setter_name) | 1409 self._AddSetter(setter, html_setter_name) |
| 1408 | 1410 |
| 1409 def _AddGetter(self, attr, html_name): | 1411 def _AddGetter(self, attr, html_name): |
| 1410 self._members_emitter.Emit( | 1412 self._members_emitter.Emit( |
| 1411 '\n' | 1413 '\n' |
| 1412 ' $TYPE get $(HTML_NAME)() => _wrap($(THIS).$DOM_NAME);\n', | 1414 ' $TYPE get $(HTML_NAME)() => _wrap($(THIS).$DOM_NAME);\n', |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1519 ' }\n', | 1521 ' }\n', |
| 1520 THIS=self.DomObjectName(), | 1522 THIS=self.DomObjectName(), |
| 1521 TYPE=DartType(element_type), | 1523 TYPE=DartType(element_type), |
| 1522 METHOD=method_name) | 1524 METHOD=method_name) |
| 1523 | 1525 |
| 1524 def AddOperation(self, info): | 1526 def AddOperation(self, info): |
| 1525 """ | 1527 """ |
| 1526 Arguments: | 1528 Arguments: |
| 1527 info: An OperationInfo object. | 1529 info: An OperationInfo object. |
| 1528 """ | 1530 """ |
| 1531 if self._shared.IsCustomInHtmlLibrary(self._interface, info.name): | |
| 1532 return | |
| 1533 | |
| 1529 html_name = self._shared.RenameInHtmlLibrary( | 1534 html_name = self._shared.RenameInHtmlLibrary( |
| 1530 self._interface, info.name, implementation_class=True) | 1535 self._interface, info.name, implementation_class=True) |
| 1531 | 1536 |
| 1532 if not html_name: | 1537 if not html_name: |
| 1533 return | 1538 return |
| 1534 | 1539 |
| 1535 arguments = self._UnwrappedParameters(info, len(info.param_infos)) | 1540 arguments = self._UnwrappedParameters(info, len(info.param_infos)) |
| 1536 function_call = '%s.%s(%s)' % (self.DomObjectName(), info.name, arguments) | 1541 function_call = '%s.%s(%s)' % (self.DomObjectName(), info.name, arguments) |
| 1537 if info.type_name != 'void': | 1542 if info.type_name != 'void': |
| 1538 # We could place the logic for handling Document directly in _wrap | 1543 # We could place the logic for handling Document directly in _wrap |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 1567 Collect(database.GetInterface(parent.type.id), | 1572 Collect(database.GetInterface(parent.type.id), |
| 1568 seen, collected) | 1573 seen, collected) |
| 1569 | 1574 |
| 1570 inheritance_closure = {} | 1575 inheritance_closure = {} |
| 1571 for interface in database.GetInterfaces(): | 1576 for interface in database.GetInterfaces(): |
| 1572 seen = set() | 1577 seen = set() |
| 1573 collected = [] | 1578 collected = [] |
| 1574 Collect(interface, seen, collected) | 1579 Collect(interface, seen, collected) |
| 1575 inheritance_closure[interface.id] = collected | 1580 inheritance_closure[interface.id] = collected |
| 1576 return inheritance_closure | 1581 return inheritance_closure |
| OLD | NEW |