diff --git a/fec/src/checkexp.c b/fec/src/checkexp.c index 3554e31..1a32aa5 100644 --- a/fec/src/checkexp.c +++ b/fec/src/checkexp.c @@ -249,6 +249,17 @@ FeType *check_struct_init(FeCheckerState *s, FeNode *n) } else if (n->children) err(s->c,n->loc,"empty enum variant cannot have payload"); n->sem_type=et; return et; } + if (n->a && n->a->kind==FE_N_CALL) { + /* `Name(args){...}` -- the same spelling a type annotation uses, so + the same resolver answers it. */ + int ok=0; + t=type_from_expr(s,n->a,&ok); + if (!ok || !t || t->kind!=FE_TYPE_STRUCT) { + err(s->c,n->a->loc,"unknown struct type"); + return unknown(s->c); + } + return check_struct_fields(s,n,t); + } t=fe_type_intern(&s->c->types,n->text ? n->text : ""); if (!t || t->kind!=FE_TYPE_STRUCT) { err(s->c,n->loc,"unknown struct type"); return unknown(s->c); } return check_struct_fields(s,n,t); diff --git a/fec/src/parser.c b/fec/src/parser.c index 430b31e..888f5f6 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -121,7 +121,22 @@ static FeNode *postfix(FeParser *p) FeNode *n=primary(p); for(;;) { FeToken t=p->current; FeNode *m; - if(eat(p,FE_TOK_LPAREN)) { m=toknode(p,FE_N_CALL,t); m->a=n; while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(m,delimited_expr(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' after call"); n=m; } + if(eat(p,FE_TOK_LPAREN)) { m=toknode(p,FE_N_CALL,t); m->a=n; while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(m,delimited_expr(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' after call"); n=m; + /* `Name(args){...}` builds an instance of a generic struct. Without + this the arguments have nowhere to go and only `Self{...}` or a + constructor can name one. Same `{` ambiguity as `Name{...}` + above, and the same guard settles it. */ + if(is(p,FE_TOK_LBRACE) && !p->forbid_struct_literal) { + FeNode *s=toknode(p,FE_N_STRUCT_INIT,t); s->a=n; next(p); + while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) { FeNode *f; + if(!is_name(p)){error(p,"expected field name");recover(p);break;} + f=toknode(p,FE_N_FIELD,p->current);next(p); + want(p,FE_TOK_COLON,"expected ':' after field"); + f->a=expr(p,0);fe_node_add(s,f); + if(!eat(p,FE_TOK_COMMA))break; + } + want(p,FE_TOK_RBRACE,"expected '}' in struct literal"); n=s; + } } else if(eat(p,FE_TOK_LBRACKET)) { m=toknode(p,FE_N_INDEX,t);m->a=n; if(is(p,FE_TOK_DOTDOT)) { m->b=0; m->flags|=FE_NODE_SLICE; } else m->b=delimited_expr(p); diff --git a/fec/tests/exec/geninst/README.md b/fec/tests/exec/geninst/README.md new file mode 100644 index 0000000..7bfdd80 --- /dev/null +++ b/fec/tests/exec/geninst/README.md @@ -0,0 +1,8 @@ +# 제네릭 인스턴스 리터럴 + +`Name(args){...}` 와 `binding.Name(args){...}` 로 제네릭의 인스턴스를 짓는다. +전에는 `Self{...}` 나 생성자 함수로만 만들 수 있었다. + +`Handle(T)` 는 `T` 를 본문에서 쓰지 않는다 -- typed handle 의 자연스러운 모양이고, +`Handle(Node)` 와 `Handle(Kind)` 를 갈라놓는 것 말고는 하는 일이 없다. 그 둘이 +실제로 다른 타입이라는 것은 `generic/badphant.fe` 가 거부로 고정한다. diff --git a/fec/tests/exec/geninst/hold.fe b/fec/tests/exec/geninst/hold.fe new file mode 100644 index 0000000..44646f8 --- /dev/null +++ b/fec/tests/exec/geninst/hold.fe @@ -0,0 +1,14 @@ +unit hold; + +// Fields are `pub` so another unit can write the literal directly. A generic +// with a `pub` field is the only way to reach `binding.Name(args){...}`. +pub struct Cell(T) { + pub v: T, +} + +// `T` is never used in the body. That is the natural shape of a typed handle: +// the parameter is there to keep `Handle(Node)` and `Handle(Type)` apart, not +// to describe any storage. +pub struct Handle(T) { + pub raw: u32, +} diff --git a/fec/tests/exec/geninst/main.fe b/fec/tests/exec/geninst/main.fe new file mode 100644 index 0000000..b469b2a --- /dev/null +++ b/fec/tests/exec/geninst/main.fe @@ -0,0 +1,38 @@ +// EXIT:0 +// OUTPUT:cell 7 41 +// OUTPUT:handle 3 4 sum 7 +// OUTPUT:local 9 +unit main; + +import std.io; +import hold; + +struct Node { v: i32, } +struct Kind { v: i32, } + +// A generic declared here, instantiated with an explicit argument below. +struct Boxed(T) { + v: T, +} + +// Two instances of the same phantom generic are different nominal types, so +// this only accepts one of them. +fn only_node(h: hold.Handle(Node)) -> u32 { return h.raw; } +fn only_kind(h: hold.Handle(Kind)) -> u32 { return h.raw; } + +fn main() -> i32 { + // `binding.Name(args){...}` -- a generic instance from another unit. + let a: hold.Cell(i32) = hold.Cell(i32){ v: 7 }; + let b: hold.Cell(u8) = hold.Cell(u8){ v: 41 }; + @print("cell {} {}\n", a.v, b.v); + + let n: hold.Handle(Node) = hold.Handle(Node){ raw: 3 }; + let k: hold.Handle(Kind) = hold.Handle(Kind){ raw: 4 }; + @print("handle {} {} sum {}\n", only_node(n), only_kind(k), + only_node(n) + only_kind(k)); + + // `Name(args){...}` -- a generic declared in this unit. + let c: Boxed(i32) = Boxed(i32){ v: 9 }; + @print("local {}\n", c.v); + return 0; +} diff --git a/fec/tests/generic/badphant.fe b/fec/tests/generic/badphant.fe new file mode 100644 index 0000000..6735b73 --- /dev/null +++ b/fec/tests/generic/badphant.fe @@ -0,0 +1,15 @@ +// ERROR:14:argument type mismatch +unit badphant; + +// A type parameter that the body never mentions still tells two instances +// apart. `Handle(Node)` and `Handle(Kind)` are different nominal types. +struct Handle(T) { raw: u32, } + +struct Node { v: i32, } +struct Kind { v: i32, } + +fn only_node(h: Handle(Node)) -> u32 { return h.raw; } + +fn bad() -> u32 { + return only_node(Handle(Kind){ raw: 1 }); +} diff --git a/fec/tests/generic/okphant.fe b/fec/tests/generic/okphant.fe new file mode 100644 index 0000000..dad55a0 --- /dev/null +++ b/fec/tests/generic/okphant.fe @@ -0,0 +1,16 @@ +unit okphant; + +// The parameter is used nowhere in the body. That is legal, and it is the +// natural shape of a typed handle. +struct Handle(T) { raw: u32, } + +struct Node { v: i32, } +struct Kind { v: i32, } + +fn only_node(h: Handle(Node)) -> u32 { return h.raw; } +fn only_kind(h: Handle(Kind)) -> u32 { return h.raw; } + +fn ok() -> u32 { + return only_node(Handle(Node){ raw: 1 }) + + only_kind(Handle(Kind){ raw: 2 }); +}