diff --git a/scripts/cxx-api/parser/__init__.py b/scripts/cxx-api/parser/__init__.py
index 5a3940f6558e..9dade47a0352 100644
--- a/scripts/cxx-api/parser/__init__.py
+++ b/scripts/cxx-api/parser/__init__.py
@@ -4,7 +4,6 @@
# LICENSE file in the root directory of this source tree.
from .main import build_snapshot
-from .member import FunctionMember, FunctionModifiers
from .path_utils import get_repo_root
-__all__ = ["build_snapshot", "FunctionMember", "FunctionModifiers", "get_repo_root"]
+__all__ = ["build_snapshot", "get_repo_root"]
diff --git a/scripts/cxx-api/parser/main.py b/scripts/cxx-api/parser/main.py
index 40fce52d7d66..1674484cd4b8 100644
--- a/scripts/cxx-api/parser/main.py
+++ b/scripts/cxx-api/parser/main.py
@@ -6,6 +6,7 @@
from __future__ import annotations
import os
+import re
from pprint import pprint
from doxmlparser import compound, index
@@ -20,7 +21,7 @@
from .scope import StructLikeScopeKind
from .snapshot import Snapshot
from .template import Template
-from .utils import parse_qualified_path
+from .utils import Argument, extract_qualifiers, parse_qualified_path
def resolve_ref_text_name(type_def: compound.refTextType) -> str:
@@ -206,6 +207,35 @@ def get_variable_member(
)
+def get_doxygen_params(
+ function_def: compound.MemberdefType,
+) -> list[tuple[str | None, str, str | None, str | None]] | None:
+ """
+ Extract structured parameter information from doxygen elements.
+
+ Returns a list of Argument tuples (qualifiers, type, name, default_value),
+ or None if no elements are available.
+ """
+ params = function_def.param
+ if not params:
+ return None
+
+ arguments: list[Argument] = []
+ for param in params:
+ param_type = (
+ resolve_ref_text_name(param.get_type()).strip() if param.get_type() else ""
+ )
+ param_name = param.declname or param.defname or None
+ param_default = (
+ resolve_ref_text_name(param.defval).strip() if param.defval else None
+ )
+
+ qualifiers, core_type = extract_qualifiers(param_type)
+ arguments.append((qualifiers, core_type, param_name, param_default))
+
+ return arguments
+
+
def get_function_member(
function_def: compound.MemberdefType,
visibility: str,
@@ -217,10 +247,15 @@ def get_function_member(
function_name = function_def.get_name()
function_type = resolve_ref_text_name(function_def.get_type())
function_arg_string = function_def.get_argsstring()
- function_virtual = (
- function_def.get_virt() == "virtual"
- or function_def.get_virt() == "pure-virtual"
- )
+ is_pure_virtual = function_def.get_virt() == "pure-virtual"
+ function_virtual = function_def.get_virt() == "virtual" or is_pure_virtual
+
+ # Doxygen incorrectly merges "=0" into the return type for pure-virtual
+ # functions using trailing return types (e.g. "auto f() -> T = 0").
+ # Strip the trailing "=0" from the type string.
+ function_type = re.sub(r"\s*=\s*0\s*$", "", function_type)
+
+ doxygen_params = get_doxygen_params(function_def)
function = FunctionMember(
function_name,
@@ -228,7 +263,9 @@ def get_function_member(
visibility,
function_arg_string,
function_virtual,
+ is_pure_virtual,
is_static,
+ doxygen_params,
)
function.add_template(get_template_params(function_def))
diff --git a/scripts/cxx-api/parser/member.py b/scripts/cxx-api/parser/member.py
index 7bfddf8062e5..d6930ba55e0f 100644
--- a/scripts/cxx-api/parser/member.py
+++ b/scripts/cxx-api/parser/member.py
@@ -5,12 +5,21 @@
from __future__ import annotations
-import re
from abc import ABC, abstractmethod
-from dataclasses import dataclass
from typing import TYPE_CHECKING
from .template import Template, TemplateList
+from .utils import (
+ Argument,
+ format_arguments,
+ format_parsed_type,
+ parse_arg_string,
+ parse_function_pointer_argstring,
+ parse_type_with_argstrings,
+ qualify_arguments,
+ qualify_parsed_type,
+ qualify_type_str,
+)
if TYPE_CHECKING:
from .scope import Scope
@@ -90,10 +99,14 @@ def __init__(
self.is_mutable: bool = is_mutable
self.definition: str = definition
self.argstring: str | None = argstring
+ self._fp_arguments: list[Argument] = (
+ parse_function_pointer_argstring(argstring) if argstring else []
+ )
+ self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type)
def close(self, scope: Scope):
- # TODO: handle unqualified references
- pass
+ self._fp_arguments = qualify_arguments(self._fp_arguments, scope)
+ self._parsed_type = qualify_parsed_type(self._parsed_type, scope)
def _is_function_pointer(self) -> bool:
"""Check if this variable is a function pointer type."""
@@ -125,15 +138,17 @@ def to_string(
result += "mutable "
if self._is_function_pointer():
+ formatted_args = format_arguments(self._fp_arguments)
+ qualified_type = format_parsed_type(self._parsed_type)
# Function pointer types: argstring is ")(args...)"
# If type already contains "(*", e.g. "void *(*" or "void(*", use directly
# Otherwise add "(*" to form proper function pointer syntax
- if "(*" in self.type:
- result += f"{self.type}{name}{self.argstring}"
+ if "(*" in qualified_type:
+ result += f"{qualified_type}{name})({formatted_args})"
else:
- result += f"{self.type} (*{name}{self.argstring}"
+ result += f"{qualified_type} (*{name})({formatted_args})"
else:
- result += f"{self.type} {name}"
+ result += f"{format_parsed_type(self._parsed_type)} {name}"
if self.value is not None and (self.is_const or self.is_constexpr):
result += f" = {self.value}"
@@ -143,32 +158,6 @@ def to_string(
return result
-@dataclass
-class FunctionModifiers:
- """Parsed function modifiers that appear after the parameter list."""
-
- is_const: bool = False
- is_override: bool = False
- is_final: bool = False
- is_noexcept: bool = False
- noexcept_expr: str | None = None
- is_pure_virtual: bool = False
- is_default: bool = False
- is_delete: bool = False
-
-
-# Pattern for function pointer / pointer to member / reference to array:
-# Matches: (*name), (Class::*name), (&name), (*name)[N]
-# Group 1: the identifier name
-_FUNC_PTR_PATTERN = re.compile(
- r"\(\s*" # Opening paren
- r"(?:[a-zA-Z_][a-zA-Z0-9_]*\s*::\s*)?" # Optional Class::
- r"[*&]\s*" # * or &
- r"([a-zA-Z_][a-zA-Z0-9_]*)" # Capture: identifier name
- r"\s*\)" # Closing paren
-)
-
-
class FunctionMember(Member):
def __init__(
self,
@@ -177,20 +166,31 @@ def __init__(
visibility: str,
arg_string: str,
is_virtual: bool,
+ is_pure_virtual: bool,
is_static: bool,
+ doxygen_params: list[Argument] | None = None,
) -> None:
super().__init__(name, visibility)
self.type: str = type
self.is_virtual: bool = is_virtual
self.is_static: bool = is_static
- self.arguments, self.modifiers = FunctionMember.parse_arg_string(arg_string)
+ parsed_arguments, self.modifiers = parse_arg_string(arg_string)
+ self.arguments = (
+ doxygen_params if doxygen_params is not None else parsed_arguments
+ )
+
+ # Doxygen signals pure-virtual via the virt attribute, but the arg string
+ # may not contain "= 0" (e.g. trailing return type syntax), so the
+ # modifiers parsed from the arg string may miss it. Propagate the flag.
+ if is_pure_virtual:
+ self.modifiers.is_pure_virtual = True
self.is_const = self.modifiers.is_const
self.is_override = self.modifiers.is_override
def close(self, scope: Scope):
- # TODO: handle unqualified references
- pass
+ self.type = qualify_type_str(self.type, scope)
+ self.arguments = qualify_arguments(self.arguments, scope)
def to_string(
self,
@@ -218,19 +218,7 @@ def to_string(
if self.type:
result += f"{self.type} "
- result += f"{name}("
-
- for i, (arg_type, arg_name, arg_default) in enumerate(self.arguments):
- if arg_name:
- result += f"{arg_type} {arg_name}"
- else:
- result += arg_type
- if arg_default:
- result += f" = {arg_default}"
- if i < len(self.arguments) - 1:
- result += ", "
-
- result += ")"
+ result += f"{name}({format_arguments(self.arguments)})"
if self.modifiers.is_const:
result += " const"
@@ -257,328 +245,28 @@ def to_string(
result += ";"
return result
- @staticmethod
- def _find_matching_paren(s: str, start: int = 0) -> int:
- """Find the index of the closing parenthesis matching the opening one at start.
-
- Handles nested parentheses and angle brackets (for templates).
- Returns -1 if no matching parenthesis is found.
- """
- if start >= len(s) or s[start] != "(":
- return -1
-
- depth = 0
- angle_depth = 0
- i = start
-
- while i < len(s):
- c = s[i]
- if c == "<":
- angle_depth += 1
- elif c == ">":
- angle_depth -= 1
- elif c == "(":
- depth += 1
- elif c == ")":
- depth -= 1
- if depth == 0:
- return i
- i += 1
-
- return -1
-
- @staticmethod
- def _find_matching_angle(s: str, start: int = 0) -> int:
- """Find the index of the closing angle bracket matching the opening one at start.
-
- Handles nested angle brackets and parentheses.
- Returns -1 if no matching bracket is found.
- """
- if start >= len(s) or s[start] != "<":
- return -1
-
- depth = 0
- paren_depth = 0
- i = start
-
- while i < len(s):
- c = s[i]
- if c == "(":
- paren_depth += 1
- elif c == ")":
- paren_depth -= 1
- elif c == "<" and paren_depth == 0:
- depth += 1
- elif c == ">" and paren_depth == 0:
- depth -= 1
- if depth == 0:
- return i
- i += 1
-
- return -1
-
- @staticmethod
- def _split_arguments(args_str: str) -> list[str]:
- """Split arguments by comma, respecting nested structures.
-
- Handles:
- - Nested parentheses: std::function
- - Nested angle brackets: std::map
- - Brace initializers: std::vector v = {1, 2, 3}
- """
- result = []
- current = []
- paren_depth = 0
- angle_depth = 0
- brace_depth = 0
-
- for c in args_str:
- if c == "<":
- angle_depth += 1
- elif c == ">":
- angle_depth = max(0, angle_depth - 1)
- elif c == "(":
- paren_depth += 1
- elif c == ")":
- paren_depth = max(0, paren_depth - 1)
- elif c == "{":
- brace_depth += 1
- elif c == "}":
- brace_depth = max(0, brace_depth - 1)
- elif (
- c == "," and paren_depth == 0 and angle_depth == 0 and brace_depth == 0
- ):
- result.append("".join(current).strip())
- current = []
- continue
- current.append(c)
-
- if current:
- result.append("".join(current).strip())
-
- return [arg for arg in result if arg]
-
- # C++ reserved keywords that serve as type qualifiers/specifiers.
- # When ALL tokens in the prefix (everything before the last token) are
- # from this set, the last token must be part of the type, not a name.
- # Primitive type names (int, bool, char, etc.) are intentionally excluded
- # to avoid ambiguity with common named params like "int x" or "bool flag".
- _CPP_TYPE_QUALIFIERS = frozenset(
- {
- "const",
- "volatile",
- "mutable",
- "unsigned",
- "signed",
- "long",
- "short",
- }
- )
-
- @staticmethod
- def _prefix_is_all_qualifiers(prefix: str) -> bool:
- """Check if all tokens in the prefix are type qualifiers/specifiers.
-
- When the prefix consists entirely of reserved qualifiers (e.g., "const",
- "const unsigned"), the following token cannot be a parameter name — it
- must be completing the type.
- """
- return all(t in FunctionMember._CPP_TYPE_QUALIFIERS for t in prefix.split())
-
- @staticmethod
- def _looks_like_type_token(token: str) -> bool:
- """Check if a token ends with type-related punctuation.
-
- Returns True if the token ends with pointer, reference, template closer,
- or array bracket — indicating it's part of a type, not a name.
- """
- return (
- token.endswith("*")
- or token.endswith("&")
- or token.endswith(">")
- or token.endswith("]")
- )
-
- @staticmethod
- def _parse_single_argument(arg: str) -> tuple[str, str, str | None]:
- """Parse a single C++ argument into (type, name, default_value).
-
- Handles complex cases:
- - Regular: "int x" -> ("int", "x", None)
- - With default: "int x = 5" -> ("int", "x", "5")
- - Function pointer: "int (*callback)(int, int)" -> ("int (*)(int, int)", "callback", None)
- - Pointer to member: "void (Class::*method)()" -> ("void (Class::*)()", "method", None)
- - Reference to array: "int (&arr)[10]" -> ("int (&)[10]", "arr", None)
- - No name (void): "void" -> ("void", "", None)
- - Unnamed multi-token: "const bool" -> ("const bool", "", None)
- """
- arg = arg.strip()
- if not arg:
- return ("", "", None)
-
- default_value: str | None = None
- base_arg = arg
-
- # Handle default values: split on '=' but respect nested structures
- eq_pos = FunctionMember._find_default_value_start(arg)
- if eq_pos != -1:
- base_arg = arg[:eq_pos].strip()
- default_value = arg[eq_pos + 1 :].strip()
-
- # Try to match function pointer / pointer to member / reference to array pattern
- match = _FUNC_PTR_PATTERN.search(base_arg)
- if match:
- name = match.group(1)
- # Reconstruct type by removing the name from the pattern
- type_str = base_arg[: match.start(1)] + base_arg[match.end(1) :]
- return (type_str.strip(), name, default_value)
-
- # Regular case: last token is the name
- parts = base_arg.rsplit(None, 1)
- if len(parts) == 2:
- prefix, potential_name = parts
- if FunctionMember._looks_like_type_token(
- potential_name
- ) or FunctionMember._prefix_is_all_qualifiers(prefix):
- return (base_arg, "", default_value)
- return (prefix, potential_name, default_value)
- else:
- return (base_arg, "", default_value)
-
- @staticmethod
- def _find_default_value_start(arg: str) -> int:
- """Find the position of '=' that starts a default value.
-
- Returns -1 if no default value found.
- Ignores '=' inside nested structures like templates or lambdas.
- """
- paren_depth = 0
- angle_depth = 0
- brace_depth = 0
-
- i = 0
- while i < len(arg):
- c = arg[i]
- if c == "<":
- angle_depth += 1
- elif c == ">":
- angle_depth = max(0, angle_depth - 1)
- elif c == "(":
- paren_depth += 1
- elif c == ")":
- paren_depth = max(0, paren_depth - 1)
- elif c == "{":
- brace_depth += 1
- elif c == "}":
- brace_depth = max(0, brace_depth - 1)
- elif (
- c == "=" and paren_depth == 0 and angle_depth == 0 and brace_depth == 0
- ):
- return i
- i += 1
-
- return -1
-
- @staticmethod
- def _parse_modifiers(modifiers_str: str) -> FunctionModifiers:
- """Parse function modifiers after the parameter list.
-
- Handles: const, override, final, noexcept, noexcept(expr), = 0, = default, = delete
- """
- result = FunctionModifiers()
- s = modifiers_str.strip()
-
- # Handle = 0, = default, = delete
- eq_match = re.search(r"=\s*(0|default|delete)\s*$", s)
- if eq_match:
- value = eq_match.group(1)
- if value == "0":
- result.is_pure_virtual = True
- elif value == "default":
- result.is_default = True
- elif value == "delete":
- result.is_delete = True
- s = s[: eq_match.start()].strip()
-
- # Handle noexcept with optional expression
- noexcept_match = re.search(r"\bnoexcept\b", s)
- if noexcept_match:
- result.is_noexcept = True
- # Check for noexcept(expr)
- rest = s[noexcept_match.end() :]
- rest_stripped = rest.lstrip()
- if rest_stripped.startswith("("):
- paren_end = FunctionMember._find_matching_paren(rest_stripped, 0)
- if paren_end != -1:
- result.noexcept_expr = rest_stripped[1:paren_end].strip()
- # Remove noexcept and its expression from the string for further parsing
- s = s[: noexcept_match.start()] + s[noexcept_match.end() :]
- if result.noexcept_expr:
- # Also remove the (expr) part
- s = re.sub(r"\([^)]*\)", "", s, count=1)
-
- # Parse remaining tokens
- tokens = s.split()
- for token in tokens:
- if token == "const":
- result.is_const = True
- elif token == "override":
- result.is_override = True
- elif token == "final":
- result.is_final = True
-
- return result
-
- @staticmethod
- def parse_arg_string(
- arg_string: str,
- ) -> tuple[list[tuple[str, str, str | None]], FunctionModifiers]:
- """Parse a C++ function argument string.
-
- Args:
- arg_string: String in format "(type1 arg1, type2 arg2 = default) [modifiers]"
-
- Returns:
- Tuple of (arguments, modifiers) where:
- - arguments: list of (type, name, default_value) tuples
- - modifiers: FunctionModifiers dataclass with parsed modifier flags
- """
- arg_string = arg_string.strip()
-
- if not arg_string.startswith("("):
- return ([], FunctionModifiers())
-
- close_paren = FunctionMember._find_matching_paren(arg_string, 0)
- if close_paren == -1:
- return ([], FunctionModifiers())
-
- args_content = arg_string[1:close_paren].strip()
- modifiers_str = arg_string[close_paren + 1 :]
-
- arguments: list[tuple[str, str, str | None]] = []
- if args_content:
- for arg in FunctionMember._split_arguments(args_content):
- parsed = FunctionMember._parse_single_argument(arg)
- if parsed[0] or parsed[1]:
- arguments.append(parsed)
-
- modifiers = FunctionMember._parse_modifiers(modifiers_str)
-
- return (arguments, modifiers)
-
class TypedefMember(Member):
def __init__(
self, name: str, type: str, argstring: str | None, visibility: str, keyword: str
) -> None:
super().__init__(name, visibility)
- self.type: str = type
self.keyword: str = keyword
self.argstring: str | None = argstring
+ # Parse function pointer argstrings (e.g. ")(int x, float y)")
+ self._fp_arguments: list[Argument] = (
+ parse_function_pointer_argstring(argstring) if argstring else []
+ )
+
+ # Parse inline function signatures in the type so that argument
+ # lists are stored as structured data, not raw strings.
+ self._parsed_type: list[str | list[Argument]] = parse_type_with_argstrings(type)
+ self.type: str = type
+
def close(self, scope: Scope):
- # TODO: handle unqualified references
- pass
+ self._fp_arguments = qualify_arguments(self._fp_arguments, scope)
+ self._parsed_type = qualify_parsed_type(self._parsed_type, scope)
def _is_function_pointer(self) -> bool:
"""Check if this typedef is a function pointer type."""
@@ -602,14 +290,16 @@ def to_string(
result += self.keyword
if self.keyword == "using":
- result += f" {name} = {self.type};"
+ result += f" {name} = {format_parsed_type(self._parsed_type)};"
elif self._is_function_pointer():
+ formatted_args = format_arguments(self._fp_arguments)
+ qualified_type = format_parsed_type(self._parsed_type)
# Function pointer typedef: "typedef return_type (*name)(args);"
# type is e.g. "void(*", argstring is ")(args...)"
- if "(*" in self.type:
- result += f" {self.type}{name}{self.argstring};"
+ if "(*" in qualified_type:
+ result += f" {qualified_type}{name})({formatted_args});"
else:
- result += f" {self.type}(*{name}{self.argstring};"
+ result += f" {qualified_type}(*{name})({formatted_args});"
else:
result += f" {self.type} {name};"
diff --git a/scripts/cxx-api/parser/scope.py b/scripts/cxx-api/parser/scope.py
index c68803c9230a..a6086094f3ea 100644
--- a/scripts/cxx-api/parser/scope.py
+++ b/scripts/cxx-api/parser/scope.py
@@ -178,6 +178,64 @@ def _get_base_name(self, name: str) -> str:
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
+ base_first = self._get_base_name(path[0])
+ while (
+ current_scope is not None and base_first not in current_scope.inner_scopes
+ ):
+ 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 for m in current_scope._members):
+ # Found as a member, assume following segments exist in the scope
+ 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:
+ 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_member(self, member: Member | None) -> None:
"""
Add a member to the scope.
diff --git a/scripts/cxx-api/parser/utils/__init__.py b/scripts/cxx-api/parser/utils/__init__.py
new file mode 100644
index 000000000000..b711a41f4e2b
--- /dev/null
+++ b/scripts/cxx-api/parser/utils/__init__.py
@@ -0,0 +1,32 @@
+# 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 .argument_parsing import (
+ Argument,
+ extract_qualifiers,
+ format_arguments,
+ format_parsed_type,
+ FunctionModifiers,
+ parse_arg_string,
+ parse_function_pointer_argstring,
+ parse_type_with_argstrings,
+)
+from .qualified_path import parse_qualified_path
+from .type_qualification import qualify_arguments, qualify_parsed_type, qualify_type_str
+
+__all__ = [
+ "Argument",
+ "extract_qualifiers",
+ "format_arguments",
+ "format_parsed_type",
+ "FunctionModifiers",
+ "parse_arg_string",
+ "parse_function_pointer_argstring",
+ "parse_qualified_path",
+ "parse_type_with_argstrings",
+ "qualify_arguments",
+ "qualify_parsed_type",
+ "qualify_type_str",
+]
diff --git a/scripts/cxx-api/parser/utils/argument_parsing.py b/scripts/cxx-api/parser/utils/argument_parsing.py
new file mode 100644
index 000000000000..2e0d0bb47851
--- /dev/null
+++ b/scripts/cxx-api/parser/utils/argument_parsing.py
@@ -0,0 +1,531 @@
+# 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
+
+import re
+from dataclasses import dataclass
+
+# Type alias for a parsed argument tuple:
+# (qualifiers, type, name, default_value)
+# - qualifiers: leading CV-qualifiers like "const", "const volatile", or None
+# - type: the core type without leading CV-qualifiers
+# - name: the parameter name, or None if unnamedą
+# - default_value: the default value expression, or None
+Argument = tuple[str | None, str, str | None, str | None]
+
+
+@dataclass
+class FunctionModifiers:
+ """Parsed function modifiers that appear after the parameter list."""
+
+ is_const: bool = False
+ is_override: bool = False
+ is_final: bool = False
+ is_noexcept: bool = False
+ noexcept_expr: str | None = None
+ is_pure_virtual: bool = False
+ is_default: bool = False
+ is_delete: bool = False
+
+
+# Pattern for function pointer / pointer to member / reference to array:
+# Matches: (*name), (Class::*name), (&name), (*name)[N]
+# Group 1: the identifier name
+_FUNC_PTR_PATTERN = re.compile(
+ r"\(\s*" # Opening paren
+ r"(?:[a-zA-Z_][a-zA-Z0-9_]*\s*::\s*)?" # Optional Class::
+ r"[*&]\s*" # * or &
+ r"([a-zA-Z_][a-zA-Z0-9_]*)" # Capture: identifier name
+ r"\s*\)" # Closing paren
+)
+
+# CV-qualifiers that should be extracted into the qualifiers field.
+# These precede a type but are not part of the type name itself, so
+# they must be separated to allow unambiguous type qualification later.
+_CV_QUALIFIERS = frozenset({"const", "volatile", "mutable"})
+
+# Type specifiers that ARE part of the type name (e.g. "unsigned long").
+# They are kept in the type field, but are still used by
+# _prefix_is_all_qualifiers to detect patterns like "unsigned long"
+# where the last token completes the type rather than naming a parameter.
+_TYPE_SPECIFIERS = frozenset({"unsigned", "signed", "long", "short"})
+
+# Union of CV-qualifiers and type specifiers. Used by
+# _prefix_is_all_qualifiers to decide whether a prefix consists entirely
+# of qualifier/specifier keywords.
+_CPP_TYPE_QUALIFIERS = _CV_QUALIFIERS | _TYPE_SPECIFIERS
+
+
+def _find_matching_bracket(
+ s: str,
+ start: int,
+ open_char: str,
+ close_char: str,
+ ignore_inside: str | None = None,
+) -> int:
+ """Find the index of the closing bracket matching the opening one at start.
+
+ Args:
+ s: The string to search.
+ start: The index of the opening bracket.
+ open_char: The opening bracket character (e.g., '(' or '<').
+ close_char: The closing bracket character (e.g., ')' or '>').
+ ignore_inside: Optional bracket type inside which to ignore matches.
+ If '(', ignores matches inside parentheses. If '<', ignores inside angles.
+
+ Returns:
+ The index of the matching closing bracket, or -1 if not found.
+ """
+ if start >= len(s) or s[start] != open_char:
+ return -1
+
+ depth = 0
+ ignore_depth = 0
+ ignore_open = ignore_close = ""
+ if ignore_inside == "(":
+ ignore_open, ignore_close = "(", ")"
+ elif ignore_inside == "<":
+ ignore_open, ignore_close = "<", ">"
+
+ for i in range(start, len(s)):
+ c = s[i]
+ if ignore_open and c == ignore_open:
+ ignore_depth += 1
+ elif ignore_close and c == ignore_close:
+ ignore_depth -= 1
+ elif c == open_char and ignore_depth == 0:
+ depth += 1
+ elif c == close_char and ignore_depth == 0:
+ depth -= 1
+ if depth == 0:
+ return i
+
+ return -1
+
+
+def _find_matching_paren(s: str, start: int = 0) -> int:
+ """Find the index of the closing parenthesis matching the opening one at start."""
+ return _find_matching_bracket(s, start, "(", ")")
+
+
+def _find_matching_angle(s: str, start: int = 0) -> int:
+ """Find the index of the closing angle bracket matching the opening one at start."""
+ return _find_matching_bracket(s, start, "<", ">", ignore_inside="(")
+
+
+def _iter_at_depth_zero(s: str):
+ """Iterate over string, yielding (index, char, at_depth_zero) tuples.
+
+ Tracks nested parentheses, angle brackets, and braces. The at_depth_zero
+ flag is True when all nesting depths are zero AFTER processing the current
+ character (so brackets themselves are never at depth zero).
+ """
+ paren_depth = angle_depth = brace_depth = 0
+
+ for i, c in enumerate(s):
+ if c == "<":
+ angle_depth += 1
+ elif c == ">":
+ angle_depth = max(0, angle_depth - 1)
+ elif c == "(":
+ paren_depth += 1
+ elif c == ")":
+ paren_depth = max(0, paren_depth - 1)
+ elif c == "{":
+ brace_depth += 1
+ elif c == "}":
+ brace_depth = max(0, brace_depth - 1)
+
+ yield i, c, (paren_depth == 0 and angle_depth == 0 and brace_depth == 0)
+
+
+def _split_arguments(args_str: str) -> list[str]:
+ """Split arguments by comma, respecting nested structures.
+
+ Handles:
+ - Nested parentheses: std::function
+ - Nested angle brackets: std::map
+ - Brace initializers: std::vector v = {1, 2, 3}
+ """
+ result = []
+ current: list[str] = []
+
+ for _, c, at_zero in _iter_at_depth_zero(args_str):
+ if c == "," and at_zero:
+ result.append("".join(current).strip())
+ current = []
+ else:
+ current.append(c)
+
+ if current:
+ result.append("".join(current).strip())
+
+ return [arg for arg in result if arg]
+
+
+def _prefix_is_all_qualifiers(prefix: str) -> bool:
+ """Check if all tokens in the prefix are type qualifiers/specifiers.
+
+ When the prefix consists entirely of reserved qualifiers (e.g., "const",
+ "const unsigned"), the following token cannot be a parameter name — it
+ must be completing the type.
+ """
+ return all(t in _CPP_TYPE_QUALIFIERS for t in prefix.split())
+
+
+def _looks_like_type_token(token: str) -> bool:
+ """Check if a token ends with type-related punctuation.
+
+ Returns True if the token ends with pointer, reference, template closer,
+ or array bracket — indicating it's part of a type, not a name.
+ """
+ return (
+ token.endswith("*")
+ or token.endswith("&")
+ or token.endswith(">")
+ or token.endswith("]")
+ )
+
+
+def _find_default_value_start(arg: str) -> int:
+ """Find the position of '=' that starts a default value.
+
+ Returns -1 if no default value found.
+ Ignores '=' inside nested structures like templates or lambdas.
+ """
+ for i, c, at_zero in _iter_at_depth_zero(arg):
+ if c == "=" and at_zero:
+ return i
+ return -1
+
+
+def extract_qualifiers(type_str: str) -> tuple[str | None, str]:
+ """Extract leading CV-qualifiers (const, volatile, mutable) from a type string.
+
+ Returns ``(qualifiers, core_type)`` where *qualifiers* is ``None`` when
+ no leading CV-qualifiers are found or when extracting them would leave
+ the type empty.
+
+ Examples::
+
+ "const std::string&" → ("const", "std::string&")
+ "const volatile int*" → ("const volatile", "int*")
+ "unsigned long" → (None, "unsigned long")
+ "const unsigned long" → ("const", "unsigned long")
+ "int" → (None, "int")
+ "mutable std::mutex" → ("mutable", "std::mutex")
+ """
+ if not type_str:
+ return (None, "")
+
+ tokens = type_str.split()
+ qual_count = 0
+ for token in tokens:
+ if token in _CV_QUALIFIERS:
+ qual_count += 1
+ else:
+ break
+
+ # No leading qualifiers, or ALL tokens are qualifiers (e.g. just "const")
+ # — keep them in the type to avoid producing an empty type string.
+ if qual_count == 0 or qual_count >= len(tokens):
+ return (None, type_str)
+
+ return (" ".join(tokens[:qual_count]), " ".join(tokens[qual_count:]))
+
+
+def _parse_single_argument(arg: str) -> Argument:
+ """Parse a single C++ argument into ``(qualifiers, type, name, default_value)``.
+
+ Leading CV-qualifiers (``const``, ``volatile``, ``mutable``) are extracted
+ into the *qualifiers* field so the *type* field contains only the core
+ type reference that may later need namespace qualification.
+
+ Handles complex cases::
+
+ "int x" → (None, "int", "x", None)
+ "const std::string& s" → ("const", "std::string&", "s", None)
+ "int x = 5" → (None, "int", "x", "5")
+ "int (*callback)(int, int)" → (None, "int (*)(int, int)", "callback", None)
+ "void (Class::*method)()" → (None, "void (Class::*)()", "method", None)
+ "int (&arr)[10]" → (None, "int (&)[10]", "arr", None)
+ "void" → (None, "void", None, None)
+ "const unsigned long" → ("const", "unsigned long", None, None)
+ """
+ arg = arg.strip()
+ if not arg:
+ return (None, "", None, None)
+
+ default_value: str | None = None
+ base_arg = arg
+
+ # Handle default values: split on '=' but respect nested structures
+ eq_pos = _find_default_value_start(arg)
+ if eq_pos != -1:
+ base_arg = arg[:eq_pos].strip()
+ default_value = arg[eq_pos + 1 :].strip()
+
+ # Extract leading CV-qualifiers before further parsing
+ qualifiers, base_arg = extract_qualifiers(base_arg)
+
+ # Try to match function pointer / pointer to member / reference to array pattern
+ match = _FUNC_PTR_PATTERN.search(base_arg)
+ if match:
+ name = match.group(1)
+ # Reconstruct type by removing the name from the pattern
+ type_str = base_arg[: match.start(1)] + base_arg[match.end(1) :]
+ return (qualifiers, type_str.strip(), name, default_value)
+
+ # Regular case: last token is the name
+ parts = base_arg.rsplit(None, 1)
+ if len(parts) == 2:
+ prefix, potential_name = parts
+ # The last token is NOT a name when:
+ # - it looks like a type token (ends with *, &, >, ]), OR
+ # - it is a CV-qualifier (trailing const/volatile), OR
+ # - the prefix is all qualifiers/specifiers AND the last token is
+ # also a type specifier (e.g. "unsigned long" — "long" completes
+ # the type rather than naming a parameter).
+ if (
+ _looks_like_type_token(potential_name)
+ or potential_name in _CV_QUALIFIERS
+ or (
+ _prefix_is_all_qualifiers(prefix) and potential_name in _TYPE_SPECIFIERS
+ )
+ ):
+ return (qualifiers, base_arg, None, default_value)
+ return (qualifiers, prefix, potential_name, default_value)
+ else:
+ return (qualifiers, base_arg, None, default_value)
+
+
+def _parse_modifiers(modifiers_str: str) -> FunctionModifiers:
+ """Parse function modifiers after the parameter list.
+
+ Handles: const, override, final, noexcept, noexcept(expr), = 0, = default, = delete
+ """
+ result = FunctionModifiers()
+ s = modifiers_str.strip()
+
+ # Handle = 0, = default, = delete
+ eq_match = re.search(r"=\s*(0|default|delete)\s*$", s)
+ if eq_match:
+ value = eq_match.group(1)
+ if value == "0":
+ result.is_pure_virtual = True
+ elif value == "default":
+ result.is_default = True
+ elif value == "delete":
+ result.is_delete = True
+ s = s[: eq_match.start()].strip()
+
+ # Handle noexcept with optional expression
+ noexcept_match = re.search(r"\bnoexcept\b", s)
+ if noexcept_match:
+ result.is_noexcept = True
+ # Check for noexcept(expr)
+ rest = s[noexcept_match.end() :]
+ rest_stripped = rest.lstrip()
+ if rest_stripped.startswith("("):
+ paren_end = _find_matching_paren(rest_stripped, 0)
+ if paren_end != -1:
+ result.noexcept_expr = rest_stripped[1:paren_end].strip()
+ # Remove noexcept and its expression from the string for further parsing
+ s = s[: noexcept_match.start()] + s[noexcept_match.end() :]
+ if result.noexcept_expr:
+ # Also remove the (expr) part
+ s = re.sub(r"\([^)]*\)", "", s, count=1)
+
+ # Parse remaining tokens
+ tokens = s.split()
+ for token in tokens:
+ if token == "const":
+ result.is_const = True
+ elif token == "override":
+ result.is_override = True
+ elif token == "final":
+ result.is_final = True
+
+ return result
+
+
+def parse_arg_string(
+ arg_string: str,
+) -> tuple[list[Argument], FunctionModifiers]:
+ """Parse a C++ function argument string.
+
+ Args:
+ arg_string: String in format "(type1 arg1, type2 arg2 = default) [modifiers]"
+
+ Returns:
+ Tuple of (arguments, modifiers) where:
+ - arguments: list of (qualifiers, type, name, default_value) tuples
+ - modifiers: FunctionModifiers dataclass with parsed modifier flags
+ """
+ arg_string = arg_string.strip()
+
+ if not arg_string.startswith("("):
+ return ([], FunctionModifiers())
+
+ close_paren = _find_matching_paren(arg_string, 0)
+ if close_paren == -1:
+ return ([], FunctionModifiers())
+
+ args_content = arg_string[1:close_paren].strip()
+ modifiers_str = arg_string[close_paren + 1 :]
+
+ arguments: list[Argument] = []
+ if args_content:
+ for arg in _split_arguments(args_content):
+ parsed = _parse_single_argument(arg)
+ if parsed[0] or parsed[1]:
+ arguments.append(parsed)
+
+ modifiers = _parse_modifiers(modifiers_str)
+
+ return (arguments, modifiers)
+
+
+def format_arguments(arguments: list[Argument]) -> str:
+ """Format a list of parsed arguments into a comma-separated string.
+
+ Args:
+ arguments: list of (qualifiers, type, name, default_value) tuples as
+ returned by parse_arg_string or _parse_single_argument.
+
+ Returns:
+ Formatted argument string, e.g. "const int x, float y = 0.0".
+ """
+ parts = []
+ for qualifiers, arg_type, arg_name, arg_default in arguments:
+ part = ""
+ if qualifiers:
+ part += f"{qualifiers} "
+ part += arg_type
+ if arg_name:
+ part += f" {arg_name}"
+ if arg_default:
+ part += f" = {arg_default}"
+ parts.append(part)
+ return ", ".join(parts)
+
+
+def parse_function_pointer_argstring(
+ argstring: str,
+) -> list[Argument]:
+ """Parse a function pointer argstring of the form ')(args...)'.
+
+ Doxygen represents function pointer arguments in the argstring field
+ starting with ')(' — the ')' closes the declarator and '(' opens
+ the parameter list.
+
+ Args:
+ argstring: Raw argstring from doxygen, e.g. ")(int x, float y)".
+
+ Returns:
+ List of (qualifiers, type, name, default_value) tuples for each parameter.
+ """
+ if not argstring or not argstring.startswith(")("):
+ return []
+ # Remove leading ')' to get '(args...)'
+ inner = argstring[1:]
+ arguments, _ = parse_arg_string(inner)
+ return arguments
+
+
+def parse_type_with_argstrings(
+ type_str: str,
+) -> list[str | list[Argument]]:
+ """Parse a type string, extracting inline function argument lists.
+
+ Doxygen sometimes embeds raw function signatures in type strings
+ (e.g. for 'using' typedefs). This function splits the type into
+ segments: plain-text fragments and parsed argument lists.
+
+ Each segment is either:
+ - A ``str``: literal text (e.g. ``"void"``, ``"(*)"``).
+ - A ``list[Argument]``: a parsed argument list,
+ where each tuple is ``(qualifiers, type, name, default_value)``.
+
+ Handles cases like:
+ - ``void(int x, float y)``
+ → ``["void", [(None, "int", "x", None), (None, "float", "y", None)]]``
+ - ``std::function``
+ → ``["std::function"]``
+ - ``void(*)(int x, float y)``
+ → ``["void(*)", [(None, "int", "x", None), (None, "float", "y", None)]]``
+ - ``int``
+ → ``["int"]``
+ """
+ if not type_str:
+ return [type_str] if type_str is not None else []
+
+ segments: list[str | list[Argument]] = []
+ i = 0
+ current_text: list[str] = []
+
+ while i < len(type_str):
+ if type_str[i] == "(":
+ close = _find_matching_paren(type_str, i)
+ if close == -1:
+ current_text.append(type_str[i])
+ i += 1
+ continue
+
+ inner = type_str[i + 1 : close]
+ stripped = inner.strip()
+
+ # Check if this is a declarator like (*) or (&) — don't parse those
+ if stripped in ("*", "&") or re.match(
+ r"^[a-zA-Z_][a-zA-Z0-9_]*\s*::\s*[*&]$", stripped
+ ):
+ current_text.append(type_str[i : close + 1])
+ i = close + 1
+ continue
+
+ # Try to parse as a function argument list
+ args: list[Argument] = []
+ if stripped:
+ for arg in _split_arguments(stripped):
+ parsed = _parse_single_argument(arg)
+ if parsed[0] or parsed[1]:
+ args.append(parsed)
+
+ if args:
+ # Flush accumulated text as a plain segment
+ if current_text:
+ segments.append("".join(current_text))
+ current_text = []
+ segments.append(args)
+ else:
+ current_text.append(type_str[i : close + 1])
+ i = close + 1
+ else:
+ current_text.append(type_str[i])
+ i += 1
+
+ if current_text:
+ segments.append("".join(current_text))
+
+ return segments
+
+
+def format_parsed_type(
+ segments: list[str | list[Argument]],
+) -> str:
+ """Format the structured output of :func:`parse_type_with_argstrings` back
+ into a type string.
+
+ Each segment is either a plain string (emitted as-is) or a parsed
+ argument list (formatted as ``(qualifiers type name = default, ...)``).
+ """
+ parts: list[str] = []
+ for seg in segments:
+ if isinstance(seg, str):
+ parts.append(seg)
+ else:
+ parts.append(f"({format_arguments(seg)})")
+ return "".join(parts)
diff --git a/scripts/cxx-api/parser/utils.py b/scripts/cxx-api/parser/utils/qualified_path.py
similarity index 98%
rename from scripts/cxx-api/parser/utils.py
rename to scripts/cxx-api/parser/utils/qualified_path.py
index d69920cdaf73..1010ca6a6cdb 100644
--- a/scripts/cxx-api/parser/utils.py
+++ b/scripts/cxx-api/parser/utils/qualified_path.py
@@ -3,6 +3,8 @@
# 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
+
def parse_qualified_path(path: str) -> list[str]:
"""
diff --git a/scripts/cxx-api/parser/utils/type_qualification.py b/scripts/cxx-api/parser/utils/type_qualification.py
new file mode 100644
index 000000000000..115de82b5b34
--- /dev/null
+++ b/scripts/cxx-api/parser/utils/type_qualification.py
@@ -0,0 +1,148 @@
+# 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 TYPE_CHECKING
+
+from .argument_parsing import _find_matching_angle, _split_arguments, Argument
+
+if TYPE_CHECKING:
+ from .scope import Scope
+
+
+def qualify_arguments(arguments: list[Argument], scope: Scope) -> list[Argument]:
+ """Qualify type and default-value references in a list of arguments."""
+ result: list[Argument] = []
+ for qualifiers, arg_type, name, default in arguments:
+ qualified_type = qualify_type_str(arg_type, scope)
+ qualified_default = scope.qualify_name(default)
+ if qualified_default is not None:
+ default = qualified_default
+
+ result.append((qualifiers, qualified_type, name, default))
+ return result
+
+
+def qualify_type_str(type_str: str, scope: Scope) -> str:
+ """Qualify a type string, handling trailing decorators (*, &, &&, etc.)."""
+ if not type_str:
+ return type_str
+
+ # Handle template arguments first: qualify types inside angle brackets
+ angle_start = type_str.find("<")
+ if angle_start != -1:
+ angle_end = _find_matching_angle(type_str, angle_start)
+ if angle_end != -1:
+ prefix = type_str[:angle_start]
+ template_args = type_str[angle_start + 1 : angle_end]
+ suffix = type_str[angle_end + 1 :]
+
+ # Qualify the prefix (outer type before the template)
+ qualified_prefix = scope.qualify_name(prefix) or prefix
+
+ # Split template arguments and qualify each one
+ args = _split_arguments(template_args)
+ qualified_args = [qualify_type_str(arg.strip(), scope) for arg in args]
+ qualified_template = "< " + ", ".join(qualified_args) + " >"
+
+ # Recursively qualify the suffix (handles nested templates, pointers, etc.)
+ qualified_suffix = qualify_type_str(suffix, scope) if suffix else ""
+
+ return qualified_prefix + qualified_template + qualified_suffix
+
+ # Try qualifying the entire string (handles simple cases without templates)
+ qualified = scope.qualify_name(type_str)
+ if qualified is not None:
+ return qualified
+
+ # Handle function pointer/reference declarator prefix: "Type(*" or "Type(&"
+ for declarator in ("(*", "(&"):
+ if type_str.endswith(declarator):
+ prefix = type_str[: -len(declarator)].rstrip()
+ if prefix:
+ q = qualify_type_str(prefix, scope)
+ if q != prefix:
+ return q + declarator
+ return type_str
+
+ # Strip trailing pointer/reference/const decorators and try again.
+ # Loop so that mixed sequences like "Type *const" or "Type const*" are
+ # fully peeled regardless of order.
+ stripped = type_str.rstrip()
+ suffix = ""
+ changed = True
+ while changed:
+ changed = False
+ while stripped and stripped[-1] in ("*", "&"):
+ suffix = stripped[-1] + suffix
+ stripped = stripped[:-1].rstrip()
+ changed = True
+ # Handle trailing "const" (possibly preceded by space or pointer)
+ if stripped.endswith(" const") or stripped.endswith("*const"):
+ is_after_ptr = stripped.endswith("*const")
+ suffix = "const " + suffix
+ stripped = stripped[: -len("const")].rstrip()
+ if is_after_ptr:
+ # Re-enter loop to peel the '*' that preceded "const"
+ pass
+ changed = True
+
+ if suffix and stripped:
+ qualified = scope.qualify_name(stripped)
+ if qualified is not None:
+ suffix = suffix.rstrip()
+ if "const" in suffix:
+ return qualified + " " + suffix
+ return qualified + suffix
+
+ return type_str
+
+
+def _qualify_text_before_args(text: str, scope: Scope) -> str:
+ """Qualify the trailing return-type in a text segment that precedes an arg list.
+
+ When ``parse_type_with_argstrings`` splits ``"std::function< Result(Param)"``
+ the text segment ``"std::function< Result"`` contains a return type at the
+ end that must be qualified independently.
+ """
+ # Try qualifying the whole text first (covers simple "Result" etc.)
+ qualified = qualify_type_str(text, scope)
+ if qualified != text:
+ return qualified
+
+ # Try qualifying just the last whitespace-delimited token
+ stripped = text.rstrip()
+ last_space = stripped.rfind(" ")
+ if last_space == -1:
+ return text
+
+ prefix = stripped[: last_space + 1]
+ last_token = stripped[last_space + 1 :]
+
+ qualified_token = scope.qualify_name(last_token)
+ if qualified_token is not None:
+ return prefix + qualified_token
+
+ return text
+
+
+def qualify_parsed_type(
+ segments: list[str | list[Argument]], scope: Scope
+) -> list[str | list[Argument]]:
+ """Qualify references inside parsed-type segments."""
+ result: list[str | list[Argument]] = []
+ for i, seg in enumerate(segments):
+ if isinstance(seg, list):
+ result.append(qualify_arguments(seg, scope))
+ else:
+ # If the next segment is a parsed argument list, the end of this
+ # text likely contains a return type that needs qualification.
+ next_is_args = i + 1 < len(segments) and isinstance(segments[i + 1], list)
+ if next_is_args:
+ result.append(_qualify_text_before_args(seg, scope))
+ else:
+ result.append(qualify_type_str(seg, scope))
+ return result
diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api
index a963a16ebda3..1204a64ce870 100644
--- a/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api
+++ b/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api
@@ -2,18 +2,18 @@ class test::Clss {
public virtual void fn16() = 0;
public void fn0(int32_t arg1);
public void fn1(int32_t arg1, int32_t arg2);
- public void fn2(std::function< int32_t(int32_t)> arg1);
- public void fn3(std::function< int32_t(int32_t)> arg1, std::function< int32_t(int32_t, int32_t)> arg2);
+ public void fn2(std::function< int32_t(int32_t) > arg1);
+ public void fn3(std::function< int32_t(int32_t) > arg1, std::function< int32_t(int32_t, int32_t) > arg2);
public void fn4(std::map< std::string, int > m);
public void fn5(std::unordered_map< K, std::vector< V > > m);
public void fn6(std::tuple< int, float, std::string > t);
public void fn7(std::vector< std::vector< std::pair< int, int > > > v);
- public void fn8(std::map< K, std::function< void(A, B)> > m);
+ public void fn8(std::map< K, std::function< void(A, B) > > m);
public void fn9(int(*)(int, int) callback);
public void fn10(void(*)(const char *, size_t) handler);
- public void fn11(int(*(*)(int))(double) fp);
+ public void fn11(int(*(*fp)(int))(double));
public void fn12(int x = 5, std::string s = "default");
- public void fn13(std::function< void()> f = nullptr);
+ public void fn13(std::function< void() > f = nullptr);
public void fn14(std::vector< int > v = {1, 2, 3});
public void fn15() noexcept;
public void fn17() = default;
diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api
new file mode 100644
index 000000000000..e2c51844a8fa
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api
@@ -0,0 +1,7 @@
+class test::Base {
+ public virtual const char * getName() = 0;
+ public virtual std::vector< test::Method > getMethods() = 0;
+}
+
+struct test::Method {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/test.h b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/test.h
new file mode 100644
index 000000000000..b2e9e2382da8
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/test.h
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include
+
+namespace test {
+
+struct Method {};
+
+class Base {
+ public:
+ virtual auto getMethods() -> std::vector = 0;
+ virtual auto getName() -> const char * = 0;
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/snapshot.api
new file mode 100644
index 000000000000..f65f0cac022b
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/snapshot.api
@@ -0,0 +1,6 @@
+struct test::Param {
+}
+
+typedef void(*test::FreeFnPtr)(test::Param);
+using test::FreeCallback = void(test::Param);
+void test::freeFunction(test::Param p);
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/test.h
new file mode 100644
index 000000000000..30ddd5fef0c3
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_free_declarations/test.h
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+
+void freeFunction(Param p);
+typedef void (*FreeFnPtr)(Param);
+using FreeCallback = void(Param);
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_function_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_function_args/snapshot.api
new file mode 100644
index 000000000000..63d0bd88b917
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_function_args/snapshot.api
@@ -0,0 +1,8 @@
+struct test::Container {
+ public void doConst(const test::Param& p);
+ public void doMultiple(test::Param a, test::Param b, int x);
+ public void doSomething(test::Param p);
+}
+
+struct test::Param {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_function_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_function_args/test.h
new file mode 100644
index 000000000000..96b6acc6abb8
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_function_args/test.h
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+
+struct Container {
+ void doSomething(Param p);
+ void doConst(const Param &p);
+ void doMultiple(Param a, Param b, int x);
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/snapshot.api
new file mode 100644
index 000000000000..1ebe9e74ffb9
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/snapshot.api
@@ -0,0 +1,8 @@
+facebook::yoga::Result facebook::react::getResult(facebook::yoga::Node node, int flags);
+void facebook::react::processNode(facebook::yoga::Node node);
+
+struct facebook::yoga::Node {
+}
+
+struct facebook::yoga::Result {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/test.h
new file mode 100644
index 000000000000..6ffd778bca6c
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_partially_qualified_args/test.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace facebook {
+
+namespace yoga {
+
+struct Node {};
+
+struct Result {};
+
+} // namespace yoga
+
+namespace react {
+
+void processNode(yoga::Node node);
+yoga::Result getResult(yoga::Node node, int flags);
+
+} // namespace react
+
+} // namespace facebook
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api
new file mode 100644
index 000000000000..a75e6cc67a16
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api
@@ -0,0 +1 @@
+void test::doSomething(const Node *const node, int x);
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/test.h
new file mode 100644
index 000000000000..47211e25d134
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/test.h
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Node;
+
+void doSomething(const Node *const node, int x);
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/snapshot.api
new file mode 100644
index 000000000000..c41c8f2abba8
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/snapshot.api
@@ -0,0 +1,6 @@
+struct test::Container {
+ public typedef void(*FnPtr)(test::Param, int);
+}
+
+struct test::Param {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/test.h
new file mode 100644
index 000000000000..06af2a21afba
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_typedef_function_pointer/test.h
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+
+struct Container {
+ typedef void (*FnPtr)(Param, int);
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/snapshot.api
new file mode 100644
index 000000000000..056c4c0561bf
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/snapshot.api
@@ -0,0 +1,10 @@
+struct test::Container {
+ public using Callback = void(test::Param);
+ public using Processor = test::Result(test::Param, int);
+}
+
+struct test::Param {
+}
+
+struct test::Result {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/test.h
new file mode 100644
index 000000000000..c8baaf8b95af
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_using_typedef_inline_args/test.h
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+struct Result {};
+
+struct Container {
+ using Callback = void(Param);
+ using Processor = Result(Param, int);
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/snapshot.api
new file mode 100644
index 000000000000..88dbaa0273da
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/snapshot.api
@@ -0,0 +1,6 @@
+struct test::Container {
+ public void(*handler)(test::Param, int);
+}
+
+struct test::Param {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/test.h
new file mode 100644
index 000000000000..188271dcf760
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_function_pointer/test.h
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+
+struct Container {
+ void (*handler)(Param, int);
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/snapshot.api
new file mode 100644
index 000000000000..977576a2f088
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/snapshot.api
@@ -0,0 +1,13 @@
+struct test::Container {
+ public std::function< test::Result(test::Param, int)> processor;
+ public std::function< void(const test::Param&, test::Result)> multiRef;
+ public std::function< void(test::Param)> simpleCallback;
+ public test::Result(*fnPtrReturn)(const test::Param&);
+ public void(*fnPtr)(test::Param, int);
+}
+
+struct test::Param {
+}
+
+struct test::Result {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/test.h
new file mode 100644
index 000000000000..1c726e151ae4
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_inline_function_type/test.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include
+
+namespace test {
+
+struct Param {};
+struct Result {};
+
+struct Container {
+ std::function simpleCallback;
+ std::function processor;
+ std::function multiRef;
+ void (*fnPtr)(Param, int);
+ Result (*fnPtrReturn)(const Param &);
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/snapshot.api
new file mode 100644
index 000000000000..76fb590bba71
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/snapshot.api
@@ -0,0 +1,7 @@
+struct test::Container {
+ public const test::Param constField;
+ public test::Param field;
+}
+
+struct test::Param {
+}
diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/test.h b/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/test.h
new file mode 100644
index 000000000000..ff0868054a75
--- /dev/null
+++ b/scripts/cxx-api/tests/snapshots/should_qualify_variable_type/test.h
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace test {
+
+struct Param {};
+
+struct Container {
+ Param field;
+ const Param constField;
+};
+
+} // namespace test
diff --git a/scripts/cxx-api/tests/test_member.py b/scripts/cxx-api/tests/test_parse_arg_string.py
similarity index 60%
rename from scripts/cxx-api/tests/test_member.py
rename to scripts/cxx-api/tests/test_parse_arg_string.py
index 8f1a4e848cc0..ae99fb034ab0 100644
--- a/scripts/cxx-api/tests/test_member.py
+++ b/scripts/cxx-api/tests/test_parse_arg_string.py
@@ -3,11 +3,11 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
-"""Unit tests for FunctionMember.parse_arg_string()"""
+"""Unit tests for parse_arg_string()"""
import unittest
-from ..parser import FunctionMember, FunctionModifiers
+from ..parser.utils.argument_parsing import parse_arg_string
class TestParseArgString(unittest.TestCase):
@@ -19,24 +19,23 @@ class TestParseArgString(unittest.TestCase):
def test_empty_args(self):
"""Empty parentheses"""
- args, mods = FunctionMember.parse_arg_string("()")
+ args, mods = parse_arg_string("()")
self.assertEqual(args, [])
- self.assertEqual(mods, FunctionModifiers())
def test_single_simple_arg(self):
"""Single argument: int x"""
- args, mods = FunctionMember.parse_arg_string("(int x)")
- self.assertEqual(args, [("int", "x", None)])
+ args, mods = parse_arg_string("(int x)")
+ self.assertEqual(args, [(None, "int", "x", None)])
def test_multiple_simple_args(self):
"""Multiple arguments: int x, float y"""
- args, mods = FunctionMember.parse_arg_string("(int x, float y)")
- self.assertEqual(args, [("int", "x", None), ("float", "y", None)])
+ args, mods = parse_arg_string("(int x, float y)")
+ self.assertEqual(args, [(None, "int", "x", None), (None, "float", "y", None)])
def test_void_arg(self):
"""void with no name"""
- args, mods = FunctionMember.parse_arg_string("(void)")
- self.assertEqual(args, [("void", "", None)])
+ args, mods = parse_arg_string("(void)")
+ self.assertEqual(args, [(None, "void", None, None)])
# =========================================================================
# Modifiers: const, override, final
@@ -44,42 +43,42 @@ def test_void_arg(self):
def test_const_modifier(self):
"""() const"""
- args, mods = FunctionMember.parse_arg_string("() const")
+ args, mods = parse_arg_string("() const")
self.assertTrue(mods.is_const)
self.assertFalse(mods.is_override)
def test_override_modifier(self):
"""() override"""
- args, mods = FunctionMember.parse_arg_string("() override")
+ args, mods = parse_arg_string("() override")
self.assertTrue(mods.is_override)
self.assertFalse(mods.is_const)
def test_const_override(self):
"""() const override"""
- args, mods = FunctionMember.parse_arg_string("() const override")
+ args, mods = parse_arg_string("() const override")
self.assertTrue(mods.is_const)
self.assertTrue(mods.is_override)
def test_override_const(self):
"""() override const - reversed order"""
- args, mods = FunctionMember.parse_arg_string("() override const")
+ args, mods = parse_arg_string("() override const")
self.assertTrue(mods.is_const)
self.assertTrue(mods.is_override)
def test_final_modifier(self):
"""() final"""
- args, mods = FunctionMember.parse_arg_string("() final")
+ args, mods = parse_arg_string("() final")
self.assertTrue(mods.is_final)
def test_override_final(self):
"""() override final"""
- args, mods = FunctionMember.parse_arg_string("() override final")
+ args, mods = parse_arg_string("() override final")
self.assertTrue(mods.is_override)
self.assertTrue(mods.is_final)
def test_const_override_final(self):
"""() const override final"""
- args, mods = FunctionMember.parse_arg_string("() const override final")
+ args, mods = parse_arg_string("() const override final")
self.assertTrue(mods.is_const)
self.assertTrue(mods.is_override)
self.assertTrue(mods.is_final)
@@ -90,31 +89,31 @@ def test_const_override_final(self):
def test_noexcept(self):
"""() noexcept"""
- args, mods = FunctionMember.parse_arg_string("() noexcept")
+ args, mods = parse_arg_string("() noexcept")
self.assertTrue(mods.is_noexcept)
self.assertIsNone(mods.noexcept_expr)
def test_noexcept_true(self):
"""() noexcept(true)"""
- args, mods = FunctionMember.parse_arg_string("() noexcept(true)")
+ args, mods = parse_arg_string("() noexcept(true)")
self.assertTrue(mods.is_noexcept)
self.assertEqual(mods.noexcept_expr, "true")
def test_noexcept_false(self):
"""() noexcept(false)"""
- args, mods = FunctionMember.parse_arg_string("() noexcept(false)")
+ args, mods = parse_arg_string("() noexcept(false)")
self.assertTrue(mods.is_noexcept)
self.assertEqual(mods.noexcept_expr, "false")
def test_noexcept_expr(self):
"""() noexcept(noexcept(other()))"""
- args, mods = FunctionMember.parse_arg_string("() noexcept(noexcept(other()))")
+ args, mods = parse_arg_string("() noexcept(noexcept(other()))")
self.assertTrue(mods.is_noexcept)
self.assertEqual(mods.noexcept_expr, "noexcept(other())")
def test_const_noexcept(self):
"""() const noexcept"""
- args, mods = FunctionMember.parse_arg_string("() const noexcept")
+ args, mods = parse_arg_string("() const noexcept")
self.assertTrue(mods.is_const)
self.assertTrue(mods.is_noexcept)
@@ -124,28 +123,28 @@ def test_const_noexcept(self):
def test_pure_virtual(self):
"""() = 0"""
- args, mods = FunctionMember.parse_arg_string("() = 0")
+ args, mods = parse_arg_string("() = 0")
self.assertTrue(mods.is_pure_virtual)
def test_default(self):
"""() = default"""
- args, mods = FunctionMember.parse_arg_string("() = default")
+ args, mods = parse_arg_string("() = default")
self.assertTrue(mods.is_default)
def test_delete(self):
"""() = delete"""
- args, mods = FunctionMember.parse_arg_string("() = delete")
+ args, mods = parse_arg_string("() = delete")
self.assertTrue(mods.is_delete)
def test_const_pure_virtual(self):
"""() const = 0"""
- args, mods = FunctionMember.parse_arg_string("() const = 0")
+ args, mods = parse_arg_string("() const = 0")
self.assertTrue(mods.is_const)
self.assertTrue(mods.is_pure_virtual)
def test_noexcept_default(self):
"""() noexcept = default"""
- args, mods = FunctionMember.parse_arg_string("() noexcept = default")
+ args, mods = parse_arg_string("() noexcept = default")
self.assertTrue(mods.is_noexcept)
self.assertTrue(mods.is_default)
@@ -155,31 +154,31 @@ def test_noexcept_default(self):
def test_std_map(self):
"""std::map with comma inside template"""
- args, mods = FunctionMember.parse_arg_string("(std::map m)")
- self.assertEqual(args, [("std::map", "m", None)])
+ args, mods = parse_arg_string("(std::map m)")
+ self.assertEqual(args, [(None, "std::map", "m", None)])
def test_std_unordered_map_nested(self):
"""std::unordered_map>"""
- args, mods = FunctionMember.parse_arg_string(
- "(std::unordered_map> m)"
+ args, mods = parse_arg_string("(std::unordered_map> m)")
+ self.assertEqual(
+ args, [(None, "std::unordered_map>", "m", None)]
)
- self.assertEqual(args, [("std::unordered_map>", "m", None)])
def test_std_tuple(self):
"""std::tuple"""
- args, mods = FunctionMember.parse_arg_string(
- "(std::tuple t)"
+ args, mods = parse_arg_string("(std::tuple t)")
+ self.assertEqual(
+ args, [(None, "std::tuple", "t", None)]
)
- self.assertEqual(args, [("std::tuple", "t", None)])
def test_deeply_nested_templates(self):
"""std::vector>>"""
- args, mods = FunctionMember.parse_arg_string(
+ args, mods = parse_arg_string(
"(std::vector>> v)"
)
self.assertEqual(
args,
- [("std::vector>>", "v", None)],
+ [(None, "std::vector>>", "v", None)],
)
# =========================================================================
@@ -188,23 +187,24 @@ def test_deeply_nested_templates(self):
def test_std_function_simple(self):
"""std::function"""
- args, mods = FunctionMember.parse_arg_string("(std::function f)")
- self.assertEqual(args, [("std::function", "f", None)])
+ args, mods = parse_arg_string("(std::function f)")
+ self.assertEqual(args, [(None, "std::function", "f", None)])
def test_std_function_with_args(self):
"""std::function"""
- args, mods = FunctionMember.parse_arg_string("(std::function f)")
- self.assertEqual(args, [("std::function", "f", None)])
+ args, mods = parse_arg_string("(std::function f)")
+ self.assertEqual(args, [(None, "std::function", "f", None)])
def test_std_function_complex(self):
"""std::function"""
- args, mods = FunctionMember.parse_arg_string(
+ args, mods = parse_arg_string(
"(std::function callback)"
)
self.assertEqual(
args,
[
(
+ None,
"std::function",
"callback",
None,
@@ -214,23 +214,23 @@ def test_std_function_complex(self):
def test_multiple_std_function_args(self):
"""Multiple std::function args"""
- args, mods = FunctionMember.parse_arg_string(
+ args, mods = parse_arg_string(
"(std::function f, std::function g)"
)
self.assertEqual(
args,
[
- ("std::function", "f", None),
- ("std::function", "g", None),
+ (None, "std::function", "f", None),
+ (None, "std::function", "g", None),
],
)
def test_map_with_function_value(self):
"""std::map>"""
- args, mods = FunctionMember.parse_arg_string(
- "(std::map> m)"
+ args, mods = parse_arg_string("(std::map> m)")
+ self.assertEqual(
+ args, [(None, "std::map>", "m", None)]
)
- self.assertEqual(args, [("std::map>", "m", None)])
# =========================================================================
# Function pointers
@@ -238,20 +238,20 @@ def test_map_with_function_value(self):
def test_function_pointer_simple(self):
"""int (*callback)(int, int)"""
- args, mods = FunctionMember.parse_arg_string("(int (*callback)(int, int))")
- self.assertEqual(args, [("int (*)(int, int)", "callback", None)])
+ args, mods = parse_arg_string("(int (*callback)(int, int))")
+ self.assertEqual(args, [(None, "int (*)(int, int)", "callback", None)])
def test_function_pointer_void(self):
"""void (*handler)(const char*, size_t)"""
- args, mods = FunctionMember.parse_arg_string(
- "(void (*handler)(const char*, size_t))"
+ args, mods = parse_arg_string("(void (*handler)(const char*, size_t))")
+ self.assertEqual(
+ args, [(None, "void (*)(const char*, size_t)", "handler", None)]
)
- self.assertEqual(args, [("void (*)(const char*, size_t)", "handler", None)])
def test_function_pointer_no_args(self):
"""void (*fn)()"""
- args, mods = FunctionMember.parse_arg_string("(void (*fn)())")
- self.assertEqual(args, [("void (*)()", "fn", None)])
+ args, mods = parse_arg_string("(void (*fn)())")
+ self.assertEqual(args, [(None, "void (*)()", "fn", None)])
# =========================================================================
# Pointer to member function
@@ -259,16 +259,14 @@ def test_function_pointer_no_args(self):
def test_pointer_to_member(self):
"""void (Class::*method)(int, int)"""
- args, mods = FunctionMember.parse_arg_string(
- "(void (Class::*method)(int, int))"
- )
- self.assertEqual(args, [("void (Class::*)(int, int)", "method", None)])
+ args, mods = parse_arg_string("(void (Class::*method)(int, int))")
+ self.assertEqual(args, [(None, "void (Class::*)(int, int)", "method", None)])
def test_pointer_to_member_const(self):
"""int (Foo::*getter)() const - note: const after () is part of member fn signature"""
- args, mods = FunctionMember.parse_arg_string("(int (Foo::*getter)() const)")
+ args, mods = parse_arg_string("(int (Foo::*getter)() const)")
# The "const" here is part of the argument type, not a method modifier
- self.assertEqual(args, [("int (Foo::*)() const", "getter", None)])
+ self.assertEqual(args, [(None, "int (Foo::*)() const", "getter", None)])
# =========================================================================
# Reference to array / pointer to array
@@ -276,13 +274,13 @@ def test_pointer_to_member_const(self):
def test_reference_to_array(self):
"""int (&arr)[10]"""
- args, mods = FunctionMember.parse_arg_string("(int (&arr)[10])")
- self.assertEqual(args, [("int (&)[10]", "arr", None)])
+ args, mods = parse_arg_string("(int (&arr)[10])")
+ self.assertEqual(args, [(None, "int (&)[10]", "arr", None)])
def test_pointer_to_array(self):
"""int (*arr)[10]"""
- args, mods = FunctionMember.parse_arg_string("(int (*arr)[10])")
- self.assertEqual(args, [("int (*)[10]", "arr", None)])
+ args, mods = parse_arg_string("(int (*arr)[10])")
+ self.assertEqual(args, [(None, "int (*)[10]", "arr", None)])
# =========================================================================
# Default arguments
@@ -290,40 +288,36 @@ def test_pointer_to_array(self):
def test_default_int(self):
"""int x = 5"""
- args, mods = FunctionMember.parse_arg_string("(int x = 5)")
- self.assertEqual(args, [("int", "x", "5")])
+ args, mods = parse_arg_string("(int x = 5)")
+ self.assertEqual(args, [(None, "int", "x", "5")])
def test_default_string(self):
"""std::string s = "default\" """
- args, mods = FunctionMember.parse_arg_string('(std::string s = "default")')
- self.assertEqual(args, [("std::string", "s", '"default"')])
+ args, mods = parse_arg_string('(std::string s = "default")')
+ self.assertEqual(args, [(None, "std::string", "s", '"default"')])
def test_default_nullptr(self):
"""std::function f = nullptr"""
- args, mods = FunctionMember.parse_arg_string(
- "(std::function f = nullptr)"
- )
- self.assertEqual(args, [("std::function", "f", "nullptr")])
+ args, mods = parse_arg_string("(std::function f = nullptr)")
+ self.assertEqual(args, [(None, "std::function", "f", "nullptr")])
def test_default_brace_initializer(self):
"""std::vector v = {1, 2, 3}"""
- args, mods = FunctionMember.parse_arg_string("(std::vector v = {1, 2, 3})")
- self.assertEqual(args, [("std::vector", "v", "{1, 2, 3}")])
+ args, mods = parse_arg_string("(std::vector v = {1, 2, 3})")
+ self.assertEqual(args, [(None, "std::vector", "v", "{1, 2, 3}")])
def test_multiple_defaults(self):
"""int x = 5, std::string s = "test\" """
- args, mods = FunctionMember.parse_arg_string(
- '(int x = 5, std::string s = "test")'
- )
+ args, mods = parse_arg_string('(int x = 5, std::string s = "test")')
self.assertEqual(
args,
- [("int", "x", "5"), ("std::string", "s", '"test"')],
+ [(None, "int", "x", "5"), (None, "std::string", "s", '"test"')],
)
def test_default_template_with_comma(self):
"""std::map m = {}"""
- args, mods = FunctionMember.parse_arg_string("(std::map m = {})")
- self.assertEqual(args, [("std::map", "m", "{}")])
+ args, mods = parse_arg_string("(std::map m = {})")
+ self.assertEqual(args, [(None, "std::map", "m", "{}")])
# =========================================================================
# Complex CV-qualifiers
@@ -331,20 +325,18 @@ def test_default_template_with_comma(self):
def test_const_ref(self):
"""const std::string& s"""
- args, mods = FunctionMember.parse_arg_string("(const std::string& s)")
- self.assertEqual(args, [("const std::string&", "s", None)])
+ args, mods = parse_arg_string("(const std::string& s)")
+ self.assertEqual(args, [("const", "std::string&", "s", None)])
def test_const_ptr_const_ref(self):
"""const int* const& ptr"""
- args, mods = FunctionMember.parse_arg_string("(const int* const& ptr)")
- self.assertEqual(args, [("const int* const&", "ptr", None)])
+ args, mods = parse_arg_string("(const int* const& ptr)")
+ self.assertEqual(args, [("const", "int* const&", "ptr", None)])
def test_shared_ptr_const(self):
"""const std::shared_ptr& p"""
- args, mods = FunctionMember.parse_arg_string(
- "(const std::shared_ptr& p)"
- )
- self.assertEqual(args, [("const std::shared_ptr&", "p", None)])
+ args, mods = parse_arg_string("(const std::shared_ptr& p)")
+ self.assertEqual(args, [("const", "std::shared_ptr&", "p", None)])
# =========================================================================
# Mixed complex cases
@@ -352,14 +344,14 @@ def test_shared_ptr_const(self):
def test_mixed_args_with_modifiers(self):
"""Multiple complex args with const override"""
- args, mods = FunctionMember.parse_arg_string(
+ args, mods = parse_arg_string(
"(std::map m, std::function f) const override"
)
self.assertEqual(
args,
[
- ("std::map", "m", None),
- ("std::function", "f", None),
+ (None, "std::map", "m", None),
+ (None, "std::function", "f", None),
],
)
self.assertTrue(mods.is_const)
@@ -367,15 +359,15 @@ def test_mixed_args_with_modifiers(self):
def test_full_complex_signature(self):
"""Complex signature with everything"""
- args, mods = FunctionMember.parse_arg_string(
+ args, mods = parse_arg_string(
"(const std::vector& v, "
"std::function f = nullptr) const noexcept override"
)
self.assertEqual(
args,
[
- ("const std::vector&", "v", None),
- ("std::function", "f", "nullptr"),
+ ("const", "std::vector&", "v", None),
+ (None, "std::function", "f", "nullptr"),
],
)
self.assertTrue(mods.is_const)