mirror of https://github.com/swig/swig.git
2 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
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)
|
|
|
|
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)
|