Commit Graph

2 Commits

Author SHA1 Message Date
William S Fulton dcbd7f40ac C++20 concepts: parser builds the constraint subtree alongside the string
Replace the scanner level constraint capture from earlier commits with
proper bison productions that build a structured constraint subtree.
The flat 'requires' string attribute is still set (via Constraint_str
on the new subtree) so downstream consumers see no change while the
migration is in flight.

Grammar changes (Source/CParse/parser.y):

  constraint
      : constraint_or
  constraint_or
      : constraint_and
      | constraint_or LOR constraint_and       { Constraint_combine "or" }
  constraint_and
      : constraint_primary
      | constraint_and LAND constraint_primary { Constraint_combine "and" }
  constraint_primary
      : idcolon                                 { atom kind="concept-id" }
      | LPAREN { skip_balanced('(',')') }       { atom kind="expression" }
      | requires_expression                     { atom kind="requires-expression" }
  requires_expression
      : REQUIRES requirement_parameter_list_opt requirement_body
  requirement_parameter_list_opt
      : LPAREN parms RPAREN
      | %empty
  requirement_body
      : LBRACE { skip_balanced('{','}'); parse_requirement_seq(...) }

The four attachment points are rewired:

  - cpp_concept_decl    parses CONCEPT idcolon EQUAL constraint SEMI
  - cpp_const           takes 'REQUIRES constraint' as the trailing form
  - requires_clause_opt takes 'REQUIRES constraint' as the prefix form
  - valexpr             takes 'requires_expression' as a value position primary

The dtype struct's String *requires_clause becomes Node *constraint_node.

Two design choices follow from the bison conflict audit:

  - constraint_primary uses idcolon directly: idcolon already covers
    'Numeric<T,U>' via 'identifier less_valparms_greater', so no new
    LESSTHAN production is needed (and the LESSTHAN ambiguity that one
    would create is avoided).
  - LPAREN content in constraint_primary is opaque captured: SWIG users
    write parenthesised non constraint expressions in concept bodies
    (e.g. 'Numeric<T> && (sizeof(T) <= 4)') that are not constraint-
    expressions per [temp.constr] but are accepted by GCC/Clang as
    extensions.  Capturing the (...) span as a kind="expression" atom
    matches the preexisting scanner behaviour and avoids the LALR(1)
    conflict between 'LPAREN constraint RPAREN' and an expression
    fallback.  The requirement_body of a requires-expression is opaque-
    captured for the same reason - the audit predicted 1-3 S/R conflicts
    if valexpr were reused inside requirement context.

Source/CParse/cscanner.c gains parse_requirement_seq() and the
parse_one_requirement helper, which split the captured requirement-
body text at top level ';' (tracking '(' '[' '{' depth) and build a
chain of structured 'requirement' nodes with kind="simple" / "type" /
"compound" / "nested".  Compound requirements parse the noexcept
keyword and the optional '-> Concept' return-type-requirement
structurally; the inner expression bodies remain text stored.

Source/Modules/lang.cxx Dispatcher::emit_one is taught to no-op on
'constraint', 'requires-expression', and 'requirement' node types so
the metadata children attached to cdecl, template, and concept nodes
do not generate "Unrecognized parse tree node type" errors.

bison -Wall -Werror reports no new shift/reduce or reduce/reduce
conflicts.

Tests:
  - cpp20_concepts.i / _class_methods.i / _classes.i / _extra.i and
    cpp20_variable_templates.i pass for Python and Java.
  - The errors/cpp_template_concept test is updated to use a concept
    body 'std::integral<T>' that the new grammar accepts (the previous
    'true' literal needed an opaque expression fallback that the spec
    does not require).
  - cpp20_concepts_extra.i's 'Sized' concept gains parens around its
    sizeof expression for the same reason.

The scanner level scanner_capture_decl, scanner_capture_constraint,
and scanner_capture_prefix_requires_clause helpers are now dead code;
they are removed in a later commit along with the flat 'requires'
attribute.

Assisted-by: Claude Code (Opus 4.7)
2026-05-10 17:37:52 +01:00
William S Fulton 495aaeec17 C++20 concepts: embed in the parse tree and reject %template instantiation
Previously a 'concept' declaration was silently consumed by skip_decl
and a trailing/prefix requires-clause by skip_constraint /
skip_prefix_requires_clause; nothing reached the parse tree.  Make
concepts and constraint text first class so future passes can act on
them, and turn '%template' on a concept into a clear error instead of
silently producing a malformed wrapper.

Parse tree shape
----------------

A concept declaration is now a 'concept' node wrapped by the existing
'template' node (so the same machinery as a class/cdecl template head
applies, including symbol table registration and add_symbols).  After
cpp_template_decl runs the node contains:

  nodeType        - 'template'
  templatetype    - 'concept'
  name            - the concept identifier
  templateparms   - the template parameter list
  type            - 'bool'
  requires        - the constraint expression as a flat string

For function templates, a trailing requires-clause attached to the
declarator and a prefix requires-clause attached to the template head
both populate a 'requires' attribute on the wrapping template node.
When both are present they are joined with ' && ' to mirror the C++20
spec's normalization of associated constraints
([temp.constr.decl]).

Scanner: capture instead of discard
-----------------------------------

Source/CParse/cscanner.c factors the existing skip_decl,
skip_constraint and skip_prefix_requires_clause / skip_constraint_primary
into '_inner' helpers that thread an optional String *capture buffer
through the existing token loops.  The skip_constraint and
skip_prefix_requires_clause wrappers are dropped - their only callers
went through the capture path.  Three new exports take their place:

  scanner_capture_decl
  scanner_capture_constraint
  scanner_capture_prefix_requires_clause

A small capture_append_token helper records each token's raw text into
the capture buffer.  Subspans returned by Scanner_skip_balanced
preserve the source's original whitespace inside the delimiters; for
the rest, a separating space is inserted only where it is needed for
readability:

  - between two word character runs, so 'std' '::' 'integral' '<' 'T'
    '>' joins as 'std::integral<T>' rather than 'std :: integral < T >',
    while 'T' 't' (two ids) keeps the 'T t' spacing;
  - around the logical operators '&&' and '||', so a compound
    constraint reads 'Numeric<T> && SmallNumeric<T>' rather than
    'Numeric<T>&&SmallNumeric<T>'.

Grammar
-------

Source/CParse/parser.y:

  - Adds a String *requires_clause field to the dtype struct (Define).
    default_dtype zero initialises it.

  - cpp_concept_decl now consumes 'CONCEPT idcolon EQUAL', captures the
    constraint via scanner_capture_decl, and produces a 'concept' node
    with name/requires/type set.  add_symbols is left to the wrapping
    cpp_template_decl rule, matching what happens for class and cdecl
    template heads.

  - requires_clause_opt is typed <str> and returns the captured
    constraint text via scanner_capture_prefix_requires_clause.
    cpp_template_decl consumes $requires_clause_opt
    after cpp_template_possible reduces and Setattr's 'requires' on
    the inner template node, conjoining with any preexisting trailing
    'requires' attribute via ' && '.

  - cpp_const's REQUIRES branches use scanner_capture_constraint and
    propagate $$.requires_clause to c_decl, which Setattr's 'requires'
    on the resulting cdecl.

Template parameter substitution of 'requires'
---------------------------------------------

Source/CParse/templ.c appends Getattr(n, 'requires') to cpatchlist in the
cdecl and generic branches of cparse_template_expand, so the same template-
parameter substitution already used for 'code' rewrites the constraint at
%template instantiation time.  After

  template<typename T> requires Numeric<T> && SmallNumeric<T>
  T half(T x) { return x / 2; }
  %template(half_int) half<int>;

the instantiated cdecl contains:
  "requires" - "Numeric<int> && SmallNumeric<int>"'.

Error on '%template' applied to a concept
-----------------------------------------

Source/CParse/templ.c's Swig_cparse_template_locate now checks the
templatetype before falling into the class/classforward and function-
template branches; if it is 'concept' it emits

  Error: %template not allowed on concept 'Numeric' - concepts cannot
  be instantiated like class or function templates.

and returns 0 so the surrounding %template directive becomes a no-op
rather than producing a malformed wrapper.  A new errors test case
covers it: Examples/test suite/errors/cpp_template_concept.{i,stderr}.

Typepass
--------

Source/Modules/typepass.cxx's templateDeclaration supports the new 'concept'
value to mark it as a known templatetype, it is just another templatetype
carried by a 'template' node.

Out of scope
------------

The constraint expression is still a flat string.  A structured
representation - a 'requires' node carrying a parms ParmList plus
per requirement child nodes for the simple-, type-, compound- and
nested-requirement forms in [expr.prim.req], and a 'constraint' tree
for requires-clauses themselves - is left for a future commit; it
would also let templ.c reuse SwigType machinery instead of patching a
String.

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