mirror of
https://github.com/react/react-native.git
synced 2026-09-28 21:33:11 +08:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56091 Changelog: [Internal] When qualifying a member, prevent taking itself as a target for qualification, to prevent types referencing themselves. Reviewed By: cipolleschi Differential Revision: D96455842 fbshipit-source-id: 2ed59a4688d0daa98db603a734283f41ffbe4ead
271 lines
10 KiB
Python
271 lines
10 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Generic
|
|
|
|
from natsort import natsorted
|
|
|
|
from ..member import FriendMember, Member, TypedefMember
|
|
from ..utils import parse_qualified_path, qualify_type_str
|
|
from .base_scope_kind import _natsort_key, ScopeKindT
|
|
from .enum_scope_kind import EnumScopeKind
|
|
from .namespace_scope_kind import NamespaceScopeKind
|
|
from .struct_like_scope_kind import StructLikeScopeKind
|
|
|
|
|
|
class Scope(Generic[ScopeKindT]):
|
|
def __init__(self, kind: ScopeKindT, name: str | None = None) -> None:
|
|
self.name: str | None = name
|
|
self.kind: ScopeKindT = kind
|
|
self.parent_scope: Scope | None = None
|
|
self.inner_scopes: dict[str, Scope] = {}
|
|
self.location: str | None = None
|
|
self._members: list[Member] = []
|
|
self._private_typedefs: dict[str, TypedefMember] = {}
|
|
self._qualifying_member: Member | None = None
|
|
|
|
def get_qualified_name(self) -> str:
|
|
"""
|
|
Get the qualified name of the scope, with template arguments qualified.
|
|
"""
|
|
path = []
|
|
current_scope = self
|
|
while current_scope is not None:
|
|
if current_scope.name is not None:
|
|
name = current_scope.name
|
|
if (
|
|
isinstance(current_scope.kind, StructLikeScopeKind)
|
|
and current_scope.kind.specialization_args is not None
|
|
):
|
|
name = (
|
|
f"{name}<{', '.join(current_scope.kind.specialization_args)}>"
|
|
)
|
|
path.append(name)
|
|
current_scope = current_scope.parent_scope
|
|
path.reverse()
|
|
return "::".join(path)
|
|
|
|
def _get_base_name(self, name: str) -> str:
|
|
"""Strip template arguments from a name for scope lookup."""
|
|
angle_idx = name.find("<")
|
|
return name[:angle_idx] if angle_idx != -1 else name
|
|
|
|
def qualify_name(self, name: str | None) -> str | None:
|
|
"""
|
|
Qualify a name with the relevant scope if possible.
|
|
Handles template arguments by stripping them for lookup but preserving
|
|
them in the output.
|
|
"""
|
|
if not name:
|
|
return None
|
|
|
|
path = parse_qualified_path(name)
|
|
if not path:
|
|
return None
|
|
|
|
current_scope = self
|
|
# Walk up to find a scope that contains the first path segment
|
|
# Check both inner_scopes AND members (for type aliases, etc.)
|
|
base_first = self._get_base_name(path[0])
|
|
while current_scope is not None:
|
|
# Check if it's an inner scope
|
|
if base_first in current_scope.inner_scopes:
|
|
break
|
|
|
|
# Skip self-qualification if name matches current scope's name
|
|
if (
|
|
current_scope.name
|
|
and self._get_base_name(current_scope.name) == base_first
|
|
):
|
|
current_scope = current_scope.parent_scope
|
|
continue
|
|
|
|
# Check if it's a member (type alias, variable, etc.)
|
|
for m in current_scope._members:
|
|
if m is current_scope._qualifying_member:
|
|
continue
|
|
if m.name == base_first and not isinstance(m, FriendMember):
|
|
prefix = current_scope.get_qualified_name()
|
|
return f"{prefix}::{name}" if prefix else name
|
|
|
|
# Check private typedefs: substitute with the expanded definition
|
|
if len(path) == 1 and base_first in current_scope._private_typedefs:
|
|
return current_scope._private_typedefs[base_first].get_value()
|
|
|
|
current_scope = current_scope.parent_scope
|
|
|
|
if current_scope is None:
|
|
return None
|
|
|
|
# Remember the scope where we found the first segment — its qualified
|
|
# name is the prefix that must precede the matched path segments.
|
|
anchor_scope = current_scope
|
|
|
|
# Walk down through the path, tracking matched segments with original template args
|
|
matched_segments: list[str] = []
|
|
for i, path_segment in enumerate(path):
|
|
base_name = self._get_base_name(path_segment)
|
|
if base_name in current_scope.inner_scopes:
|
|
matched_segments.append(path_segment)
|
|
current_scope = current_scope.inner_scopes[base_name]
|
|
elif any(
|
|
m.name == base_name
|
|
and not isinstance(m, FriendMember)
|
|
and m is not current_scope._qualifying_member
|
|
for m in current_scope._members
|
|
) or any(
|
|
any(m.name == base_name for m in inner._members)
|
|
for inner in current_scope.inner_scopes.values()
|
|
if isinstance(inner.kind, EnumScopeKind)
|
|
):
|
|
# Found as a member (or as an unscoped enum value accessible
|
|
# from the parent scope), assume following segments exist
|
|
prefix = "::".join(matched_segments)
|
|
suffix = "::".join(path[i:])
|
|
anchor_prefix = anchor_scope.get_qualified_name()
|
|
if prefix:
|
|
if anchor_prefix:
|
|
return f"{anchor_prefix}::{prefix}::{suffix}"
|
|
return f"{prefix}::{suffix}"
|
|
else:
|
|
if anchor_prefix:
|
|
return f"{anchor_prefix}::{suffix}"
|
|
return suffix
|
|
else:
|
|
# Segment not found as an inner scope or a real member of
|
|
# the current scope. When inside a struct-like scope this
|
|
# typically means Doxygen's refid-based qualification
|
|
# incorrectly placed a type under a compound that does not
|
|
# actually contain it — for example a friend declaration or
|
|
# an inherited constructor reported as a member ref. Try
|
|
# to re-qualify from the remaining unmatched segments so the
|
|
# type resolves against the broader scope hierarchy.
|
|
if isinstance(current_scope.kind, StructLikeScopeKind):
|
|
remaining = "::".join(path[i:])
|
|
return self.qualify_name(remaining)
|
|
return None
|
|
|
|
# Return qualified name with preserved template arguments
|
|
prefix = anchor_scope.get_qualified_name()
|
|
if prefix:
|
|
return f"{prefix}::{'::'.join(matched_segments)}"
|
|
else:
|
|
return "::".join(matched_segments)
|
|
|
|
def add_private_typedef(self, member: TypedefMember) -> None:
|
|
"""
|
|
Store a private typedef for use during type resolution.
|
|
|
|
Private typedefs are not included in the snapshot output, but their
|
|
definitions are substituted for references to them in public members.
|
|
"""
|
|
self._private_typedefs[member.name] = member
|
|
|
|
def add_member(self, member: Member | None) -> None:
|
|
"""
|
|
Add a member to the scope.
|
|
"""
|
|
if member is None:
|
|
return
|
|
self._members.append(member)
|
|
|
|
def get_members(self) -> list[Member]:
|
|
"""
|
|
Get all members of the scope.
|
|
"""
|
|
return self._members
|
|
|
|
def close(self) -> None:
|
|
"""
|
|
Close the scope by setting the kind of all temporary scopes.
|
|
"""
|
|
# Qualify specialization args early so that members and inner scopes
|
|
# see the fully-qualified name when they call get_qualified_name().
|
|
if (
|
|
isinstance(self.kind, StructLikeScopeKind)
|
|
and self.kind.specialization_args is not None
|
|
and self.parent_scope is not None
|
|
):
|
|
self.kind.specialization_args = [
|
|
qualify_type_str(arg, self.parent_scope)
|
|
for arg in self.kind.specialization_args
|
|
]
|
|
|
|
for typedef in self._private_typedefs.values():
|
|
typedef.close(self)
|
|
|
|
for member in self.get_members():
|
|
self._qualifying_member = member
|
|
member.close(self)
|
|
self._qualifying_member = None
|
|
|
|
self.kind.close(self)
|
|
|
|
for _, inner_scope in self.inner_scopes.items():
|
|
inner_scope.close()
|
|
|
|
def to_string(self) -> str:
|
|
"""
|
|
Get the string representation of the scope.
|
|
"""
|
|
# Get this scope's content (e.g., class members, free functions, ...)
|
|
this_content = self.kind.to_string(self)
|
|
|
|
# Separate inner scopes into namespaces and non-namespaces
|
|
# Keep (scope, string) tuples to sort by scope properties
|
|
namespace_scope_items: list[tuple[Scope, str]] = []
|
|
non_namespace_scope_items: list[tuple[Scope, str]] = []
|
|
|
|
for _, inner_scope in self.inner_scopes.items():
|
|
if inner_scope.name is None:
|
|
continue
|
|
inner_str = inner_scope.to_string()
|
|
if not inner_str.strip():
|
|
continue
|
|
|
|
if isinstance(inner_scope.kind, NamespaceScopeKind):
|
|
namespace_scope_items.append((inner_scope, inner_str))
|
|
else:
|
|
non_namespace_scope_items.append((inner_scope, inner_str))
|
|
|
|
# Sort non-namespace scopes by depth (fewer :: first) then by string
|
|
def scope_sort_key(item: tuple[Scope, str]) -> tuple:
|
|
scope, string = item
|
|
depth = scope.get_qualified_name().count("::")
|
|
return (depth, _natsort_key(string))
|
|
|
|
non_namespace_scope_items.sort(key=scope_sort_key)
|
|
non_namespace_scope_strings = [s for _, s in non_namespace_scope_items]
|
|
namespace_scope_strings = [s for _, s in namespace_scope_items]
|
|
|
|
# Build result:
|
|
# 1. Free members / this scope's content first
|
|
# 2. Non-namespace inner scopes (classes, structs, enums), sorted by depth
|
|
# 3. Namespace inner scopes, each separated by "\n\n\n" (two blank lines)
|
|
|
|
local_parts = []
|
|
if this_content.strip():
|
|
local_parts.append(this_content)
|
|
local_parts.extend(non_namespace_scope_strings)
|
|
|
|
# NOTE: Don't sort local_parts together - free members should come first
|
|
local_block = "\n\n".join(local_parts)
|
|
|
|
# Combine with namespace scopes using one more blank line for clearer separation
|
|
all_blocks = []
|
|
if local_block.strip():
|
|
all_blocks.append(local_block)
|
|
all_blocks.extend(natsorted(namespace_scope_strings))
|
|
|
|
return "\n\n\n".join(all_blocks).strip()
|
|
|
|
def print(self):
|
|
"""
|
|
Print a scope and its contents.
|
|
"""
|
|
print(self.to_string())
|