@@ -1369,6 +1369,110 @@ static bool _outputDeclHasSemantic(
13691369}
13701370
13711371
1372+ // A user-defined generic struct found in an entry-point signature type, paired
1373+ // with the source location of its use.
1374+ struct GenericStructTypeUse
1375+ {
1376+ StructDecl* structDecl;
1377+ SourceLoc useLoc;
1378+ };
1379+
1380+ // Collect every user-defined generic struct (e.g. `Foo<int>`) reachable from an
1381+ // entry-point signature type `type`, recursing through wrapper/composite types so
1382+ // that `Foo<int>`, `Foo<int>[N]`, `Optional<Foo<int>>`, and
1383+ // `ConstantBuffer<Foo<int>>` are all found. Results are appended to `outUses` and
1384+ // `visited` guards against cycles in the `Val` graph.
1385+ //
1386+ // This is needed because the general capability-inference walk
1387+ // (`SemanticsDeclReferenceVisitor`) records a type's requirements only when its
1388+ // decl-ref is a `DirectDeclRef`; a generic specialization uses a
1389+ // `GenericAppDeclRef` and is skipped, so a `[require(...)]` on a generic struct
1390+ // used in an entry-point signature is otherwise dropped. The non-generic spelling
1391+ // `Foo` is already handled by that walk, so only the generic case is collected
1392+ // here (to avoid duplicate reporting).
1393+ //
1394+ // This deliberately lives in entry-point validation rather than in the general
1395+ // inference walk: inferring a generic struct type's requirements for *every*
1396+ // function that names such a type would require many core-module library
1397+ // functions (e.g. the cooperative vector/matrix/tensor `Load`/`Store` helpers,
1398+ // which take `CoopVec<T,N>` etc.) to redeclare those capabilities. Restricting
1399+ // the check to entry-point signatures matches the reported defect without
1400+ // changing library-function inference.
1401+ //
1402+ // Only the struct decl itself is filtered for `MagicTypeModifier`/
1403+ // `IntrinsicTypeModifier`: builtin generic types (e.g. `LineStream<T>`,
1404+ // `OutputPatch<T,N>`) already have dedicated, more specific entry-point
1405+ // diagnostics, so reporting a generic capability error for them would only
1406+ // duplicate those. Wrapper builtins are still recursed *through* so that a
1407+ // user-defined `Foo<int>` nested inside them is found.
1408+ static void collectGenericStructTypeUses (
1409+ ASTBuilder* astBuilder,
1410+ Val* type,
1411+ SourceLoc useLoc,
1412+ HashSet<Val*>& visited,
1413+ List<GenericStructTypeUse>& outUses,
1414+ UInt recursionDepth = 0 )
1415+ {
1416+ if (!type || !visited.add (type))
1417+ return ;
1418+
1419+ // Bound the recursion to avoid overflowing the stack on a legitimately deep
1420+ // acyclic chain (e.g. `Wrap<Wrap<...<Foo<int>>...>>`), where each level is a
1421+ // distinct hash-consed `Val` that the visited set does not collapse. This
1422+ // mirrors the `kMaxTypeNestingDepth` guard used by the other type walks in
1423+ // this file; a type nested past that limit is already diagnosed with
1424+ // "maximum type nesting level exceeded" by `validateVaryingType`, which runs
1425+ // earlier in `validateEntryPoint`, so we simply stop descending here.
1426+ if (recursionDepth >= kMaxTypeNestingDepth )
1427+ return ;
1428+
1429+ if (auto declRefType = as<DeclRefType>(type))
1430+ {
1431+ auto structDeclRef = declRefType->getDeclRef ().as <StructDecl>();
1432+ if (structDeclRef && as<GenericAppDeclRef>(declRefType->getDeclRefBase ()) &&
1433+ !structDeclRef.getDecl ()->findModifier <MagicTypeModifier>() &&
1434+ !structDeclRef.getDecl ()->findModifier <IntrinsicTypeModifier>())
1435+ {
1436+ // Only contribute structs that actually carry a requirement; this keeps
1437+ // both the aggregation and the diagnostic loop free of null/empty sets.
1438+ auto * caps = structDeclRef.getDecl ()->inferredCapabilityRequirements ;
1439+ if (caps && !caps->isEmpty ())
1440+ outUses.add ({structDeclRef.getDecl (), useLoc});
1441+ }
1442+ // Recurse through the struct's fields *with substitutions applied*, so a
1443+ // wrapper like `struct Wrapper<T> { Foo<T> f; }` used as `Wrapper<int>`,
1444+ // or a non-generic `struct Wrapper { Foo<int> f; }`, still reaches
1445+ // `Foo<int>` (which the `Val`-operand walk below alone would miss, since
1446+ // the field type is not an operand of the wrapper type).
1447+ if (structDeclRef)
1448+ {
1449+ for (auto fieldDeclRef :
1450+ getFields (astBuilder, structDeclRef, MemberFilterStyle::Instance))
1451+ collectGenericStructTypeUses (
1452+ astBuilder,
1453+ getType (astBuilder, fieldDeclRef),
1454+ useLoc,
1455+ visited,
1456+ outUses,
1457+ recursionDepth + 1 );
1458+ }
1459+ }
1460+
1461+ // Recurse into the type's `Val` operands (generic arguments, element types,
1462+ // etc.) so nested user generic structs inside wrappers/arrays are found.
1463+ for (Index i = 0 ; i < type->getOperandCount (); i++)
1464+ {
1465+ if (type->m_operands [i].kind == ValNodeOperandKind::ValNode)
1466+ collectGenericStructTypeUses (
1467+ astBuilder,
1468+ type->getOperand (i),
1469+ useLoc,
1470+ visited,
1471+ outUses,
1472+ recursionDepth + 1 );
1473+ }
1474+ }
1475+
13721476// Validate that an entry point function conforms to any additional
13731477// constraints based on the stage (and profile?) it specifies.
13741478void validateEntryPoint (EntryPoint* entryPoint, DiagnosticSink* sink)
@@ -1923,13 +2027,64 @@ void validateEntryPoint(EntryPoint* entryPoint, DiagnosticSink* sink)
19232027 }
19242028 }
19252029
2030+ // Augment the entry point's inferred requirements with the capability
2031+ // requirements of the *generic* struct types in its signature (parameters and
2032+ // return type). The general inference walk records a type's requirements only
2033+ // when its decl-ref is a `DirectDeclRef`, so a non-generic struct such as
2034+ // `Foo a` is already covered there, but a generic one such as `Foo<int> a`
2035+ // (whose decl-ref is a `GenericAppDeclRef`) is not. We gather the missing
2036+ // generic-struct requirements here so a `[require(...)]` on `Foo` is enforced
2037+ // for both spellings. `signatureStructUses` keeps each contributing struct and
2038+ // its use location so we can point the diagnostic at the exact use site (the
2039+ // non-generic case is reported by `diagnoseMissingCapabilityProvenance`).
2040+ CapabilitySet entryPointInferredCaps{entryPointFuncDecl->inferredCapabilityRequirements };
2041+ List<GenericStructTypeUse> signatureStructUses;
2042+ {
2043+ auto astBuilder = linkage->getASTBuilder ();
2044+ // Use a fresh `visited` set per signature position. `Val` nodes are
2045+ // hash-consed, so the same specialization `Foo<int>` on two parameters is
2046+ // the identical `Val*`; a shared set would drop the second use site and the
2047+ // user would see only one "see using of 'Foo'" note. The per-position set
2048+ // still guards against cycles within a single type.
2049+ for (auto param : entryPointFuncDecl->getParameters ())
2050+ {
2051+ // Prefer the written type-expression location (the `Foo<int>` use
2052+ // site); fall back to the parameter location if no type syntax was
2053+ // retained.
2054+ SourceLoc useLoc = (param->type .exp ) ? param->type .exp ->loc : param->loc ;
2055+ HashSet<Val*> visited;
2056+ collectGenericStructTypeUses (
2057+ astBuilder,
2058+ param->getType (),
2059+ useLoc,
2060+ visited,
2061+ signatureStructUses);
2062+ }
2063+ // The return type has the same silent-compile bug as parameters: a
2064+ // `Foo<int> main()` whose `Foo` requires an unavailable capability must be
2065+ // diagnosed too.
2066+ SourceLoc returnLoc = (entryPointFuncDecl->returnType .exp )
2067+ ? entryPointFuncDecl->returnType .exp ->loc
2068+ : entryPointFuncDecl->loc ;
2069+ HashSet<Val*> visited;
2070+ collectGenericStructTypeUses (
2071+ astBuilder,
2072+ entryPointFuncDecl->returnType .type ,
2073+ returnLoc,
2074+ visited,
2075+ signatureStructUses);
2076+ }
2077+ // Every collected use carries a non-empty requirement (filtered in the
2078+ // collector), so this join is unconditional.
2079+ for (auto & use : signatureStructUses)
2080+ entryPointInferredCaps.nonDestructiveJoin (use.structDecl ->inferredCapabilityRequirements );
2081+
19262082 for (auto target : linkage->targets )
19272083 {
19282084 auto targetCaps = target->getTargetCaps ();
19292085 auto stageCapabilitySet = entryPoint->getProfile ().getCapabilityName ();
19302086 targetCaps.join (stageCapabilitySet);
1931- if (targetCaps.isIncompatibleWith (
1932- CapabilitySet{entryPointFuncDecl->inferredCapabilityRequirements }))
2087+ if (targetCaps.isIncompatibleWith (entryPointInferredCaps))
19332088 {
19342089 // Incompatable means we don't support a set of abstract atoms.
19352090 // Diagnose that we lack support for 'stage' and 'target' atoms with our provided
@@ -1952,6 +2107,33 @@ void validateEntryPoint(EntryPoint* entryPoint, DiagnosticSink* sink)
19522107 sink,
19532108 entryPointFuncDecl,
19542109 failedSet);
2110+
2111+ // The provenance walk above follows `capabilityRequirementProvenance`,
2112+ // which does not record generic struct signature types. Point at any
2113+ // such struct whose requirement is itself incompatible with the
2114+ // target, mirroring the notes emitted for non-generic structs.
2115+ for (auto & use : signatureStructUses)
2116+ {
2117+ if (!targetCaps.isIncompatibleWith (
2118+ CapabilitySet{use.structDecl ->inferredCapabilityRequirements }))
2119+ continue ;
2120+ maybeDiagnose (
2121+ sink,
2122+ linkage->m_optionSet ,
2123+ DiagnosticCategory::Capability,
2124+ Diagnostics::SeeUsingOf{.decl = use.structDecl , .location = use.useLoc });
2125+ maybeDiagnose (
2126+ sink,
2127+ linkage->m_optionSet ,
2128+ DiagnosticCategory::Capability,
2129+ Diagnostics::SeeDefinitionOf{.decl = use.structDecl });
2130+ if (auto requireAttr = use.structDecl ->findModifier <RequireCapabilityAttribute>())
2131+ maybeDiagnose (
2132+ sink,
2133+ linkage->m_optionSet ,
2134+ DiagnosticCategory::Capability,
2135+ Diagnostics::SeeDeclarationOfModifier{.modifier = requireAttr});
2136+ }
19552137 }
19562138 else
19572139 {
@@ -1989,13 +2171,11 @@ void validateEntryPoint(EntryPoint* entryPoint, DiagnosticSink* sink)
19892171
19902172 // Only attempt to error if a specific profile or capability is requested
19912173 if ((specificCapabilityRequested || specificProfileRequested) &&
1992- targetCaps.atLeastOneSetImpliedInOther (
1993- CapabilitySet{entryPointFuncDecl->inferredCapabilityRequirements }) ==
2174+ targetCaps.atLeastOneSetImpliedInOther (entryPointInferredCaps) ==
19942175 CapabilitySet::ImpliesReturnFlags::NotImplied)
19952176 {
19962177 CapabilitySet combinedSets = targetCaps;
1997- combinedSets.join (
1998- CapabilitySet{entryPointFuncDecl->inferredCapabilityRequirements });
2178+ combinedSets.join (entryPointInferredCaps);
19992179 CapabilityAtomSet addedAtoms{};
20002180 if (auto targetCapSet = targetCaps.getAtomSets ())
20012181 {
0 commit comments