Skip to content

Commit

Permalink
Merge pull request #1806 from ruby/untyped-function
Browse files Browse the repository at this point in the history
Untyped function
  • Loading branch information
soutaro authored Apr 22, 2024
2 parents feb8722 + 3fd7d02 commit e2970ca
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 53 deletions.
6 changes: 5 additions & 1 deletion docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ _literal_ ::= _string-literal_
| `false`

_proc_ ::= `^` _parameters?_ _self-type-binding?_ _block?_ `->` _type_
| `^` `(` `?` `)` _self-type-binding?_ _block?_ `->` _type_ # Proc type with untyped parameter
```

### Class instance type
Expand Down Expand Up @@ -310,6 +311,7 @@ end

```markdown
_method-type_ ::= _parameters?_ _block?_ `->` _type_ # Method type
| `(` `?` `)` `->` _type_ # Method type with untyped parameters

_parameters?_ ::= (Empty)
| _parameters_ (Parameters)
Expand All @@ -333,9 +335,11 @@ _var-name_ ::= /[a-z]\w*/
_self-type-binding?_ = (Empty)
| `[` `self` `:` _type_ `]` (Self type binding)

_block?_ = (No block)
_block?_ = (No block)
| `{` _parameters_ _self-type-binding?_ `->` _type_ `}` (Block)
| `{` `(` `?` `)` `->` _type_ `}` (Block with untyped parameters)
| `?` `{` _parameters_ _self-type-binding?_ `->` _type_ `}` (Optional block)
| `?` `{` `(` `?` `)` `->` _type_ `}` (Optional block with untyped parameters)
```

### Parameters
Expand Down
2 changes: 2 additions & 0 deletions ext/rbs_extension/constants.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ VALUE RBS_Types_ClassInstance;
VALUE RBS_Types_ClassSingleton;
VALUE RBS_Types_Function_Param;
VALUE RBS_Types_Function;
VALUE RBS_Types_UntypedFunction;
VALUE RBS_Types_Interface;
VALUE RBS_Types_Intersection;
VALUE RBS_Types_Literal;
Expand Down Expand Up @@ -138,6 +139,7 @@ void rbs__init_constants(void) {
IMPORT_CONSTANT(RBS_Types_ClassInstance, RBS_Types, "ClassInstance");
IMPORT_CONSTANT(RBS_Types_ClassSingleton, RBS_Types, "ClassSingleton");
IMPORT_CONSTANT(RBS_Types_Function, RBS_Types, "Function");
IMPORT_CONSTANT(RBS_Types_UntypedFunction, RBS_Types, "UntypedFunction");
IMPORT_CONSTANT(RBS_Types_Function_Param, RBS_Types_Function, "Param");
IMPORT_CONSTANT(RBS_Types_Interface, RBS_Types, "Interface");
IMPORT_CONSTANT(RBS_Types_Intersection, RBS_Types, "Intersection");
Expand Down
1 change: 1 addition & 0 deletions ext/rbs_extension/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extern VALUE RBS_Types_ClassInstance;
extern VALUE RBS_Types_ClassSingleton;
extern VALUE RBS_Types_Function_Param;
extern VALUE RBS_Types_Function;
extern VALUE RBS_Types_UntypedFunction;
extern VALUE RBS_Types_Interface;
extern VALUE RBS_Types_Intersection;
extern VALUE RBS_Types_Literal;
Expand Down
56 changes: 40 additions & 16 deletions ext/rbs_extension/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ typedef struct {
VALUE rest_keywords;
} method_params;

static bool rbs_is_untyped_params(method_params *params) {
return NIL_P(params->required_positionals);
}

// /**
// * Returns RBS::Location object of `current_token` of a parser state.
// *
Expand Down Expand Up @@ -362,6 +366,7 @@ static bool is_keyword(parserstate *state) {

/*
params ::= {} `)`
| {} `?` `)` -- Untyped function params (assign params.required = nil)
| <required_params> `)`
| <required_params> `,` `)`
Expand Down Expand Up @@ -389,6 +394,11 @@ static bool is_keyword(parserstate *state) {
| {} `**` <function_param>
*/
static void parse_params(parserstate *state, method_params *params) {
if (state->next_token.type == pQUESTION && state->next_token2.type == pRPAREN) {
params->required_positionals = Qnil;
parser_advance(state);
return;
}
if (state->next_token.type == pRPAREN) {
return;
}
Expand Down Expand Up @@ -613,6 +623,13 @@ static void parse_function(parserstate *state, VALUE *function, VALUE *block, VA
parser_advance_assert(state, pRPAREN);
}

// Untyped method parameter means it cannot have block
if (rbs_is_untyped_params(&params)) {
if (state->next_token.type != pARROW) {
raise_syntax_error(state, state->next_token2, "A method type with untyped method parameter cannot have block");
}
}

// Passing NULL to function_self_type means the function itself doesn't accept self type binding. (== method type)
if (function_self_type) {
*function_self_type = parse_self_type_binding(state);
Expand Down Expand Up @@ -641,8 +658,11 @@ static void parse_function(parserstate *state, VALUE *function, VALUE *block, VA
parser_advance_assert(state, pARROW);
VALUE block_return_type = parse_optional(state);

*block = rbs_block(
rbs_function(
VALUE block_function = Qnil;
if (rbs_is_untyped_params(&block_params)) {
block_function = rbs_untyped_function(block_return_type);
} else {
block_function = rbs_function(
block_params.required_positionals,
block_params.optional_positionals,
block_params.rest_positionals,
Expand All @@ -651,27 +671,31 @@ static void parse_function(parserstate *state, VALUE *function, VALUE *block, VA
block_params.optional_keywords,
block_params.rest_keywords,
block_return_type
),
required,
block_self_type
);
);
}

*block = rbs_block(block_function, required, block_self_type);

parser_advance_assert(state, pRBRACE);
}

parser_advance_assert(state, pARROW);
VALUE type = parse_optional(state);

*function = rbs_function(
params.required_positionals,
params.optional_positionals,
params.rest_positionals,
params.trailing_positionals,
params.required_keywords,
params.optional_keywords,
params.rest_keywords,
type
);
if (rbs_is_untyped_params(&params)) {
*function = rbs_untyped_function(type);
} else {
*function = rbs_function(
params.required_positionals,
params.optional_positionals,
params.rest_positionals,
params.trailing_positionals,
params.required_keywords,
params.optional_keywords,
params.rest_keywords,
type
);
}
}

/*
Expand Down
11 changes: 11 additions & 0 deletions ext/rbs_extension/ruby_objs.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ VALUE rbs_function_param(VALUE type, VALUE name, VALUE location) {
);
}

VALUE rbs_untyped_function(VALUE return_type) {
VALUE args = rb_hash_new();
rb_hash_aset(args, ID2SYM(rb_intern("return_type")), return_type);

return CLASS_NEW_INSTANCE(
RBS_Types_UntypedFunction,
1,
&args
);
}

VALUE rbs_function(
VALUE required_positional_params,
VALUE optional_positional_params,
Expand Down
1 change: 1 addition & 0 deletions ext/rbs_extension/ruby_objs.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ VALUE rbs_class_instance(VALUE typename, VALUE type_args, VALUE location);
VALUE rbs_class_singleton(VALUE typename, VALUE location);
VALUE rbs_function_param(VALUE type, VALUE name, VALUE location);
VALUE rbs_function(VALUE required_positional_params, VALUE optional_positional_params, VALUE rest_positional_params, VALUE trailing_positional_params, VALUE required_keywords, VALUE optional_keywords, VALUE rest_keywords, VALUE return_type);
VALUE rbs_untyped_function(VALUE return_type);
VALUE rbs_interface(VALUE typename, VALUE type_args, VALUE location);
VALUE rbs_intersection(VALUE types, VALUE location);
VALUE rbs_literal(VALUE literal, VALUE location);
Expand Down
21 changes: 10 additions & 11 deletions lib/rbs/prototype/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,9 @@ def block_from_body(node)
end
end

method_block = Types::Block.new(
required: required,
type: Types::Function.empty(untyped),
self_type: nil
)

if yields
function = Types::Function.empty(untyped)

yields.each do |yield_node|
array_content = yield_node.children[0]&.children&.compact || []

Expand All @@ -54,9 +50,9 @@ def block_from_body(node)
[array_content, nil]
end

if (diff = positionals.size - method_block.type.required_positionals.size) > 0
if (diff = positionals.size - function.required_positionals.size) > 0
diff.times do
method_block.type.required_positionals << Types::Function::Param.new(
function.required_positionals << Types::Function::Param.new(
type: untyped,
name: nil
)
Expand All @@ -67,7 +63,7 @@ def block_from_body(node)
keywords.children[0].children.each_slice(2) do |key_node, value_node|
if key_node
key = key_node.children[0]
method_block.type.required_keywords[key] ||=
function.required_keywords[key] ||=
Types::Function::Param.new(
type: untyped,
name: nil
Expand All @@ -76,10 +72,13 @@ def block_from_body(node)
end
end
end
else
function = Types::UntypedFunction.new(return_type: untyped)
end
end

method_block

Types::Block.new(required: required, type: function, self_type: nil)
end
end

def each_child(node, &block)
Expand Down
34 changes: 21 additions & 13 deletions lib/rbs/prototype/rbi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,11 @@ def method_type(args_node, type_node, variables:, overloads:)
Types::Function::Param.new(name: name, type: type)
end

method_type.update(type: method_type.type.update(required_positionals: required_positionals))
if method_type.type.is_a?(RBS::Types::Function)
method_type.update(type: method_type.type.update(required_positionals: required_positionals))
else
method_type
end
end
when :type_parameters
type_params = []
Expand Down Expand Up @@ -446,18 +450,22 @@ def parse_params(args_node, args, method_type, variables:, overloads:)
end
end

method_type.update(
type: method_type.type.update(
required_positionals: required_positionals,
optional_positionals: optional_positionals,
rest_positionals: rest_positionals,
trailing_positionals: trailing_positionals,
required_keywords: required_keywords,
optional_keywords: optional_keywords,
rest_keywords: rest_keywords
),
block: method_block
)
if method_type.type.is_a?(Types::Function)
method_type.update(
type: method_type.type.update(
required_positionals: required_positionals,
optional_positionals: optional_positionals,
rest_positionals: rest_positionals,
trailing_positionals: trailing_positionals,
required_keywords: required_keywords,
optional_keywords: optional_keywords,
rest_keywords: rest_keywords
),
block: method_block
)
else
method_type
end
end

def type_of(type_node, variables:)
Expand Down
78 changes: 78 additions & 0 deletions lib/rbs/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,84 @@ def with_nonreturn_void?
end
end

class UntypedFunction
attr_reader :return_type

def initialize(return_type:)
@return_type = return_type
end

def free_variables(acc = Set.new)
return_type.free_variables(acc)
end

def map_type(&block)
if block
update(return_type: yield(return_type))
else
enum_for :map_type
end
end

def each_type(&block)
if block
yield return_type
else
enum_for :each_type
end
end

def each_param(&block)
if block
# noop
else
enum_for :each_param
end
end

def to_json(state = _ = nil)
{
return_type: return_type
}.to_json(state)
end

def sub(subst)
map_type { _1.sub(subst) }
end

def with_return_type(ty)
update(return_type: ty)
end

def update(return_type: self.return_type)
UntypedFunction.new(return_type: return_type)
end

def empty?
true
end

def has_self_type?
return_type.has_self_type?
end

def has_classish_type?
return_type.has_classish_type?
end

def with_nonreturn_void
false
end

def param_to_s
"?"
end

def return_to_s
return_type.to_s(1)
end
end

class Block
attr_reader :type
attr_reader :required
Expand Down
6 changes: 3 additions & 3 deletions sig/method_types.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ module RBS
type loc = def_loc | attr_loc

attr_reader type_params: Array[AST::TypeParam]
attr_reader type: Types::Function
attr_reader type: Types::function
attr_reader block: Types::Block?
attr_reader location: loc?

def initialize: (type_params: Array[AST::TypeParam], type: Types::Function, block: Types::Block?, location: loc?) -> void
def initialize: (type_params: Array[AST::TypeParam], type: Types::function, block: Types::Block?, location: loc?) -> void

def ==: (untyped other) -> bool

Expand All @@ -29,7 +29,7 @@ module RBS
#
def sub: (Substitution) -> MethodType

def update: (?type_params: Array[AST::TypeParam], ?type: Types::Function, ?block: Types::Block?, ?location: loc?) -> MethodType
def update: (?type_params: Array[AST::TypeParam], ?type: Types::function, ?block: Types::Block?, ?location: loc?) -> MethodType

def free_variables: (?Set[Symbol] set) -> Set[Symbol]

Expand Down
Loading

0 comments on commit e2970ca

Please sign in to comment.