Repository: soutaro/steep (Ruby)

Issue:
`obj.[]=(k, v)` does not (necessarily) return `v` in Ruby
… whereas `obj[k] = v` gives `v` regardless of the implementation.
Discussion is open for whether this is a Ruby bug (inconsistency).

Ruby:
```ruby
def []=(k, v)
  -1
end

# Control Subject
p(self[:k] = :v) #: Symbol

# Experimental Subject
p self.[]=(:k, :v) #: Integer
```

Ruby output:
```
:v
-1
```

RBS:
```
class Object
  def []=: (untyped, untyped) -> Integer
end
```

`steep check`:
```
# Type checking files:

..................................................................................F.

writer.rb:9:0: [error] Assertion cannot hold: no relationship between inferred type (`::Symbol`) and asserted type (`::Integer`)
│ Diagnostic ID: Ruby::FalseAssertion
│
└ p self.[]=(:k, :v) #: Integer
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Detected 1 problem from 1 file
```


Discussion:
```ruby
def k=(v)
  -1
end

self.k=(:v) #=> :v
self.__send__(:k=, :v) #=> -1
```



Patch:
diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb
index 6beb063d9..6ddb1ccf4 100644
--- a/lib/steep/type_construction.rb
+++ b/lib/steep/type_construction.rb
@@ -3285,7 +3285,14 @@ def type_send_interface(node, interface:, receiver:, receiver_type:, method_name
 
         if call && constr
           case method_name.to_s
-          when "[]=", /\w=\Z/
+          when "[]="
+            if test_send_node(node) {|_, _, _, loc| !loc.dot }
+              last_arg = arguments.last or raise
+              if typing.has_type?(last_arg)
+                call = call.with_return_type(typing.type_of(node: last_arg))
+              end
+            end
+          when /\w=\Z/
             last_arg = arguments.last or raise
             if typing.has_type?(last_arg)
               call = call.with_return_type(typing.type_of(node: last_arg))


Test patch:
diff --git a/test/type_check_test.rb b/test/type_check_test.rb
index 69ab29547..c6fd06a08 100644
--- a/test/type_check_test.rb
+++ b/test/type_check_test.rb
@@ -1670,4 +1670,48 @@ def self.foo: () -> void
       YAML
     )
   end
+
+  def test_calls_with_index_writer_methods
+    run_type_check_test(
+      signatures: {
+        "a.rbs" => <<~RBS
+          class WithIndexWriter
+            def []=: (String, String) -> Integer
+          end
+        RBS
+      },
+      code: {
+        "a.rb" => <<~RUBY
+          obj = WithIndexWriter.new
+          obj.[]=("hoge", "huga").foo
+          obj&.[]=("hoge", "huga").foo
+        RUBY
+      },
+      expectations: <<~YAML
+        ---
+        - file: a.rb
+          diagnostics:
+          - range:
+              start:
+                line: 2
+                character: 24
+              end:
+                line: 2
+                character: 27
+            severity: ERROR
+            message: Type `::Integer` does not have method `foo`
+            code: Ruby::NoMethod
+          - range:
+              start:
+                line: 3
+                character: 25
+              end:
+                line: 3
+                character: 28
+            severity: ERROR
+            message: Type `(::Integer | nil)` does not have method `foo`
+            code: Ruby::NoMethod
+      YAML
+    )
+  end
 end