Skip to content

Commit d89e4ad

Browse files
authored
Merge pull request #3042 from Shopify/support-forwarding-parameters
Support forwarding parameters in method types
2 parents 4752d54 + f9f223c commit d89e4ad

15 files changed

Lines changed: 273 additions & 41 deletions

File tree

config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,9 @@ nodes:
598598
- name: rest_keywords
599599
c_type: rbs_node
600600
optional: true # NULL when no double-splat (e.g., `() -> void` vs `(**String) -> void`)
601+
- name: forwarding
602+
c_type: rbs_node
603+
optional: true # NULL when no forwarding parameter (e.g., `() -> void` vs `(...) -> void`)
601604
- name: return_type
602605
c_type: rbs_node
603606
- name: RBS::Types::Function::Param
@@ -610,6 +613,8 @@ nodes:
610613
optional: true # NULL when param is unnamed (e.g., `(String) -> void` vs `(String name) -> void`)
611614
locations:
612615
- optional: name
616+
- name: RBS::Types::Function::ForwardingParam
617+
rust_name: FunctionForwardingParamNode
613618
- name: RBS::Types::Interface
614619
rust_name: InterfaceTypeNode
615620
fields:

ext/rbs_extension/ast_translation.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,7 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
16361636
VALUE arg_required_keywords = rbs_hash_to_ruby_hash(ctx, node->required_keywords);
16371637
VALUE arg_optional_keywords = rbs_hash_to_ruby_hash(ctx, node->optional_keywords);
16381638
VALUE arg_rest_keywords = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->rest_keywords); // rbs_node
1639+
VALUE arg_forwarding = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->forwarding); // rbs_node
16391640
VALUE arg_return_type = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->return_type); // rbs_node
16401641

16411642
// Claim the shared kwargs hash, clear it, fill it, and hand it to `.new`.
@@ -1649,9 +1650,23 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
16491650
rb_hash_aset(h, ID2SYM(rb_intern("required_keywords")), arg_required_keywords);
16501651
rb_hash_aset(h, ID2SYM(rb_intern("optional_keywords")), arg_optional_keywords);
16511652
rb_hash_aset(h, ID2SYM(rb_intern("rest_keywords")), arg_rest_keywords);
1653+
rb_hash_aset(h, ID2SYM(rb_intern("forwarding")), arg_forwarding);
16521654
rb_hash_aset(h, ID2SYM(rb_intern("return_type")), arg_return_type);
16531655
return CLASS_NEW_INSTANCE(RBS_Types_Function, 1, &h);
16541656
}
1657+
case RBS_TYPES_FUNCTION_FORWARDING_PARAM: {
1658+
rbs_types_function_forwarding_param_t *node = (rbs_types_function_forwarding_param_t *) instance;
1659+
1660+
// Compute child VALUEs into locals variables first, before any recursion into `rbs_struct_to_ruby_value()`.
1661+
VALUE arg_location = rbs_location_range_to_ruby_location(ctx, node->base.location);
1662+
1663+
// Claim the shared kwargs hash, clear it, fill it, and hand it to `.new`.
1664+
// Must not recurse between `rb_hash_clear()` and `CLASS_NEW_INSTANCE()`.
1665+
VALUE h = ctx.reusable_kwargs_hash;
1666+
rb_hash_clear(h);
1667+
rb_hash_aset(h, ID2SYM(rb_intern("location")), arg_location);
1668+
return CLASS_NEW_INSTANCE(RBS_Types_Function_ForwardingParam, 1, &h);
1669+
}
16551670
case RBS_TYPES_FUNCTION_PARAM: {
16561671
rbs_types_function_param_t *node = (rbs_types_function_param_t *) instance;
16571672

ext/rbs_extension/class_constants.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ VALUE RBS_Types_Block;
7979
VALUE RBS_Types_ClassInstance;
8080
VALUE RBS_Types_ClassSingleton;
8181
VALUE RBS_Types_Function;
82+
VALUE RBS_Types_Function_ForwardingParam;
8283
VALUE RBS_Types_Function_Param;
8384
VALUE RBS_Types_Interface;
8485
VALUE RBS_Types_Intersection;
@@ -173,6 +174,7 @@ void rbs__init_constants(void) {
173174
IMPORT_CONSTANT(RBS_Types_ClassInstance, RBS_Types, "ClassInstance");
174175
IMPORT_CONSTANT(RBS_Types_ClassSingleton, RBS_Types, "ClassSingleton");
175176
IMPORT_CONSTANT(RBS_Types_Function, RBS_Types, "Function");
177+
IMPORT_CONSTANT(RBS_Types_Function_ForwardingParam, RBS_Types_Function, "ForwardingParam");
176178
IMPORT_CONSTANT(RBS_Types_Function_Param, RBS_Types_Function, "Param");
177179
IMPORT_CONSTANT(RBS_Types_Interface, RBS_Types, "Interface");
178180
IMPORT_CONSTANT(RBS_Types_Intersection, RBS_Types, "Intersection");

ext/rbs_extension/class_constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ extern VALUE RBS_Types_Block;
8787
extern VALUE RBS_Types_ClassInstance;
8888
extern VALUE RBS_Types_ClassSingleton;
8989
extern VALUE RBS_Types_Function;
90+
extern VALUE RBS_Types_Function_ForwardingParam;
9091
extern VALUE RBS_Types_Function_Param;
9192
extern VALUE RBS_Types_Interface;
9293
extern VALUE RBS_Types_Intersection;

include/rbs/ast.h

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,19 @@ enum rbs_node_type {
125125
RBS_TYPES_CLASS_INSTANCE = 63,
126126
RBS_TYPES_CLASS_SINGLETON = 64,
127127
RBS_TYPES_FUNCTION = 65,
128-
RBS_TYPES_FUNCTION_PARAM = 66,
129-
RBS_TYPES_INTERFACE = 67,
130-
RBS_TYPES_INTERSECTION = 68,
131-
RBS_TYPES_LITERAL = 69,
132-
RBS_TYPES_OPTIONAL = 70,
133-
RBS_TYPES_PROC = 71,
134-
RBS_TYPES_RECORD = 72,
135-
RBS_TYPES_RECORD_FIELD_TYPE = 73,
136-
RBS_TYPES_TUPLE = 74,
137-
RBS_TYPES_UNION = 75,
138-
RBS_TYPES_UNTYPED_FUNCTION = 76,
139-
RBS_TYPES_VARIABLE = 77,
128+
RBS_TYPES_FUNCTION_FORWARDING_PARAM = 66,
129+
RBS_TYPES_FUNCTION_PARAM = 67,
130+
RBS_TYPES_INTERFACE = 68,
131+
RBS_TYPES_INTERSECTION = 69,
132+
RBS_TYPES_LITERAL = 70,
133+
RBS_TYPES_OPTIONAL = 71,
134+
RBS_TYPES_PROC = 72,
135+
RBS_TYPES_RECORD = 73,
136+
RBS_TYPES_RECORD_FIELD_TYPE = 74,
137+
RBS_TYPES_TUPLE = 75,
138+
RBS_TYPES_UNION = 76,
139+
RBS_TYPES_UNTYPED_FUNCTION = 77,
140+
RBS_TYPES_VARIABLE = 78,
140141
RBS_AST_SYMBOL,
141142
};
142143

@@ -854,9 +855,15 @@ typedef struct rbs_types_function {
854855
struct rbs_hash *RBS_NONNULL required_keywords;
855856
struct rbs_hash *RBS_NONNULL optional_keywords;
856857
struct rbs_node *RBS_NULLABLE rest_keywords;
858+
struct rbs_node *RBS_NULLABLE forwarding;
857859
struct rbs_node *RBS_NONNULL return_type;
858860
} rbs_types_function_t;
859861

862+
typedef struct rbs_types_function_forwarding_param {
863+
rbs_node_t base;
864+
865+
} rbs_types_function_forwarding_param_t;
866+
860867
typedef struct rbs_types_function_param {
861868
rbs_node_t base;
862869

@@ -1030,7 +1037,8 @@ rbs_types_bases_void_t *RBS_NONNULL rbs_types_bases_void_new(rbs_allocator_t *RB
10301037
rbs_types_block_t *RBS_NONNULL rbs_types_block_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_t *RBS_NONNULL type, bool required, rbs_node_t *RBS_NULLABLE self_type);
10311038
rbs_types_class_instance_t *RBS_NONNULL rbs_types_class_instance_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range);
10321039
rbs_types_class_singleton_t *RBS_NONNULL rbs_types_class_singleton_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range);
1033-
rbs_types_function_t *RBS_NONNULL rbs_types_function_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_list_t *RBS_NONNULL required_positionals, rbs_node_list_t *RBS_NONNULL optional_positionals, rbs_node_t *RBS_NULLABLE rest_positionals, rbs_node_list_t *RBS_NONNULL trailing_positionals, rbs_hash_t *RBS_NONNULL required_keywords, rbs_hash_t *RBS_NONNULL optional_keywords, rbs_node_t *RBS_NULLABLE rest_keywords, rbs_node_t *RBS_NONNULL return_type);
1040+
rbs_types_function_t *RBS_NONNULL rbs_types_function_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_list_t *RBS_NONNULL required_positionals, rbs_node_list_t *RBS_NONNULL optional_positionals, rbs_node_t *RBS_NULLABLE rest_positionals, rbs_node_list_t *RBS_NONNULL trailing_positionals, rbs_hash_t *RBS_NONNULL required_keywords, rbs_hash_t *RBS_NONNULL optional_keywords, rbs_node_t *RBS_NULLABLE rest_keywords, rbs_node_t *RBS_NULLABLE forwarding, rbs_node_t *RBS_NONNULL return_type);
1041+
rbs_types_function_forwarding_param_t *RBS_NONNULL rbs_types_function_forwarding_param_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location);
10341042
rbs_types_function_param_t *RBS_NONNULL rbs_types_function_param_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_t *RBS_NONNULL type, rbs_ast_symbol_t *RBS_NULLABLE name);
10351043
rbs_types_interface_t *RBS_NONNULL rbs_types_interface_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range);
10361044
rbs_types_intersection_t *RBS_NONNULL rbs_types_intersection_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_list_t *RBS_NONNULL types);

lib/rbs/types.rb

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -959,16 +959,44 @@ def to_s
959959
end
960960
end
961961

962+
class ForwardingParam
963+
attr_reader :location
964+
965+
def initialize(location:)
966+
@location = location
967+
end
968+
969+
def ==(other)
970+
other.is_a?(ForwardingParam)
971+
end
972+
973+
alias eql? ==
974+
975+
def hash
976+
self.class.hash
977+
end
978+
979+
def to_json(state = nil)
980+
json = {} #: Hash[Symbol, untyped]
981+
json.to_json(state)
982+
end
983+
984+
def to_s
985+
"..."
986+
end
987+
end
988+
962989
attr_reader :required_positionals
963990
attr_reader :optional_positionals
964991
attr_reader :rest_positionals
965992
attr_reader :trailing_positionals
966993
attr_reader :required_keywords
967994
attr_reader :optional_keywords
968995
attr_reader :rest_keywords
996+
attr_reader :forwarding
969997
attr_reader :return_type
970998

971-
def initialize(required_positionals:, optional_positionals:, rest_positionals:, trailing_positionals:, required_keywords:, optional_keywords:, rest_keywords:, return_type:)
999+
def initialize(required_positionals:, optional_positionals:, rest_positionals:, trailing_positionals:, required_keywords:, optional_keywords:, rest_keywords:, return_type:, forwarding: nil)
9721000
@return_type = return_type
9731001
@required_positionals = required_positionals
9741002
@optional_positionals = optional_positionals
@@ -977,6 +1005,7 @@ def initialize(required_positionals:, optional_positionals:, rest_positionals:,
9771005
@required_keywords = required_keywords
9781006
@optional_keywords = optional_keywords
9791007
@rest_keywords = rest_keywords
1008+
@forwarding = forwarding
9801009
end
9811010

9821011
def ==(other)
@@ -988,6 +1017,7 @@ def ==(other)
9881017
other.required_keywords == required_keywords &&
9891018
other.optional_keywords == optional_keywords &&
9901019
other.rest_keywords == rest_keywords &&
1020+
other.forwarding == forwarding &&
9911021
other.return_type == return_type
9921022
end
9931023

@@ -1002,6 +1032,7 @@ def hash
10021032
required_keywords.hash ^
10031033
optional_keywords.hash ^
10041034
rest_keywords.hash ^
1035+
forwarding.hash ^
10051036
return_type.hash
10061037
end
10071038

@@ -1043,6 +1074,7 @@ def map_type(&block)
10431074
required_keywords: hmapv(required_keywords) {|param| param.map_type(&block) },
10441075
optional_keywords: hmapv(optional_keywords) {|param| param.map_type(&block) },
10451076
rest_keywords: rest_keywords&.yield_self {|param| param.map_type(&block) },
1077+
forwarding: forwarding,
10461078
return_type: yield(return_type)
10471079
)
10481080
else
@@ -1110,6 +1142,7 @@ def to_json(state = nil)
11101142
required_keywords: required_keywords,
11111143
optional_keywords: optional_keywords,
11121144
rest_keywords: rest_keywords,
1145+
forwarding: forwarding,
11131146
return_type: return_type
11141147
}.to_json(state)
11151148
end
@@ -1129,6 +1162,7 @@ def self.empty(return_type)
11291162
required_keywords: {},
11301163
optional_keywords: {},
11311164
rest_keywords: nil,
1165+
forwarding: nil,
11321166
return_type: return_type
11331167
)
11341168
end
@@ -1142,12 +1176,13 @@ def with_return_type(type)
11421176
required_keywords: required_keywords,
11431177
optional_keywords: optional_keywords,
11441178
rest_keywords: rest_keywords,
1179+
forwarding: forwarding,
11451180
return_type: type
11461181
)
11471182
end
11481183

11491184
def update(required_positionals: self.required_positionals, optional_positionals: self.optional_positionals, rest_positionals: self.rest_positionals, trailing_positionals: self.trailing_positionals,
1150-
required_keywords: self.required_keywords, optional_keywords: self.optional_keywords, rest_keywords: self.rest_keywords, return_type: self.return_type)
1185+
required_keywords: self.required_keywords, optional_keywords: self.optional_keywords, rest_keywords: self.rest_keywords, forwarding: self.forwarding, return_type: self.return_type)
11511186
Function.new(
11521187
required_positionals: required_positionals,
11531188
optional_positionals: optional_positionals,
@@ -1156,6 +1191,7 @@ def update(required_positionals: self.required_positionals, optional_positionals
11561191
required_keywords: required_keywords,
11571192
optional_keywords: optional_keywords,
11581193
rest_keywords: rest_keywords,
1194+
forwarding: forwarding,
11591195
return_type: return_type
11601196
)
11611197
end
@@ -1167,6 +1203,7 @@ def empty?
11671203
trailing_positionals.empty? &&
11681204
required_keywords.empty? &&
11691205
optional_keywords.empty? &&
1206+
!forwarding? &&
11701207
!rest_keywords
11711208
end
11721209

@@ -1175,6 +1212,7 @@ def param_to_s
11751212
params = []
11761213

11771214
params.push(*required_positionals.map(&:to_s))
1215+
params.push(forwarding.to_s) if forwarding
11781216
params.push(*optional_positionals.map {|p| "?#{p}"})
11791217
params.push("*#{rest_positionals}") if rest_positionals
11801218
params.push(*trailing_positionals.map(&:to_s))
@@ -1185,6 +1223,10 @@ def param_to_s
11851223
params.join(", ")
11861224
end
11871225

1226+
def forwarding?
1227+
!forwarding.nil?
1228+
end
1229+
11881230
def return_to_s
11891231
return_type.to_s(1)
11901232
end

lib/rbs/wasm/serialization_schema.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module WASM
2323
# :bool, :location_range, :location_range_list, :attr_ivar_name, or
2424
# [:enum, [value_or_nil, ...]].
2525
module SerializationSchema
26-
SYMBOL_TAG = 78
26+
SYMBOL_TAG = 79
2727

2828
SCHEMA = [
2929
nil, # tag 0 is reserved for NULL
@@ -91,7 +91,8 @@ module SerializationSchema
9191
[:node, "RBS::Types::Block", true, nil, [[:type, :node], [:required, :bool], [:self_type, :node]], false],
9292
[:node, "RBS::Types::ClassInstance", true, [[:name, true], [:args, false]], [[:name, :node], [:args, :node_list]], false],
9393
[:node, "RBS::Types::ClassSingleton", true, [[:name, true], [:args, false]], [[:name, :node], [:args, :node_list]], false],
94-
[:node, "RBS::Types::Function", false, nil, [[:required_positionals, :node_list], [:optional_positionals, :node_list], [:rest_positionals, :node], [:trailing_positionals, :node_list], [:required_keywords, :hash], [:optional_keywords, :hash], [:rest_keywords, :node], [:return_type, :node]], false],
94+
[:node, "RBS::Types::Function", false, nil, [[:required_positionals, :node_list], [:optional_positionals, :node_list], [:rest_positionals, :node], [:trailing_positionals, :node_list], [:required_keywords, :hash], [:optional_keywords, :hash], [:rest_keywords, :node], [:forwarding, :node], [:return_type, :node]], false],
95+
[:node, "RBS::Types::Function::ForwardingParam", true, nil, nil, false],
9596
[:node, "RBS::Types::Function::Param", true, [[:name, false]], [[:type, :node], [:name, :node]], false],
9697
[:node, "RBS::Types::Interface", true, [[:name, true], [:args, false]], [[:name, :node], [:args, :node_list]], false],
9798
[:node, "RBS::Types::Intersection", true, nil, [[:types, :node_list]], false],

schema/function.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,21 @@
7878
}
7979
]
8080
},
81+
"forwarding": {
82+
"title": "Forwarding parameter",
83+
"oneOf": [
84+
{
85+
"type": "object"
86+
},
87+
{
88+
"type": "null"
89+
}
90+
]
91+
},
8192
"return_type": {
8293
"title": "Return type of a function",
8394
"$ref": "types.json"
8495
}
8596
},
86-
"required": ["required_positionals", "optional_positionals", "rest_positionals", "trailing_positionals", "required_keywords", "optional_keywords", "rest_keywords", "return_type"]
97+
"required": ["required_positionals", "optional_positionals", "rest_positionals", "trailing_positionals", "required_keywords", "optional_keywords", "rest_keywords", "forwarding", "return_type"]
8798
}

sig/types.rbs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,20 @@ module RBS
385385
| -> Enumerator[t, Param]
386386
end
387387

388+
class ForwardingParam
389+
attr_reader location: Location?
390+
391+
def initialize: (location: Location) -> void
392+
end
393+
388394
attr_reader required_positionals: Array[Param]
389395
attr_reader optional_positionals: Array[Param]
390396
attr_reader rest_positionals: Param?
391397
attr_reader trailing_positionals: Array[Param]
392398
attr_reader required_keywords: Hash[Symbol, Param]
393399
attr_reader optional_keywords: Hash[Symbol, Param]
394400
attr_reader rest_keywords: Param?
401+
attr_reader forwarding: ForwardingParam?
395402
attr_reader return_type: t
396403

397404
def initialize: (required_positionals: Array[Param],
@@ -401,7 +408,8 @@ module RBS
401408
required_keywords: Hash[Symbol, Param],
402409
optional_keywords: Hash[Symbol, Param],
403410
rest_keywords: Param?,
404-
return_type: t) -> void
411+
return_type: t,
412+
?forwarding: ForwardingParam?) -> void
405413

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

@@ -431,9 +439,11 @@ module RBS
431439
?required_keywords: Hash[Symbol, Param],
432440
?optional_keywords: Hash[Symbol, Param],
433441
?rest_keywords: Param?,
442+
?forwarding: ForwardingParam?,
434443
?return_type: t) -> Function
435444

436445
def empty?: () -> bool
446+
def forwarding?: () -> bool
437447

438448
def param_to_s: () -> String
439449
def return_to_s: () -> String

0 commit comments

Comments
 (0)