Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic type alias #823

Merged
merged 13 commits into from
Dec 1, 2021
43 changes: 23 additions & 20 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
```markdown
_type_ ::= _class-name_ _type-arguments_ (Class instance type)
| _interface-name_ _type-arguments_ (Interface type)
| _alias-name_ _type-arguments_ (Alias type)
| `singleton(` _class-name_ `)` (Class singleton type)
| _alias-name_ (Alias type)
| _literal_ (Literal type)
| _type_ `|` _type_ (Union type)
| _type_ `&` _type_ (Intersection type)
| _type_ `?` (Optional type)
| `{` _record-name_ `:` _type_ `,` etc. `}` (Record type)
| `[]` | `[` _type_ `,` etc. `]` (Tuples)
| `{` _record-name_ `:` _type_ `,` etc. `}` (Record type)
| `[]` | `[` _type_ `,` etc. `]` (Tuples)
| _type-variable_ (Type variables)
| `^(` _parameters_ `) ->` _type_ (Proc type)
| `self`
Expand All @@ -35,8 +35,8 @@ _namespace_ ::= (Empty namespace)
| `::` (Root)
| _namespace_ /[A-Z]\w*/ `::` (Namespace)

_type-arguments_ ::= (No application)
| `[` _type_ `,` etc. `]` (Type application)
_type-arguments_ ::= (No type arguments)
| `[` _type_ `,` etc. `]` (Type arguments)

_literal_ ::= _string-literal_
| _symbol-literal_
Expand Down Expand Up @@ -64,25 +64,25 @@ _ToS # _ToS interface
::MyApp::_Each[String] # Interface name with namespace and type application
```

### Class singleton type

Class singleton type denotes _the type of a singleton object of a class_.

```
singleton(String)
singleton(::Hash) # Class singleton type cannot be parametrized.
```

### Alias type

Alias type denotes an alias declared with _alias declaration_.

The name of type aliases starts with lowercase `[a-z]`.


```
name
::JSON::t # Alias name with namespace
list[Integer] # Type alias can be generic
```

### Class singleton type

Class singleton type denotes _the type of a singleton object of a class_.

```
singleton(String)
singleton(::Hash) # Class singleton type cannot be parametrized.
```

### Literal type
Expand Down Expand Up @@ -155,7 +155,7 @@ Elem
```

Type variables cannot be distinguished from _class instance types_.
They are scoped in _class/module/interface declaration_ or _generic method types_.
They are scoped in _class/module/interface/alias declaration_ or _generic method types_.

```
class Ref[T] # Object is scoped in the class declaration.
Expand Down Expand Up @@ -414,7 +414,6 @@ These work only as _statements_, not per-method specifier.
_decl_ ::= _class-decl_ # Class declaration
| _module-decl_ # Module declaration
| _interface-decl_ # Interface declaration
| _extension-decl_ # Extension declaration
| _type-alias-decl_ # Type alias declaration
| _const-decl_ # Constant declaration
| _global-decl_ # Global declaration
Expand All @@ -434,9 +433,7 @@ _interface-members_ ::= _method-member_ # Method
| _include-member_ # Mixin (include)
| _alias-member_ # Alias

_extension-decl_ ::= `extension` _class-name_ _type-parameters_ `(` _extension-name_ `)` _members_ `end`

_type-alias-decl_ ::= `type` _alias-name_ `=` _type_
_type-alias-decl_ ::= `type` _alias-name_ _module-type-parameters_ `=` _type_

_const-decl_ ::= _const-name_ `:` _type_

Expand Down Expand Up @@ -536,6 +533,12 @@ type subject = Attendee | Speaker
type JSON::t = Integer | TrueClass | FalseClass | String | Hash[Symbol, t] | Array[t]
```

Type alias can be generic like class, module, and interface.

```
type list[out T] = [T, list[T]] | nil
```

### Constant type declaration

You can declare a constant.
Expand Down
190 changes: 96 additions & 94 deletions ext/rbs_extension/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,8 @@ static VALUE parse_simple(parserstate *state) {
}
case tULIDENT:
// fallthrough
case tLIDENT:
// fallthrough
case pCOLON2: {
range name_range;
range args_range;
Expand Down Expand Up @@ -857,19 +859,11 @@ static VALUE parse_simple(parserstate *state) {
} else if (kind == INTERFACE_NAME) {
return rbs_interface(typename, types, location);
} else if (kind == ALIAS_NAME) {
return rbs_alias(typename, location);
return rbs_alias(typename, types, location);
} else {
return Qnil;
}
}
case tLIDENT: {
VALUE location = rbs_location_current_token(state);
rbs_loc *loc = rbs_check_location(location);
rbs_loc_add_required_child(loc, rb_intern("name"), state->current_token.range);
rbs_loc_add_optional_child(loc, rb_intern("args"), NULL_RANGE);
VALUE typename = parse_type_name(state, ALIAS_NAME, NULL);
return rbs_alias(typename, location);
}
case kSINGLETON: {
range name_range;
range type_range;
Expand Down Expand Up @@ -1093,12 +1087,98 @@ VALUE parse_const_decl(parserstate *state) {
return rbs_ast_decl_constant(typename, type, location, comment);
}

/*
module_type_params ::= {} `[` module_type_param `,` ... <`]`>
| {<>}

module_type_param ::= kUNCHECKED? (kIN|kOUT|) tUIDENT
*/
VALUE parse_module_type_params(parserstate *state, range *rg) {
VALUE params = rbs_ast_decl_module_type_params();

if (state->next_token.type == pLBRACKET) {
parser_advance(state);

rg->start = state->current_token.range.start;

while (true) {
VALUE name;
VALUE unchecked = Qfalse;
VALUE variance = ID2SYM(rb_intern("invariant"));

range param_range = NULL_RANGE;
range name_range;
range variance_range = NULL_RANGE;
range unchecked_range = NULL_RANGE;

param_range.start = state->next_token.range.start;

if (state->next_token.type == kUNCHECKED) {
unchecked = Qtrue;
parser_advance(state);
unchecked_range = state->current_token.range;
}

if (state->next_token.type == kIN || state->next_token.type == kOUT) {
switch (state->next_token.type) {
case kIN:
variance = ID2SYM(rb_intern("contravariant"));
break;
case kOUT:
variance = ID2SYM(rb_intern("covariant"));
break;
default:
rbs_abort();
}

parser_advance(state);
variance_range = state->current_token.range;
}

parser_advance_assert(state, tUIDENT);
name_range = state->current_token.range;
param_range.end = state->current_token.range.end;

ID id = INTERN_TOKEN(state, state->current_token);
name = ID2SYM(id);

parser_insert_typevar(state, id);

VALUE location = rbs_new_location(state->buffer, param_range);
rbs_loc *loc = rbs_check_location(location);
rbs_loc_add_required_child(loc, rb_intern("name"), name_range);
rbs_loc_add_optional_child(loc, rb_intern("variance"), variance_range);
rbs_loc_add_optional_child(loc, rb_intern("unchecked"), unchecked_range);

VALUE param = rbs_ast_decl_module_type_params_param(name, variance, unchecked, location);
rb_funcall(params, rb_intern("add"), 1, param);

if (state->next_token.type == pCOMMA) {
parser_advance(state);
}

if (state->next_token.type == pRBRACKET) {
break;
}
}

parser_advance_assert(state, pRBRACKET);
rg->end = state->current_token.range.end;
} else {
*rg = NULL_RANGE;
}

return params;
}

/*
type_decl ::= {kTYPE} alias_name `=` <type>
*/
VALUE parse_type_decl(parserstate *state, position comment_pos, VALUE annotations) {
range decl_range;
range keyword_range, name_range, eq_range;
range keyword_range, name_range, params_range, eq_range;

parser_push_typevar_table(state, true);

decl_range.start = state->current_token.range.start;
comment_pos = nonnull_pos_or(comment_pos, decl_range.start);
Expand All @@ -1108,6 +1188,8 @@ VALUE parse_type_decl(parserstate *state, position comment_pos, VALUE annotation
parser_advance(state);
VALUE typename = parse_type_name(state, ALIAS_NAME, &name_range);

VALUE type_params = parse_module_type_params(state, &params_range);

parser_advance_assert(state, pEQ);
eq_range = state->current_token.range;

Expand All @@ -1118,10 +1200,14 @@ VALUE parse_type_decl(parserstate *state, position comment_pos, VALUE annotation
rbs_loc *loc = rbs_check_location(location);
rbs_loc_add_required_child(loc, rb_intern("keyword"), keyword_range);
rbs_loc_add_required_child(loc, rb_intern("name"), name_range);
rbs_loc_add_optional_child(loc, rb_intern("type_params"), params_range);
rbs_loc_add_required_child(loc, rb_intern("eq"), eq_range);

parser_pop_typevar_table(state);

return rbs_ast_decl_alias(
typename,
type_params,
type,
annotations,
location,
Expand Down Expand Up @@ -1184,90 +1270,6 @@ VALUE parse_annotation(parserstate *state) {
return rbs_ast_annotation(string, location);
}

/*
module_type_params ::= {} `[` module_type_param `,` ... <`]`>
| {<>}

module_type_param ::= kUNCHECKED? (kIN|kOUT|) tUIDENT
*/
VALUE parse_module_type_params(parserstate *state, range *rg) {
VALUE params = rbs_ast_decl_module_type_params();

if (state->next_token.type == pLBRACKET) {
parser_advance(state);

rg->start = state->current_token.range.start;

while (true) {
VALUE name;
VALUE unchecked = Qfalse;
VALUE variance = ID2SYM(rb_intern("invariant"));

range param_range = NULL_RANGE;
range name_range;
range variance_range = NULL_RANGE;
range unchecked_range = NULL_RANGE;

param_range.start = state->next_token.range.start;

if (state->next_token.type == kUNCHECKED) {
unchecked = Qtrue;
parser_advance(state);
unchecked_range = state->current_token.range;
}

if (state->next_token.type == kIN || state->next_token.type == kOUT) {
switch (state->next_token.type) {
case kIN:
variance = ID2SYM(rb_intern("contravariant"));
break;
case kOUT:
variance = ID2SYM(rb_intern("covariant"));
break;
default:
rbs_abort();
}

parser_advance(state);
variance_range = state->current_token.range;
}

parser_advance_assert(state, tUIDENT);
name_range = state->current_token.range;
param_range.end = state->current_token.range.end;

ID id = INTERN_TOKEN(state, state->current_token);
name = ID2SYM(id);

parser_insert_typevar(state, id);

VALUE location = rbs_new_location(state->buffer, param_range);
rbs_loc *loc = rbs_check_location(location);
rbs_loc_add_required_child(loc, rb_intern("name"), name_range);
rbs_loc_add_optional_child(loc, rb_intern("variance"), variance_range);
rbs_loc_add_optional_child(loc, rb_intern("unchecked"), unchecked_range);

VALUE param = rbs_ast_decl_module_type_params_param(name, variance, unchecked, location);
rb_funcall(params, rb_intern("add"), 1, param);

if (state->next_token.type == pCOMMA) {
parser_advance(state);
}

if (state->next_token.type == pRBRACKET) {
break;
}
}

parser_advance_assert(state, pRBRACKET);
rg->end = state->current_token.range.end;
} else {
*rg = NULL_RANGE;
}

return params;
}

/*
annotations ::= {} annotation ... <annotation>
| {<>}
Expand Down
14 changes: 8 additions & 6 deletions ext/rbs_extension/ruby_objs.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,16 @@ VALUE rbs_class_singleton(VALUE typename, VALUE location) {
);
}

VALUE rbs_alias(VALUE typename, VALUE location) {
VALUE args = rb_hash_new();
rb_hash_aset(args, ID2SYM(rb_intern("name")), typename);
rb_hash_aset(args, ID2SYM(rb_intern("location")), location);
VALUE rbs_alias(VALUE typename, VALUE args, VALUE location) {
VALUE kwargs = rb_hash_new();
rb_hash_aset(kwargs, ID2SYM(rb_intern("name")), typename);
rb_hash_aset(kwargs, ID2SYM(rb_intern("args")), args);
rb_hash_aset(kwargs, ID2SYM(rb_intern("location")), location);

return CLASS_NEW_INSTANCE(
RBS_Types_Alias,
1,
&args
&kwargs
);
}

Expand Down Expand Up @@ -339,9 +340,10 @@ VALUE rbs_ast_decl_global(VALUE name, VALUE type, VALUE location, VALUE comment)
);
}

VALUE rbs_ast_decl_alias(VALUE name, VALUE type, VALUE annotations, VALUE location, VALUE comment) {
VALUE rbs_ast_decl_alias(VALUE name, VALUE type_params, VALUE type, VALUE annotations, VALUE location, VALUE comment) {
VALUE args = rb_hash_new();
rb_hash_aset(args, ID2SYM(rb_intern("name")), name);
rb_hash_aset(args, ID2SYM(rb_intern("type_params")), type_params);
rb_hash_aset(args, ID2SYM(rb_intern("type")), type);
rb_hash_aset(args, ID2SYM(rb_intern("annotations")), annotations);
rb_hash_aset(args, ID2SYM(rb_intern("location")), location);
Expand Down
4 changes: 2 additions & 2 deletions ext/rbs_extension/ruby_objs.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

#include "ruby.h"

VALUE rbs_alias(VALUE typename, VALUE location);
VALUE rbs_alias(VALUE typename, VALUE args, VALUE location);
VALUE rbs_ast_annotation(VALUE string, VALUE location);
VALUE rbs_ast_comment(VALUE string, VALUE location);
VALUE rbs_ast_decl_alias(VALUE name, VALUE type, VALUE annotations, VALUE location, VALUE comment);
VALUE rbs_ast_decl_alias(VALUE name, VALUE type_params, VALUE type, VALUE annotations, VALUE location, VALUE comment);
VALUE rbs_ast_decl_class_super(VALUE name, VALUE args, VALUE location);
VALUE rbs_ast_decl_class(VALUE name, VALUE type_params, VALUE super_class, VALUE members, VALUE annotations, VALUE location, VALUE comment);
VALUE rbs_ast_decl_constant(VALUE name, VALUE type, VALUE location, VALUE comment);
Expand Down
Loading