parser(step 50): functions #10
Loading…
Reference in a new issue
No description provided.
Delete branch "parser/functions"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
parser(step 50): functions
Implements Step 50 — Functions (
docs/parser/50-functions.md). Both spellings of a function, plus the shared parameter/return-type grammar step 60's impl/interface members reuse. Phase:parse.What's in
parse_decl/parse_decl_text— the declaration dispatcher and its text harness, parallel to the type/expr/stmt ones. Only thefunctionarm exists; steps 60/70 fill the rest.parse_function_decl—'function' qualified-name '(' params ')' ('->' type)? block, soVector3::dotscopes a declaration to a type.parse_lambda+at_func_value— thefunc-valuein expression position (whatconst f = (…) -> … {…}binds).->is not a binary operator, so one bounded peek to the matching)tells a lambda from a tuple or a group; nothing backtracks.parse_params/parse_param—this-aware (thiscarries no type); an ordinary parameter's annotation is optional.parse_var_decl— trailing;now optional only after a brace-bodiedfunc-valueinitializer, perlanguage-grammar.md▸ Functions.Design call
A
constbound to a lambda stays a var-decl statement whose initializer is anExpr::Lambda((const f =(lambda …))) — not aDecl::FunctionDecl. This follows the step-50 doc's acceptance test (line 80-82), which overrides its own AST-section prose. All three pinned goldens pass verbatim.Deliberately not done
The arrow-less
func-value((x: int) { … }): no fixture writes one, so left unread and said so inat_func_value's doc comment rather than adding an untested branch.Verify
cargo fmt --checkclean;cargo clippy --all --all-targets -- -D warningsclean;cargo test --all→ 16 lexer + 63 parser + fixture drivers, 0 failed. Parse-fixture driver stays#[ignore]d (step-70 job). All seven function fixtures (function-c-style,function-lambda,function-void,function-overloading,global-functions,static-functions,instance-functions) build a tree end to end.Both spellings the spec gives a function, and the shared parameter and return-type grammar step 60's impl/interface members will reuse. - `parse_decl` / `parse_decl_text`: the declaration dispatcher and its text harness, parallel to the type/expr/stmt ones. Only the `function` arm exists; steps 60 and 70 fill in the rest. - `parse_function_decl`: `'function' qualified-name '(' params ')' ('->' type?)? block`, so `Vector3::dot` scopes a declaration to a type. - `parse_lambda` + `at_func_value`: the `func-value` in *expression* position, which is what `const f = (…) -> … {…}` binds. `->` is not a binary operator, so one bounded peek to the matching `)` tells a lambda from a tuple or a group; nothing backtracks. - `parse_params` / `parse_param`: `this` carries no type, an ordinary parameter's annotation is optional. - `parse_var_decl` now admits the optional trailing `;` after a brace-bodied `func-value` (language-grammar.md ▸ Functions). A `const` bound to a lambda stays a `var-decl` statement whose initializer is an `Expr::Lambda` — not a declaration form of its own, per language-grammar.md ▸ Program and declarations. Phase: parse. All seven function fixtures (function-c-style, function-lambda, function-void, function-overloading, global-functions, static-functions, instance-functions) now build a tree; their shapes are pinned as goldens here until step 70 enables the fixture driver. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Review of parser(step 50): functions. Parse-phase only; I took the two settled calls as given (const-lambda = a var-decl whose init is Expr::Lambda, and empty parse()/ignored fixture driver = tracked #25/#26).
VERIFIED: fmt + clippy (-D warnings) + 63 parser tests all green. The shapes that ARE implemented parse correctly. Lambda-vs-tuple-vs-group is disambiguated soundly for the arrow form: the "-> after the matching close paren" heuristic cannot false-positive, because Arrow is not a binary operator and the depth-scan correctly skips nested lambdas, function-type parens, and inner arrows. Whole-declaration spans pass the pin test. I specifically stress-tested the new optional-semicolon-after-brace-lambda logic and found it sound: it keys on init being a TOP-LEVEL body-carrying Expr::Lambda, so a grouped / call / ternary-armed lambda and a trailing call all still require ';', and the block-terminated ASI is exactly what language-grammar.md blesses. No bug there.
Three findings, one 3 and two 1s, all in the func-value grammar contract and diagnostic quality -- none in the semicolon logic.
TOP FINDING (severity 3): at_func_value() / parse_lambda() realize only the
-> ... blockform of func-value; the canonical grammar's other parse-legal shapes are silently rejected or mis-parsed. See the inline comment on at_func_value.Verdict in one line: the implemented surface is correct and well-tested, but the func-value grammar contract is silently narrowed from the source of truth -- close that before a future fixture trips it. Severity 3.
@ -1554,0 +1657,4 @@/// lets the callee's signature supply the type, which is a semantic question/// rather than a syntactic one.fn parse_param(c: &mut Cursor) -> Result<Param, ParseError> {if let Some(tok) = c.eat_kw(KeywordKind::This) {SEVERITY 1. The grammar forbids a type on
this(param = 'this' | ident [':' type]), butfunction f(this: int) {}is reported as "expected ')' to close the parameter list" at the:. The code already applies the right pattern for the function NAME (lines 1616-1620 check it here so parse_qualified_name's type-focused message doesn't leak to the reader of a function decl); the same check is missing forthis. eat_kw(This) returns immediately and the illegal:falls through to the generic list-close error. A one-lineif c.is(Colon)after thethisarm, rejecting with "thistakes no type", would name the actual rule instead of the nearest delimiter.@ -1554,0 +1707,4 @@////// `->` is not a binary operator, so nothing in the expression grammar can/// follow a completed parenthesized group with an `Arrow`. That makes one/// bounded peek — scan to the matching `)`, look at the token after it —SEVERITY 1 (nit). The doc comment calls this "one bounded peek", but it is a full forward scan to the matching ')' -- every '(' in primary position rescans its entire group, so deeply stacked parenthesized input is O(n^2) overall. Correctness is fine (I verified it can't false-positive: Arrow is not a binary operator, and the depth counter correctly skips nested lambdas and function-type parens; the nth() clamp and the guaranteed depth>=1 at the opening '(' mean no underflow/panic). Only the "bounded" wording overstates what this does.
@ -1554,0 +1733,4 @@}k += 1;}c.nth(k + 1).kind == TokenKind::ArrowSEVERITY 3. at_func_value() decides a leading '(' is a func-value solely from whether the token after the matching ')' is an Arrow (this line). That realizes only the
-> ... blocksubset of the canonical production in language-grammar.md / Functions:func-value = "(" [ params ] ")" [ "->" [ type ] ] ( block | expr ); (line 378)
whose disambiguation note (lines 326-329) says a "(" is a func-value when a param-shape is inside OR
()is followed by a "->" OR a block. Because the arrow and the return type are BOTH optional, three parse-legal shapes are silently rejected or mis-parsed here:const g = () { return 1; };-- empty params, no arrow, block bodyat_func_value returns false; parse_tuple_or_group tries parse_expr on
)-> "expected an expression".
const g = (x: int) { ... };(and(x) { ... }) -- arrowless, block bodyat_func_value false; parse_tuple_or_group reads
x: intas a broken group-> "expected ')' after a parenthesized expression".
const g = (x: int) -> x * 2;-- expression bodyGrammar line 388 explicitly documents
-> expr("-> { or -> expr infers thetype"), but this branch is not read at all: the type parser reads
xas areturn TYPE, then parse_lambda's mandatory parse_block errors on
*.The code comment (lines 1714-1716) owns the arrowless case explicitly, but the
exprbody is never mentioned and neither shape is pinned by any test. No current fixture writes these, so nothing fails today -- but each is a production the source-of-truth grammar admits at parse phase, and each fails with a diagnostic pointing at the wrong rule. Per AGENTS.md the grammar is canonical; either narrow the grammar to block-only bodies (and say so there), or close these. The-> exprcase is the sharper of the two because it is undocumented in code and explicitly blessed in the grammar.