[clang] [clang][ThreadSafety] Instantiate a parameter's attribute naming a later parameter (PR #232784)

Jameson Nash via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 16 07:59:08 PDT 2026


================
@@ -5493,23 +5520,25 @@ TypeSourceInfo *TemplateDeclInstantiator::SubstFunctionType(
       // the function parameters themselves.
       const FunctionProtoType *OldProto =
           cast<FunctionProtoType>(OldProtoLoc.getType());
-      for (unsigned i = 0, i_end = OldProtoLoc.getNumParams(); i != i_end;
-           ++i) {
-        ParmVarDecl *OldParam = OldProtoLoc.getParam(i);
-        if (!OldParam) {
-          Params.push_back(SemaRef.BuildParmVarDeclForTypedef(
-              D, D->getLocation(), OldProto->getParamType(i)));
-          continue;
-        }
-
-        ParmVarDecl *Parm = SemaRef.SubstParmVarDecl(
-            OldParam, TemplateArgs, /*indexAdjustment=*/0,
-            /*NumExpansions=*/std::nullopt,
-            /*ExpectParameterPack=*/false, EvaluateConstraints);
-        if (!Parm)
-          return nullptr;
-        Params.push_back(Parm);
-      }
+      SmallVector<QualType, 4> ParamTypes;
+      Sema::ExtParameterInfoBuilder ExtParamInfos;
+      if (SemaRef.SubstParmTypes(D->getLocation(), OldProtoLoc.getParams(),
+                                 nullptr, TemplateArgs, ParamTypes, &Params,
+                                 ExtParamInfos, OldProto->param_type_begin(),
+                                 EvaluateConstraints))
+        return nullptr;
+      // Declare a parameter that was only a type in the prototype. A function
+      // declared through a typedef with an attribute that rewrites the
+      // function type in place, such as noreturn, gets a prototype whose
+      // parameters have no ParmVarDecl, so no attribute of the typedef's
+      // parameters reaches it; see TypeSpecLocFiller in SemaType.
+      // FIXME: Keep the typedef's parameters there instead. The dependent
+      // branch above returns null for such a prototype, dropping the member.
----------------
vtjnash wrote:

reviewer comment: The FIXME's last sentence refers to pre-existing bugs which needs redesign elsewhere, and then this branch goes away.

---

More details, mostly synthesized by Claude, since they aren't actually relevant to the PR, just for context on why this logic is needed:

Since d462aa5a, when the typedef is dependent, this same member would take the dependent path, and the added early return in that commit makes this member vanish silently:

    template <typename T> struct X {
      typedef void functype(T);
      __attribute__((noreturn)) functype nr;
    };
    void use(X<int> x) { x.nr(0); }   // error: no member named 'nr' in 'X<int>'

It is a pre-existing split in how SemaType applies function-type attributes, and the type-location builder has a FIXME where the split lands.

Two families of function-type attributes. handleFunctionTypeAttr in clang/lib/Sema/SemaType.cpp applies calling conventions and ns_returns_retained by wrapping the written type in an AttributedType, so the typedef sugar survives as the modified type and the type location still mirrors the declarator. The other family rewrites the function type in place through FunctionTypeUnwrapper, which desugars through the typedef and rebuilds a bare prototype: noreturn, regparm, nocf_check, cmse_nonsecure_call, no_caller_saved_registers, cfi_unchecked_callee, cfi_salt, and the Arm streaming family. On a typedef-declared method all of those produce the same shape. A quick dump of a typedef-declared method with each shows it:
```
┌──────────────────────────────────┬───────────────────────────────────────────────────┐
│            Attribute             │             Declared type after Sema              │
├──────────────────────────────────┼───────────────────────────────────────────────────┤
│ noreturn, regparm(1), nocf_check │ int (int, int) __attribute__((...)), typedef gone │
├──────────────────────────────────┼───────────────────────────────────────────────────┤
│ cdecl                            │ functype __attribute__((cdecl)), typedef kept     │
├──────────────────────────────────┼───────────────────────────────────────────────────┤
│ none                             │ functype                                          │
└──────────────────────────────────┴───────────────────────────────────────────────────┘
```
Where the null entries come from. GetTypeSourceInfoForDeclarator walks the declarator chunks and then hands the remaining type location to TypeSpecLocFiller to fill from the decl-specifier. That filler has cases for the type-specifier kinds it expects, and a default that just calls initialize with a FIXME saying other kinds should be added and the default turned into an assert. A bare prototype coming from a typedef specifier hits that default, and initialize leaves every parameter entry null. SubstFunctionType's second path is the consumer that has to cope.

**Should SemaType route it differently?** Arguably yes, as a separate cleanup: the in-place family could preserve sugar the way the calling-convention family does, or the filler could learn the case. The visible cost today is small, diagnostics print the desugared type and the parameters have no source ranges, which is why nobody has fixed it since 2013. It is orthogonal to attribute deferral, so for this branch the right handling is what the tree does: build the missing declarations from the substituted type and treat them as having no attributes to defer.

https://github.com/llvm/llvm-project/pull/232784


More information about the cfe-commits mailing list