@@ -179,6 +179,19 @@ def match_arguments?(genv, changes, param_map, a_args, method_type)
179179 end
180180 end
181181
182+ # Check keyword arguments by inspecting the keywords vertex types
183+ # directly. We avoid get_keyword_arg here because it creates a fresh
184+ # Vertex each call, which would destabilize the change-set edges and
185+ # cause oscillation when match_arguments? runs on every box re-eval.
186+ if a_args . keywords
187+ method_type . req_keyword_keys . zip ( method_type . req_keyword_values ) do |key , ty |
188+ return false unless keyword_arg_typecheck? ( genv , changes , a_args . keywords , key , ty , param_map )
189+ end
190+ method_type . opt_keyword_keys . zip ( method_type . opt_keyword_values ) do |key , ty |
191+ return false unless keyword_arg_typecheck? ( genv , changes , a_args . keywords , key , ty , param_map )
192+ end
193+ end
194+
182195 return true
183196 end
184197
@@ -260,21 +273,25 @@ def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk)
260273 #
261274 # Top-level empty vertices are always uninformative. For type
262275 # parameter vertices (e.g., Array[T], Hash[K,V], tuples), we
263- # only recurse when overloads differ in their positional parameter
264- # types -- otherwise empty type params (like those of `{}`) cannot
265- # cause oscillation and should not trigger bail-out.
266- overloads_differ_in_positionals = !@method_types . each_cons ( 2 ) . all? { |mt1 , mt2 |
267- positionals_match? ( mt1 , mt2 )
276+ # only recurse when overloads differ in their positional or keyword
277+ # parameter types -- otherwise empty type params (like those of
278+ # `{}`) cannot cause oscillation and should not trigger bail-out.
279+ overloads_differ = !@method_types . each_cons ( 2 ) . all? { |mt1 , mt2 |
280+ positionals_match? ( mt1 , mt2 ) && keywords_match? ( mt1 , mt2 )
268281 }
269- has_uninformative_args = if overloads_differ_in_positionals
270- a_args . positionals . any? { |vtx | vertex_uninformative? ( genv , vtx ) }
282+ has_uninformative_args = if overloads_differ
283+ a_args . positionals . any? { |vtx | vertex_uninformative? ( genv , vtx ) } ||
284+ ( a_args . keywords && vertex_uninformative? ( genv , a_args . keywords ) )
271285 else
272- a_args . positionals . any? { |vtx | vtx . types . empty? }
286+ a_args . positionals . any? { |vtx | vtx . types . empty? } ||
287+ ( a_args . keywords && a_args . keywords . types . empty? )
273288 end
274289 if has_uninformative_args
275290 a_args . positionals . each do |vtx |
276291 changes . add_edge ( genv , vtx , changes . target )
277292 end
293+ # Note: keywords already have a permanent edge to the box
294+ # (established in MethodCallBox#initialize), so no extra edge needed.
278295 return
279296 end
280297
@@ -303,6 +320,22 @@ def vertex_uninformative?(genv, vtx, depth = 0)
303320 false
304321 end
305322
323+ # Typecheck a single keyword argument value against the expected type
324+ # by directly inspecting the pre-existing value vertices in the
325+ # keywords vertex's types (Record, Hash, Instance).
326+ def keyword_arg_typecheck? ( genv , changes , keywords_vtx , key , expected_ty , param_map )
327+ keywords_vtx . each_type do |kw_ty |
328+ val_vtx = case kw_ty
329+ when Type ::Hash then kw_ty . get_value ( key )
330+ when Type ::Record then kw_ty . get_value ( key )
331+ when Type ::Instance then kw_ty . mod == genv . mod_hash ? kw_ty . args [ 1 ] : nil
332+ else nil
333+ end
334+ return false if val_vtx && !expected_ty . typecheck ( genv , changes , val_vtx , param_map )
335+ end
336+ true
337+ end
338+
306339 # Check if two method types have structurally identical positional
307340 # parameter types (req, opt, rest).
308341 def positionals_match? ( mt1 , mt2 )
@@ -314,6 +347,17 @@ def positionals_match?(mt1, mt2)
314347 ( mt1 . rest_positionals . nil? || sig_types_match? ( mt1 . rest_positionals , mt2 . rest_positionals ) )
315348 end
316349
350+ # Check if two method types have structurally identical keyword
351+ # parameter types (req, opt, rest).
352+ def keywords_match? ( mt1 , mt2 )
353+ return false unless mt1 . req_keyword_keys == mt2 . req_keyword_keys
354+ return false unless mt1 . opt_keyword_keys == mt2 . opt_keyword_keys
355+ return false unless mt1 . rest_keywords . nil? == mt2 . rest_keywords . nil?
356+ mt1 . req_keyword_values . zip ( mt2 . req_keyword_values ) . all? { |a , b | sig_types_match? ( a , b ) } &&
357+ mt1 . opt_keyword_values . zip ( mt2 . opt_keyword_values ) . all? { |a , b | sig_types_match? ( a , b ) } &&
358+ ( mt1 . rest_keywords . nil? || sig_types_match? ( mt1 . rest_keywords , mt2 . rest_keywords ) )
359+ end
360+
317361 # Structural equality check for two SigTyNode objects.
318362 def sig_types_match? ( a , b )
319363 return false unless a . class == b . class
0 commit comments