Skip to content

Commit 241462a

Browse files
committed
feat(parser): add struct initializer shorthand
1 parent 21df3a4 commit 241462a

5 files changed

Lines changed: 157 additions & 3 deletions

File tree

docs/overview/07-structs.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ c1.port = 3
2929
c1.host = "foo"
3030
```
3131

32+
When a visible value has the same name as a field, its field initializer may
33+
omit `= value`:
34+
35+
```jik
36+
host := "localhost"
37+
port := 8080
38+
verbose := true
39+
40+
c2 := Config{host, port, verbose}
41+
c3 := Config{host = "example.com", port, verbose}
42+
```
43+
44+
`Config{host, port, verbose}` is exactly equivalent to
45+
`Config{host = host, port = port, verbose = verbose}`. These entries are always
46+
named, not positional: their order does not need to match the declaration order.
47+
3248
Struct values can be read and updated through their fields using the usual `.` syntax.
3349

3450
### 7.3 Uniform function calls

src/jik/parser.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,9 +1416,18 @@ jik_parser_parse_struct_new(JikParser *p, JikToken *struct_name_tok, char *modul
14161416
TabJikNode *init_vals = TabJikNode_new();
14171417
while ((tok = jik_parser_current_token(p)) != NULL && tok->type != TOK_RCURL) {
14181418
jik_parser_eat_newlines_if_found(p);
1419-
char *field_name = jik_parser_eat_token(p, TOK_ID)->lexeme;
1420-
jik_parser_eat_token(p, TOK_ASSIGN);
1421-
JikNode *expr = jik_parser_parse_expr(p);
1419+
JikToken *field_tok = jik_parser_eat_token(p, TOK_ID);
1420+
char *field_name = field_tok->lexeme;
1421+
JikNode *expr;
1422+
if (jik_parser_current_token(p)->type == TOK_ASSIGN) {
1423+
jik_parser_eat_token(p, TOK_ASSIGN);
1424+
expr = jik_parser_parse_expr(p);
1425+
}
1426+
else {
1427+
// Field shorthand: `Point{x}` is equivalent to `Point{x = x}`
1428+
expr = jik_node_new_identifier(
1429+
field_name, NULL, jik_parser_current_context(p), field_tok);
1430+
}
14221431
jik_parser_eat_newlines_if_found(p);
14231432
TabJikNode_set(init_vals, field_name, expr);
14241433
if (jik_parser_current_token(p)->type != TOK_RCURL) {

test/jik/test_compile_fail_semantic.jik

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,50 @@ end
487487
)
488488
end
489489

490+
func test_compile_fail_31(ts):
491+
utils::assert_compile_fail_source(ts,
492+
"""
493+
struct Foo:
494+
x: int
495+
end
496+
497+
func main():
498+
y := 1
499+
value := Foo{y}
500+
end
501+
""",
502+
"unknown field name: \"y\"")
503+
end
504+
505+
func test_compile_fail_32(ts):
506+
utils::assert_compile_fail_source(ts,
507+
"""
508+
struct Foo:
509+
x: int
510+
end
511+
512+
func main():
513+
value := Foo{x}
514+
end
515+
""",
516+
"undefined symbol: x")
517+
end
518+
519+
func test_compile_fail_33(ts):
520+
utils::assert_compile_fail_source(ts,
521+
"""
522+
struct Foo:
523+
x: int
524+
end
525+
526+
func main():
527+
x := "wrong"
528+
value := Foo{x}
529+
end
530+
""",
531+
"type mismatch: required int, got String")
532+
end
533+
490534
func run_tests(ts):
491535
test_compile_fail_01(ts)
492536
test_compile_fail_02(ts)
@@ -518,4 +562,7 @@ func run_tests(ts):
518562
test_compile_fail_28(ts)
519563
test_compile_fail_29(ts)
520564
test_compile_fail_30(ts)
565+
test_compile_fail_31(ts)
566+
test_compile_fail_32(ts)
567+
test_compile_fail_33(ts)
521568
end
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use "jik/testing" as test
2+
use "support/module_shapes" as shapes
3+
4+
5+
struct Settings:
6+
host: String
7+
port: int
8+
verbose: bool
9+
end
10+
11+
struct Label:
12+
text: String
13+
end
14+
15+
struct Holder:
16+
label: Label
17+
end
18+
19+
20+
func test_all_shorthand(ts):
21+
host := "localhost"
22+
port := 8080
23+
verbose := true
24+
settings := Settings{host, port, verbose}
25+
26+
test::suite_assert(ts, settings.host == "localhost", site())
27+
test::suite_assert(ts, settings.port == 8080, site())
28+
test::suite_assert(ts, settings.verbose, site())
29+
end
30+
31+
func test_mixed_and_omitted_fields(ts):
32+
port := 9000
33+
verbose := true
34+
settings := Settings{port, host = "example.com", verbose}
35+
defaults := Settings{port}
36+
37+
test::suite_assert(ts, settings.host == "example.com", site())
38+
test::suite_assert(ts, settings.port == 9000, site())
39+
test::suite_assert(ts, settings.verbose, site())
40+
test::suite_assert(ts, defaults.host == "", site())
41+
test::suite_assert(ts, defaults.port == 9000, site())
42+
end
43+
44+
func test_reordered_multiline_shorthand(ts):
45+
host := "reordered"
46+
port := 1234
47+
verbose := false
48+
settings := Settings{
49+
verbose,
50+
host,
51+
port
52+
}
53+
54+
test::suite_assert(ts, settings.host == "reordered", site())
55+
test::suite_assert(ts, settings.port == 1234, site())
56+
test::suite_assert(ts, not settings.verbose, site())
57+
end
58+
59+
func test_imported_struct_and_composite_field(ts):
60+
name := "Ada"
61+
tags := ["compiler"]
62+
nickname := Some{"analyst"}
63+
person := shapes::Person{name, tags, nickname}
64+
65+
text := "nested"
66+
label := Label{text}
67+
holder := Holder{label}
68+
69+
test::suite_assert(ts, person.name == "Ada", site())
70+
test::suite_assert(ts, person.tags[0] == "compiler", site())
71+
test::suite_assert(ts, person.nickname? == "analyst", site())
72+
test::suite_assert(ts, holder.label.text == "nested", site())
73+
end
74+
75+
func run_tests(ts):
76+
test_all_shorthand(ts)
77+
test_mixed_and_omitted_fields(ts)
78+
test_reordered_multiline_shorthand(ts)
79+
test_imported_struct_and_composite_field(ts)
80+
end

test/jik/tests.jik

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use "test_compile_fail_regions"
4444
use "test_region_safe_functions"
4545
use "test_region_ergonomics"
4646
use "test_compile_fail_modules"
47+
use "test_struct_field_init"
4748

4849

4950
func main():
@@ -93,6 +94,7 @@ func main():
9394
test_region_safe_functions::run_tests(ts)
9495
test_region_ergonomics::run_tests(ts)
9596
test_compile_fail_modules::run_tests(ts)
97+
test_struct_field_init::run_tests(ts)
9698

9799
test::suite_finish(ts)
98100

0 commit comments

Comments
 (0)