@@ -407,10 +407,12 @@ def argument_problems_for chain, api_map, closure_pin, locals, location
407407 return [ ] if !rules . validate_calls? || base . links . first . is_a? ( Solargraph ::Source ::Chain ::ZSuper )
408408
409409 all_errors = [ ]
410+ receiver_type = base . base . infer ( api_map , closure_pin , locals )
410411 pin . signatures . sort_by { |sig | sig . parameters . length } . each do |sig |
411412 params = param_details_from_stack ( sig , pins )
412413
413- signature_errors = signature_argument_problems_for location , locals , closure_pin , params , arguments , sig , pin
414+ signature_errors = signature_argument_problems_for ( location , locals , closure_pin , params , arguments , sig ,
415+ pin , receiver_type )
414416
415417 if signature_errors . empty?
416418 # we found a signature that works - meaning errors from
@@ -431,16 +433,28 @@ def argument_problems_for chain, api_map, closure_pin, locals, location
431433 # @param arguments [Array<Source::Chain>]
432434 # @param sig [Pin::Signature]
433435 # @param pin [Pin::Method]
436+ # @param receiver_type [ComplexType] the type of the object the
437+ # method is being called on, used to resolve the restarg's
438+ # declared type (e.g. `Elem` for `Array#push`) against the
439+ # receiver's actual generic parameters (e.g. `Integer` for an
440+ # `Array<Integer>` receiver)
434441 #
435442 # @return [Array<Problem>]
436- def signature_argument_problems_for location , locals , closure_pin , params , arguments , sig , pin
443+ def signature_argument_problems_for location , locals , closure_pin , params , arguments , sig , pin , receiver_type
437444 errors = [ ]
438445 # @todo add logic mapping up restarg parameters with
439446 # arguments (including restarg arguments). Use tuples
440447 # when possible, and when not, ensure provably
441448 # incorrect situations are detected.
442449 sig . parameters . each_with_index do |par , idx |
443- return errors if par . decl == :restarg # bail out and assume the rest is valid pending better arg processing
450+ if par . decl == :restarg
451+ # A restarg absorbs every remaining positional argument at
452+ # the call site - check each of them against the restarg's
453+ # own declared/resolved type instead of bailing out on the
454+ # whole signature. This is what catches e.g. `y.push('two')`
455+ # on a `y: Array<Integer>`.
456+ return restarg_problems_for ( location , locals , closure_pin , arguments , sig , pin , receiver_type , par , idx )
457+ end
444458 argchain = arguments [ idx ]
445459 if argchain . nil?
446460 final_arg = arguments . last
@@ -500,6 +514,91 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum
500514 errors
501515 end
502516
517+ # Checks each call-site argument absorbed by a restarg parameter
518+ # against the restarg's own declared/resolved type, instead of
519+ # bailing out on the whole signature. This is what catches e.g.
520+ # `y.push('two')` on a `y: Array<Integer>`.
521+ #
522+ # @param location [Location]
523+ # @param locals [Array<Pin::LocalVariable>]
524+ # @param closure_pin [Pin::Closure]
525+ # @param arguments [Array<Source::Chain>]
526+ # @param sig [Pin::Signature]
527+ # @param pin [Pin::Method]
528+ # @param receiver_type [ComplexType] the type of the object the
529+ # method is being called on, used to resolve the restarg's
530+ # declared type (e.g. `Elem` for `Array#push`) against the
531+ # receiver's actual generic parameters (e.g. `Integer` for an
532+ # `Array<Integer>` receiver)
533+ # @param par [Pin::Parameter] the restarg parameter
534+ # @param idx [Integer] the restarg's index within sig.parameters
535+ #
536+ # @return [Array<Problem>]
537+ def restarg_problems_for location , locals , closure_pin , arguments , sig , pin , receiver_type , par , idx
538+ errors = [ ]
539+
540+ # par.return_type is the type of the local variable the
541+ # restarg is captured into inside the method body (e.g.
542+ # `Array<Integer>`, not the unwrapped per-element `Integer`) -
543+ # resolve any remaining generics against the receiver, then
544+ # unwrap one level to get the type each individual argument
545+ # must conform to.
546+ # @sg-ignore pin.closure is a Pin::Namespace for a top-level method pin
547+ wrapped_ptype = par . return_type . resolve_generics ( pin . closure , receiver_type )
548+ ptype = ComplexType . new ( wrapped_ptype . items . flat_map ( &:subtypes ) . flat_map ( &:items ) )
549+ # @sg-ignore pin.closure is a Pin::Namespace for a top-level method pin
550+ ptype = ptype . qualify ( api_map , *pin . closure . gates ) . self_to_type ( par . context )
551+ return errors if ptype . nil? || ptype . undefined?
552+
553+ restarg_arguments ( sig , arguments , idx ) . each do |restargchain |
554+ # A spread argument's own element types aren't statically
555+ # known here - skip it rather than guess.
556+ next if restargchain . nil? || restargchain . node . type == :splat
557+
558+ restargtype = restargchain . infer ( api_map , closure_pin , locals ) . self_to_type ( closure_pin . context )
559+ next unless restargtype . defined?
560+ next if arg_conforms_to? ( restargtype , ptype )
561+
562+ errors . push Problem . new ( location ,
563+ "Wrong argument type for #{ pin . path } : #{ par . name } expected #{ ptype } , received #{ restargtype } " )
564+ end
565+ errors
566+ end
567+
568+ # @param sig [Pin::Signature]
569+ # @param arguments [Array<Source::Chain>]
570+ # @param idx [Integer] the restarg's index within sig.parameters
571+ # @return [Array<Source::Chain>] the call-site arguments absorbed
572+ # by the restarg at idx
573+ # @sg-ignore flow sensitive typing incorrectly includes an
574+ # intermediate local variable's type in the inferred return type
575+ def restarg_arguments sig , arguments , idx
576+ # A restarg can be followed by trailing positional parameters
577+ # (`def foo(*path, baz)`) - those consume the last N call-site
578+ # arguments, so they don't belong to this restarg's own
579+ # arguments.
580+ # @type [Array<Pin::Parameter>]
581+ trailing_positional_params = sig . parameters [ ( idx + 1 ) ..] || [ ]
582+ trailing_positional_count = trailing_positional_params . count { |p | p . decl == :arg }
583+ # @type [Array<Source::Chain>]
584+ # @sg-ignore flow sensitive typing issue with the ternary above
585+ restargs = trailing_positional_count . zero? ? arguments [ idx ..] || [ ] : arguments [ idx ...-trailing_positional_count ] || [ ]
586+
587+ # A trailing bare hash argument (`foo(*args, key: val)`) is
588+ # parsed as an implicit kwargs hash appended to the call's
589+ # arguments - it belongs to the signature's keyword parameters,
590+ # not the restarg.
591+ # @type [Source::Chain, nil]
592+ last_arg = restargs . last
593+ has_trailing_hash = last_arg && last_arg . links . last . is_a? ( Solargraph ::Source ::Chain ::Hash )
594+ has_keyword_params = sig . parameters . any? { |p | %i[ kwarg kwoptarg kwrestarg ] . include? ( p . decl ) }
595+ if has_trailing_hash && has_keyword_params
596+ restargs [ 0 ...-1 ]
597+ else
598+ restargs
599+ end
600+ end
601+
503602 # @param sig [Pin::Signature]
504603 # @param argchain [Solargraph::Source::Chain]
505604 # @param api_map [ApiMap]
0 commit comments