Skip to content

Commit 7b38dc5

Browse files
committed
fix(variants): require braces in payloadless variant initializer syntax
1 parent 3d2ef2e commit 7b38dc5

8 files changed

Lines changed: 60 additions & 99 deletions

File tree

docs/grammar.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,7 @@ struct_field ::= identifier "=" expr
510510
### Variant Literals
511511

512512
```ebnf
513-
variant_literal ::= qualified_identifier "." identifier
514-
| qualified_identifier "." identifier "{" [ expr ] "}"
513+
variant_literal ::= qualified_identifier "." identifier "{" [ expr ] "}"
515514
```
516515

517516
Examples:
@@ -520,7 +519,7 @@ Examples:
520519
Value.INT{3}
521520
Value.TEXT{"hi"}[r]
522521
Value.INT{}
523-
Value.EOF
522+
Value.EOF{}
524523
```
525524

526525
### Vector Literals
@@ -555,8 +554,8 @@ These forms are parsed first and refined later:
555554

556555
- `EnumName.VALUE` is initially parsed as member access, then rewritten as an
557556
enum value during semantic analysis.
558-
- `VariantName.TAG` is initially parsed as member access, then rewritten during semantic
559-
analysis as either a payloadless variant value or a variant tag marker.
557+
- `VariantName.TAG` is initially parsed as member access, then rewritten as a variant tag marker
558+
during semantic analysis. Constructing a variant value always requires braces.
560559
- `value[VariantName.TAG]` is syntactically a subscript expression and becomes
561560
variant payload access only after semantic resolution.
562561

docs/overview/11-variants.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ v2 := Value.INT{}
2828
2929
v3 := Value.TEXT{"hello"}
3030
v4 := Value.NUMS{[10 of 0]}
31-
v5 := Value.EOF
31+
v5 := Value.EOF{}
3232
```
3333

3434

@@ -86,9 +86,9 @@ Each `case`:
8686
- Tests the tag
8787
- **Binds** the payload to a local name (`v`, `msg`, `vec`).
8888

89-
Tags may omit their payload type. Construct and match those tags without braces, as with
90-
`Value.EOF`; braces always indicate a payload. If every tag has no payload, declare an `enum`
91-
instead of a `variant`.
89+
Tags may omit their payload type. Construct those tags with empty braces, as with
90+
`Value.EOF{}`, and match them without braces, as with `case Value.EOF:`. If every tag has no
91+
payload, declare an `enum` instead of a `variant`.
9292

9393
When printed, variants show their type, active tag, and payload when present: for example,
9494
`<Value INT=7>` and `<Value EOF>`.

examples/variants.jik

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func main():
4141
show_result(compute(Op.ADD, 2, 3, _))
4242
show_result(compute(Op.MUL, 4, 5, _))
4343
show_result(compute(Op.MUL, 7, 0, _))
44-
finished := CalcResult.END
44+
finished := CalcResult.END{}
4545
show_result(finished)
4646
println("done: ", finished.is_done())
4747
end

src/jik/parser.c

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,6 @@ jik_parser_parse_primary(JikParser *p)
372372
}
373373
}
374374
else if (tok->type == TOK_LANG) {
375-
if (node->type == NODE_EXPR_MEMBER_ACCESS &&
376-
node->val_member_access.node->type == NODE_EXPR_IDENTIFIER &&
377-
jik_parser_match_token_sequence(
378-
p, (JikTokenType[]){TOK_LANG, TOK_DOT, TOK_ID, TOK_RANG, TOK_ERROR})) {
379-
JikNode *ret = jik_node_new_variant_new(node->val_member_access.node,
380-
NULL,
381-
node->val_member_access.member_name,
382-
jik_parser_current_context(p),
383-
node->token);
384-
jik_set_alloc_spec(ret, jik_parser_get_region_spec(p));
385-
node = ret;
386-
continue;
387-
}
388375
JikToken *lang_tok = jik_parser_eat_token(p, TOK_LANG);
389376
JikNode *expr = NULL;
390377
if (jik_parser_current_token(p)->type != TOK_COLON) {
@@ -1438,16 +1425,11 @@ jik_parser_parse_variant_new(JikParser *p, JikToken *var_name_tok, char *module_
14381425
JikToken *variant = jik_parser_eat_token(p, TOK_ID);
14391426
jik_parser_eat_token(p, TOK_LCURL);
14401427
jik_parser_eat_newlines_if_found(p);
1441-
if (jik_parser_current_token(p)->type == TOK_RCURL) {
1442-
jik_parser_eat_token(p, TOK_RCURL);
1443-
JikNode *ret = jik_node_new_variant_new(
1444-
id, NULL, variant->lexeme, jik_parser_current_context(p), var_name_tok);
1445-
ret->val_variant_new.has_initializer_syntax = true;
1446-
jik_set_alloc_spec(ret, jik_parser_get_region_spec(p));
1447-
return ret;
1428+
JikNode *init_expr = NULL;
1429+
if (jik_parser_current_token(p)->type != TOK_RCURL) {
1430+
init_expr = jik_parser_parse_expr(p);
1431+
jik_parser_eat_newlines_if_found(p);
14481432
}
1449-
JikNode *init_expr = jik_parser_parse_expr(p);
1450-
jik_parser_eat_newlines_if_found(p);
14511433
jik_parser_eat_token(p, TOK_RCURL);
14521434
JikNode *ret = jik_node_new_variant_new(
14531435
id, init_expr, variant->lexeme, jik_parser_current_context(p), var_name_tok);

src/jik/semantic.c

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,7 @@ jik_semantic_reject_invalid_value_expr(JikNode *nd)
197197
nd->val_variant_new.name->val_id.module_id,
198198
nd->token->module_id);
199199
if (variant && variant->type == NODE_VARIANT &&
200-
jik_variant_tag_is_payloadless(variant, nd->val_variant_new.tag)) {
201-
jik_diag_fatal_error_if(nd->val_variant_new.has_initializer_syntax,
202-
"payloadless variant tag cannot use braces",
203-
JIK_STRING_NCAT("use ",
204-
nd->val_variant_new.name->val_id.name,
205-
".",
206-
nd->val_variant_new.tag));
207-
}
208-
else if (variant && variant->type == NODE_VARIANT &&
209-
!nd->val_variant_new.has_initializer_syntax) {
200+
!nd->val_variant_new.has_initializer_syntax) {
210201
jik_diag_fatal_error(JIK_STRING_NCAT("variant tag cannot be used as a value; use ",
211202
nd->val_variant_new.name->val_id.name,
212203
".",
@@ -681,33 +672,6 @@ jik_semantic_resolve_symbols(JikSemanticAnalyzer *sa)
681672
*member = *tag;
682673
}
683674
}
684-
else if (nd->type == NODE_EXPR_SUBSCRIPT_GET &&
685-
nd->val_subscript_get.node->type == NODE_EXPR_MEMBER_ACCESS &&
686-
nd->val_subscript_get.node->val_member_access.node->type ==
687-
NODE_EXPR_IDENTIFIER &&
688-
nd->val_subscript_get.expr->type == NODE_EXPR_IDENTIFIER) {
689-
JikNode *member = nd->val_subscript_get.node;
690-
JikNode *variant = jik_scope_get_symbol(
691-
nd->context,
692-
member->val_member_access.node->val_id.name,
693-
member->val_member_access.node->val_id.module_id,
694-
nd->token->module_id);
695-
if (variant && variant->type == NODE_VARIANT &&
696-
jik_variant_tag_is_payloadless(variant, member->val_member_access.member_name)) {
697-
JikNode *ret = jik_node_new_variant_new(member->val_member_access.node,
698-
NULL,
699-
member->val_member_access.member_name,
700-
nd->context,
701-
nd->token);
702-
ret->val_variant_new.name->val_id.module_id = variant->token->module_id;
703-
jik_set_alloc_spec(ret,
704-
(JikAllocSpec){.kind = JIK_ALLOC_NAMED_REGION,
705-
.src = JIK_ALLOC_SRC_UNKNOWN,
706-
.region_name =
707-
nd->val_subscript_get.expr->val_id.name});
708-
*nd = *ret;
709-
}
710-
}
711675
else if (nd->type == NODE_EXPR_MEMBER_ACCESS) {
712676
if (nd->val_member_access.node->type == NODE_EXPR_IDENTIFIER) {
713677
char *module_id = nd->val_member_access.node->val_id.module_id
@@ -729,31 +693,15 @@ jik_semantic_resolve_symbols(JikSemanticAnalyzer *sa)
729693
jik_token_to_text(nd->token));
730694
}
731695
else if (enum_node && enum_node->type == NODE_VARIANT) {
732-
if (jik_variant_tag_is_payloadless(enum_node,
733-
nd->val_member_access.member_name)) {
734-
JikNode *var_new_nd = jik_node_new_variant_new(
735-
nd->val_member_access.node,
736-
NULL,
737-
nd->val_member_access.member_name,
738-
nd->context,
739-
nd->token);
740-
var_new_nd->val_variant_new.name->val_id.module_id = module_id;
741-
jik_set_alloc_spec(var_new_nd,
742-
(JikAllocSpec){.kind = JIK_ALLOC_LOCAL,
743-
.src = JIK_ALLOC_SRC_LOCAL});
744-
*nd = *var_new_nd;
745-
}
746-
else {
747-
JikNode *var_new_nd =
748-
jik_node_new_variant_tag(nd->val_member_access.node,
749-
nd->val_member_access.member_name,
750-
nd->context,
751-
nd->token);
752-
nd->type = var_new_nd->type;
753-
nd->jik_type = var_new_nd->jik_type;
754-
nd->val_variant_tag.name = var_new_nd->val_variant_tag.name;
755-
nd->val_variant_tag.tag = var_new_nd->val_variant_tag.tag;
756-
}
696+
JikNode *var_new_nd =
697+
jik_node_new_variant_tag(nd->val_member_access.node,
698+
nd->val_member_access.member_name,
699+
nd->context,
700+
nd->token);
701+
nd->type = var_new_nd->type;
702+
nd->jik_type = var_new_nd->jik_type;
703+
nd->val_variant_tag.name = var_new_nd->val_variant_tag.name;
704+
nd->val_variant_tag.tag = var_new_nd->val_variant_tag.tag;
757705
}
758706
}
759707
}

test/jik/test_compile_fail_semantic.jik

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,37 @@ end
456456
"identifier \"_Private\" is reserved by the C backend")
457457
end
458458

459+
func test_compile_fail_30(ts):
460+
utils::assert_compile_fail_source(ts,
461+
"""
462+
variant Value:
463+
EMPTY
464+
end
465+
466+
func main():
467+
value := Value.EMPTY
468+
end
469+
""",
470+
"variant tag cannot be used as a value"
471+
)
472+
473+
utils::assert_compile_fail_source(ts,
474+
"""
475+
variant Value:
476+
EMPTY
477+
end
478+
479+
func invalid(r: Region):
480+
value := Value.EMPTY[r]
481+
end
482+
483+
func main():
484+
end
485+
""",
486+
"unsubscriptable type"
487+
)
488+
end
489+
459490
func run_tests(ts):
460491
test_compile_fail_01(ts)
461492
test_compile_fail_02(ts)
@@ -486,4 +517,5 @@ func run_tests(ts):
486517
test_compile_fail_27(ts)
487518
test_compile_fail_28(ts)
488519
test_compile_fail_29(ts)
520+
test_compile_fail_30(ts)
489521
end

test/jik/test_table.jik

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func test_table_variant_tags(ts):
114114
first := Token.IDENT{"first"}
115115
second := Token.IDENT{"second"}
116116
number := Token.NUMBER{42}
117-
end_token := Token.END
117+
end_token := Token.END{}
118118

119119
test::suite_assert(ts, TokenNames[first] == "identifier", site())
120120
test::suite_assert(ts, TokenNames[second] == "identifier", site())

test/jik/test_variant.jik

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,11 @@ variant Token:
154154
end
155155

156156
func make_eof(r: Region) -> Token:
157-
return Token.EOF[r]
157+
return Token.EOF{}[r]
158158
end
159159

160160
func test_var_07(ts):
161-
eof := Token.EOF
161+
eof := Token.EOF{}
162162
test::suite_assert(ts, eof is Token.EOF, site())
163163

164164
match eof:
@@ -168,12 +168,12 @@ func test_var_07(ts):
168168
test::suite_assert(ts, true, site())
169169
end
170170

171-
tokens := [Token.EOF, Token.INTEGER{42}]
171+
tokens := [Token.EOF{}, Token.INTEGER{42}]
172172
test::suite_assert(ts, tokens[0] is Token.EOF, site())
173173
test::suite_assert(ts, tokens[1][Token.INTEGER] == 42, site())
174174

175175
holder := TokenHolder{}
176-
holder.token = Token.EOF[.holder]
176+
holder.token = Token.EOF{}[.holder]
177177
test::suite_assert(ts, holder.token is Token.EOF, site())
178178

179179
holder.token = make_eof(.holder)

0 commit comments

Comments
 (0)