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 intersections not operating correctly #1361

Open
KieranP opened this issue Nov 27, 2024 · 1 comment
Open

Type intersections not operating correctly #1361

KieranP opened this issue Nov 27, 2024 · 1 comment

Comments

@KieranP
Copy link

KieranP commented Nov 27, 2024

Seems like Steep might be joining/operating on RBS intersection types incorrectly.

Given the following RBS:

type modelsItemsBaseAttrs = { ?durability: ::Integer }
type modelsItemsSwordAttrs = { ?sharpness: ::Integer } & modelsItemsBaseAttrs

class Models
  class Item < Base
    class Base
      attr_reader durability: ::Integer

      def initialize: (?modelsItemsBaseAttrs attrs) -> void
    end

    class Sword < Base
      attr_reader sharpness: ::Integer

      def initialize: (?modelsItemsSwordAttrs attrs) -> void
    end
  end
end

and this implementation:

class Models
  class Item < Base
    class Base
      attr_reader :durability

      def initialize(attrs = {})
        @durability = attrs[:durability] || 100
      end
    end

    class Sword < Base
      attr_reader :sharpness

      def initialize(attrs = {})
        super
        @sharpness = attrs[:sharpness] || 1
      end
    end
  end
end

I should not be seeing any errors, but steep is returning the following:

app/models/items.rb:14:29: [hint] Cannot detect the type of the expression
│ Diagnostic ID: Ruby::FallbackAny
│
└       def initialize(attrs = {})
                               ~~

app/models/items.rb:14:21: [hint] Cannot assign a value of type `::Hash[untyped, untyped]` to an expression of type `::modelsItemsSwordAttrs`
│   ::Hash[untyped, untyped] <: ::modelsItemsSwordAttrs
│     ::Hash[untyped, untyped] <: ({ ?:sharpness => ::Integer } & ::modelsItemsBaseAttrs)
│       ::Hash[untyped, untyped] <: { ?:sharpness => ::Integer }
│
│ Diagnostic ID: Ruby::IncompatibleAssignment
│
└       def initialize(attrs = {})
                       ~~~~~~~~~~

app/models/items.rb:16:21: [error] Cannot find compatible overloading of method `[]` of type `({ ?:sharpness => ::Integer } & { ?:durability => ::Integer })`
│ Method types:
│   def []: (:durability) -> (::Integer | nil)
│         | (:durability) -> ::Integer
│
│ Diagnostic ID: Ruby::UnresolvedOverloading
│
└         @sharpness = attrs[:sharpness] || 1

Weird things:

  • Steep complains about attrs being Ruby::FallbackAny when clearly it is a hash, both the default value and the typing
  • It doesn't like assigning a Hash on line 14, but has no problem doing the same thing on line 6??
  • If I move modelsItemsBaseAttrs to the front of the intersection for type modelsItemsSwordAttrs, i.e. type modelsItemsSwordAttrs = modelsItemsBaseAttrs & { ?sharpness: ::Integer }, the 3rd error around Ruby::UnresolvedOverloading disappears, even though the type orderings produce the same intersection result.

What is happening here? Am I using intersections in the wrong way? If so, how do we combine two types into a superset type? i.e. { ?a: Integer } & { ?b: String } => { ?a: Integer, ?b: String }

@KieranP
Copy link
Author

KieranP commented Dec 6, 2024

Confirming that this issue is still present in the newly released 1.9 version. The "UnresolvedOverloading" error is no longer present when I move modelsItemsBaseAttrs to the start or end of the type union, but the "IncompatibleAssignment" error is still present, and "FallbackAny" has been replaced by a "UnannotatedEmptyCollection" error.

app/models/items/sword.rb:14:29: [error] Empty hash doesn't have type annotation
│ Diagnostic ID: Ruby::UnannotatedEmptyCollection
│
└       def initialize(attrs = {})
                               ~~

app/models/items/sword.rb:14:21: [error] Cannot assign a value of type `::Hash[untyped, untyped]` to an expression of type `::modelsItemsSwordAttrs`
│   ::Hash[untyped, untyped] <: ::modelsItemsSwordAttrs
│     ::Hash[untyped, untyped] <: (::modelsItemsBaseAttrs & { ?:sharpness => ::Integer })
│       ::Hash[untyped, untyped] <: ::modelsItemsBaseAttrs
│         ::Hash[untyped, untyped] <: { ?:durability => ::Integer }
│
│ Diagnostic ID: Ruby::IncompatibleAssignment
│
└       def initialize(attrs = {})

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