soutaro / steep

Static type checker for Ruby
MIT License
1.37k stars 83 forks source link

Ruby::ArgumentTypeMismatch Cannot pass a value of type `(::Type | nil)` as an argument of type `::Type` #1079

Closed wilsonsilva closed 6 months ago

wilsonsilva commented 7 months ago

Problem

Steep ignores 3 nil checks on the same expression.

Offending code:

class Event
  attr_reader :pubkey
  attr_accessor :id, :sig

  def initialize(pubkey:, id: nil, sig: nil) # <--- Note that id and sig can be nil
    @pubkey = pubkey
    @id = id
    @sig = sig
  end

  def verify_signature
    return false if id.nil? || pubkey.nil? || sig.nil? # <--- Ignored by Steep

    valid_sig?(id, pubkey, sig) # <--- pubkey and sig should not be nil here. The line above ensures that
  end

  def valid_sig?(message, public_key, signature)
    true
  end
end

RBS:

class Event
  attr_reader pubkey: String
  attr_accessor id: String?  # <--- id can be nil
  attr_accessor sig: String?  # <--- sig can be nil

  def initialize: (pubkey: String, ?id: String?, ?sig: String?) -> void
  def verify_signature: -> bool
  def valid_sig?: (String, String, String) -> bool # <--- none can be nil
end

Expected behaviour

No errors should be reported by Steep. The method valid_sig? requires non-nullable arguments. And the method verify_signature ensures that the arguments id, pubkey and sig are not null before calling the method valid_sig?.

$ bundle exec steep check
# Type checking files:

.....................................................................................

No type error detected. πŸ«–

Actual behaviour

The following error is reported by Steep:

$ bundle exec steep check
nostr.rb:24:27: [error] Cannot pass a value of type `(::String | nil)` as an argument of type `::String`
β”‚   (::String | nil) <: ::String
β”‚     nil <: ::String
β”‚
β”‚ Diagnostic ID: Ruby::ArgumentTypeMismatch
β”‚
β””     crypto.valid_sig?(id, pubkey, sig)
                                    ~~~

Detected 1 problem from 1 file

But only when the 3 nil checks on the same expression. Fewer nil checks do not trigger the error.

Workaround

Use 2 or fewer nil checks on the same expression.

def verify_signature
  return false if id.nil? || pubkey.nil?
  return false if sig.nil?

  valid_sig?(id, pubkey, sig)
end

Steps to reproduce

  1. Clone the repo git clone git@github.com:wilsonsilva/steep-nil-argument-type-mismatch-bug.git
  2. Run bundle install
  3. Run bundle exec steep check

Environment

A tiny sample project to reproduce the problem https://github.com/wilsonsilva/steep-nil-argument-type-mismatch-bug

soutaro commented 6 months ago

Thank you for reporting the problem. Confirmed. @wilsonsilva

soutaro commented 6 months ago

The problem is caused because method call purity of pubkey() is omitted during type checking the || clause.

soutaro commented 6 months ago

Released 1.7.0.dev.3 with the fix for this problem.

wilsonsilva commented 6 months ago

Worked like a charm. Thank you!