Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type failure against an intersection type on **kwargs #701

Closed
lloeki opened this issue Jan 9, 2023 · 2 comments
Closed

Type failure against an intersection type on **kwargs #701

lloeki opened this issue Jan 9, 2023 · 2 comments

Comments

@lloeki
Copy link
Contributor

lloeki commented Jan 9, 2023

Reproduction:

# Steepfile
target :lib do
  check '.'
  signature '.'
end
# kwargs.rbs
class C
  def foo: (a: ::String, ?b: ::String?, **::Hash[::Symbol, ::String] others) -> void
  def bar: (kwargs: ::Hash[::Symbol, ::String]) -> void
end
# kwargs.rb
class C
  def foo(a:, b: nil, **others)
    p a
    p b
    p others
  end

  def bar(kwargs:)
    a = kwargs.delete(:a)

    raise ArgumentError, ':a cannot be nil' if a.nil?

    foo(a: a, **kwargs)
  end
end

C.new.bar(kwargs: { a: '1', b: '2', c: '3' })
kwargs.rb:13:16: [error] Cannot pass a value of type `::Hash[::Symbol, ::String]` as an argument of type `::Hash[::Symbol, ((::String | nil) & ::String & ::Hash[::Symbol, ::String])]`
│   ::Hash[::Symbol, ::String] <: ::Hash[::Symbol, ((::String | nil) & ::String & ::Hash[::Symbol, ::String])]
│     ::String <: ((::String | nil) & ::String & ::Hash[::Symbol, ::String])
│       ::String <: ::Hash[::Symbol, ::String]
│         ::Object <: ::Hash[::Symbol, ::String]
│           ::BasicObject <: ::Hash[::Symbol, ::String]
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└     foo(a: a, **kwargs)
                  ~~~~~~

I can't quite wrap my head around (::String | nil) & ::String & ::Hash[::Symbol, ::String] which seems to come from steep and seems to mean that it should be all of these types at once, but it can't be a String and a Hash at the same time, ever?

@lloeki
Copy link
Contributor Author

lloeki commented Jan 9, 2023

The way I understand this (which may be completely incorrect), steep synthesises the following kwargs type:

::Hash[::Symbol, ((::String | nil) & ::String & ::Hash[::Symbol, ::String])]

where:

  • ::Hash[::Symbol, ...] comes from it being keyword arguments
  • (::String | nil) comes from ?b: ::String?
  • ::String comes from a: ::String
  • ::Hash[::Symbol, ::String] comes from **::Hash[::Symbol, ::String] others

But presumably since **others is a ** kwarg catch all, it should not sit at the same level as :a and :b, and instead should be union'd with the parent ::Hash[::Symbol, ...]:

::Hash[::Symbol, ((::String | nil) & ::String)] | ::Hash[::Symbol, ::String]

Also, unless I'm mistaken about the meaning of &, (::String | nil) & ::String should be (::String | nil) | ::String since a given kwargs value could be any of these, not all at the same time.

So it would become:

::Hash[::Symbol, ((::String | nil) | ::String) | ::Hash[::Symbol, ::String])]

Which can be reduced into:

::Hash[::Symbol, ::String | nil)]

Which should ultimately accept the ::Hash[::Symbol, ::String] value.

@lloeki
Copy link
Contributor Author

lloeki commented Mar 17, 2023

Oh, wait, foo signature is wrong, it should be:

  def foo: (a: ::String, ?b: ::String?, **::String others) -> void

I seem to have missed that **::String others meaning that others will be a ::Hash[::Symbol, ::String]. I was mistakenly expecting syntax to be as exposed in this proposal.

And with that change steep check passes. Sorry for the noise!

@lloeki lloeki closed this as completed Mar 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant