@@ -28,6 +28,44 @@ def qualify_arguments(arguments: list[Argument], scope: Scope) -> list[Argument]
2828
2929def qualify_type_str (type_str : str , scope : Scope ) -> str :
3030 """Qualify a type string, handling trailing decorators (*, &, &&, etc.)."""
31+ return _qualify_type_str_impl (type_str , scope , qualify_base = True )
32+
33+
34+ def qualify_template_args_only (type_str : str , scope : Scope ) -> str :
35+ """Qualify only template arguments in a type string, leaving the base type unchanged.
36+
37+ This is useful for class names in template specializations where the base type
38+ is already positioned in the correct scope but the template arguments need
39+ qualification (e.g., "MyVector< Test >" -> "MyVector< ns::Test >").
40+ """
41+ return _qualify_type_str_impl (type_str , scope , qualify_base = False )
42+
43+
44+ def _qualify_prefix_with_decorators (prefix : str , scope : Scope ) -> str :
45+ """Qualify a template prefix that may have leading const/volatile qualifiers."""
46+ stripped = prefix .lstrip ()
47+ decorator_prefix = ""
48+ changed = True
49+ while changed :
50+ changed = False
51+ # Handle leading const/volatile qualifiers (must have trailing space)
52+ for qualifier in ("const " , "volatile " ):
53+ if stripped .startswith (qualifier ):
54+ decorator_prefix += qualifier
55+ stripped = stripped [len (qualifier ) :].lstrip ()
56+ changed = True
57+ break
58+
59+ if decorator_prefix and stripped :
60+ qualified_inner = scope .qualify_name (stripped )
61+ if qualified_inner is not None :
62+ return decorator_prefix + qualified_inner
63+
64+ return prefix
65+
66+
67+ def _qualify_type_str_impl (type_str : str , scope : Scope , qualify_base : bool ) -> str :
68+ """Implementation of type string qualification with control over base type handling."""
3169 if not type_str :
3270 return type_str
3371
@@ -44,28 +82,57 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:
4482 template_args = type_str [angle_start + 1 : angle_end ]
4583 suffix = type_str [angle_end + 1 :]
4684
47- # Qualify the prefix (outer type before the template)
48- qualified_prefix = scope .qualify_name (prefix ) or prefix
85+ # Qualify the prefix (outer type before the template) only if requested
86+ # Use recursive qualification to handle leading decorators like "const *Type"
87+ if qualify_base :
88+ # Try simple qualification first
89+ simple_qualified = scope .qualify_name (prefix )
90+ if simple_qualified is not None :
91+ qualified_prefix = simple_qualified
92+ else :
93+ # Handle prefixes with leading decorators (const, *, &, etc.)
94+ qualified_prefix = _qualify_prefix_with_decorators (prefix , scope )
95+ else :
96+ qualified_prefix = prefix
4997
50- # Split template arguments and qualify each one
98+ # Split template arguments and qualify each one (always qualify args)
5199 args = _split_arguments (template_args )
52- qualified_args = [qualify_type_str (arg .strip (), scope ) for arg in args ]
100+ qualified_args = [
101+ _qualify_type_str_impl (arg .strip (), scope , qualify_base = True )
102+ for arg in args
103+ ]
53104 qualified_template = "<" + ", " .join (qualified_args ) + ">"
54105
55106 # Recursively qualify the suffix (handles nested templates, pointers, etc.)
56- qualified_suffix = qualify_type_str (suffix , scope ) if suffix else ""
107+ qualified_suffix = (
108+ _qualify_type_str_impl (suffix , scope , qualify_base ) if suffix else ""
109+ )
57110
58111 return qualified_prefix + qualified_template + qualified_suffix
59112
60- # Handle leading qualifiers (const, volatile) that prevent qualify_name
61- # from matching. Strip them, qualify the rest, and prepend back.
62- for qualifier in ("const " , "volatile " ):
63- if type_str .startswith (qualifier ):
64- inner = type_str [len (qualifier ) :]
65- qualified_inner = qualify_type_str (inner , scope )
66- if qualified_inner != inner :
67- return qualifier + qualified_inner
68- break
113+ # If not qualifying base types, return as-is for non-template types
114+ if not qualify_base :
115+ return type_str
116+
117+ # Handle leading const/volatile qualifiers.
118+ # Strip leading qualifiers, qualify the rest, and prepend back.
119+ stripped = type_str .lstrip ()
120+ prefix = ""
121+ changed = True
122+ while changed :
123+ changed = False
124+ # Handle leading const/volatile qualifiers (must have trailing space)
125+ for qualifier in ("const " , "volatile " ):
126+ if stripped .startswith (qualifier ):
127+ prefix += qualifier
128+ stripped = stripped [len (qualifier ) :].lstrip ()
129+ changed = True
130+ break
131+
132+ if prefix and stripped :
133+ qualified_inner = _qualify_type_str_impl (stripped , scope , qualify_base = True )
134+ if qualified_inner != stripped :
135+ return prefix + qualified_inner
69136
70137 # Try qualifying the entire string (handles simple cases without templates)
71138 qualified = scope .qualify_name (type_str )
0 commit comments