Commit Graph

4 Commits

Author SHA1 Message Date
William S Fulton eb3fe4f6a1 C++20: parse type constrained template parameters
A C++20 type-constraint (e.g. 'Numeric T') used in place of 'typename' /
'class' in a template parameter list is now recognised as the standard
shorthand for 'typename T' plus a 'requires Concept<T>' clause.  The
parm fallback in 'templateparameter' classifies the parsed parm via
classify_template_param_type():

  - resolves to a concept in scope -> rewrite to 'typename T' (or
    'v.typename Ts...' for a variadic pack) and attach a 'concept-id'
    constraint atom on the parm's "constraint" attribute, matching the
    parm representation promote_abbreviated_template() already builds
    for 'Concept auto x';
  - resolves to a typedef, class or enum, or names a primitive -> leave
    the parm as a non-type template parameter;
  - is an unqualified identifier not declared anywhere SWIG has seen ->
    issue an error so the user knows to make the concept visible.

The classifier is built on existing SwigType helpers - SwigType_type
covers primitives (including multi word forms like 'unsigned int' /
'long long' and resolvable typedefs such as 'size_t'); SwigType_isenum,
SwigType_issimple, SwigType_istemplate and SwigType_isvariadic /
SwigType_del_variadic handle decoration, enum and template-id forms.

  template<Numeric T>             T cube(T x);
  template<nest::Integral T>      T half(T x);                   // ::-qualified
  template<Numeric T, typename U> T scale(T x, U factor);        // mixed
  template<Numeric T = int>       T identity(T x);               // default arg
  template<Numeric... Ts>         int count_numeric(Ts...);      // variadic
  template<typename X, Numeric... Ts>     int tag_count(X, Ts...);
  template<SmallNumeric X, Numeric... Ts> int small_tag_count(X, Ts...);
  template<Numeric T> class Box { T v; };                        // class template

A type-constraint that itself takes template arguments before the
parameter name (e.g. 'template<std::convertible_to<int> T>') is not yet
parsed and must still be moved to a 'requires'-clause.

Includes new errors/cpp_concept_not_visible test exercising the
"undeclared concept" error path, plus a non-template-member-with-
requires-clause case in cpp20_concepts_extra exercising
ConstrainedHolder<T>::cube() const requires Numeric<T> (no parser
change needed).

Assisted-by: Claude Code (Opus 4.7)
2026-05-12 23:04:20 +01:00
William S Fulton ad7b85eda2 C++20 concepts: extend tests and document broader support
Add testcases for concept constrained overload by arity, member operator
overloads, structural partial specialization with a requires-clause, and
the concept only function template redefinition that trips warning 302
(errors/cpp_concept_redefinition).  Document existing support for
constrained class templates and member function templates of plain
classes, and the limitations around constraint subsumption: same-
signature templates differing only by requires-clause are dropped, and
concept only "partial specializations" are not selected.

Assisted-by: Claude Code (Opus 4.7)
2026-05-12 23:04:20 +01:00
William S Fulton 6d03096667 C++20 concepts: combine prefix and trailing requires-clauses structurally
cpp_template_decl's mid rule action no longer string concatenates the
prefix and trailing requires-clauses with ' && '.  Instead, it finds
any existing trailing constraint subtree on the inner cdecl/class
node, removes it from the child chain, and conjoins it with the
prefix subtree via Constraint_combine("and", prefix, trailing).  The
result is a single op="and" constraint subtree (or the bare prefix
when no trailing is present), and the rendered "requires" string is
regenerated from that subtree via Constraint_str.

For 'both_clauses' from cpp20_concepts_extra.i

  template<typename T>
  requires Numeric<T>
  T both_clauses(T x) requires Sized<T> { return x + x; }

the cdecl now carries a single constraint subtree:

  +++ constraint op="and"
        +++ constraint op="atom" kind="concept-id" name="Numeric<(T)>"
        +++ constraint op="atom" kind="concept-id" name="Sized<(T)>"

with the flat 'requires' string "Numeric<(T)> && Sized<(T)>" derived
from it.  After %template(both_clauses_int) both_clauses<int>; the
two atoms substitute to "Numeric<(int)>" and "Sized<(int)>" via the
structural walker added in commit b38b45cb6.

Per [temp.constr.decl]/3, the C++20 standard conjoins the prefix and
trailing requires-clauses via '&&'; the structural form preserves
that semantic exactly while producing a more useful tree for any
future consumer that wants to walk the conjunction operands.

A new both_clauses test case in cpp20_concepts_extra exercises the
combine path; the runtime tests confirm the wrapped function returns
the expected result.

Assisted-by: Claude Code (Opus 4.7)
2026-05-10 17:37:53 +01:00
William S Fulton e39776b377 cpp20_concepts: add extra constraint forms and out of line definition tests
A new cpp20_concepts_extra.i test case covers C++20 constraint primary
forms not previously exercised by the suite:

  - identity_non_numeric<T>   '(!Numeric<T>)' - negation only legal
                              when wrapped in parens
  - mix_add<T,U>              multi parameter requires-expression
                              binding two unrelated template parameters
  - AllNumeric<Ts...>         variadic concept defined by a fold-
                              expression over '&&'
  - sum_all<T,Rest...>        variadic function template constrained
                              by the variadic concept
  - trait_primary<T>          'std::is_integral_v<T>' as a constraint
                              atom (non-concept-id boolean primary)
  - deeper<T>                 deeper nesting of '&&' / '||' across
                              multiple parens levels

cpp20_concepts_classes.i gains an OutOfLineBox<T> case: the member
function template scaled<U> is declared in class with
'requires Numeric<U>' and defined out of line with the same prefix
requires-clause on its own template head, exercising the
requires_clause_opt path on a doubly templated declaration.

Matching Python and Java runme files validate the wrapped behaviour;
both new test cases are registered in Examples/test suite/common.mk
under CPP20_TEST_CASES.

No SWIG source changes are needed - the existing scanner level
constraint text capture handles every form added here.  The tests
serve as a baseline before the planned structural rework of the
constraint representation.

Assisted-by: Claude Code (Opus 4.7)
2026-05-10 17:37:52 +01:00