Skip to content

Commit

Permalink
Add UnannotatedEmptyCollection diagnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
soutaro committed Nov 19, 2024
1 parent 271802b commit fd91c3f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 4 deletions.
10 changes: 10 additions & 0 deletions lib/steep/diagnostic/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,13 @@ def header_line
end
end

class UnannotatedEmptyCollection < Base
def header_line
node or raise
"Empty #{node.type} doesn't have type annotation"
end
end

ALL = ObjectSpace.each_object(Class).with_object([]) do |klass, array|
if klass < Base
array << klass
Expand Down Expand Up @@ -963,6 +970,7 @@ def self.default
SetterReturnTypeMismatch => :information,
SyntaxError => :hint,
TypeArgumentMismatchError => :hint,
UnannotatedEmptyCollection => :warning,
UnexpectedBlockGiven => :warning,
UnexpectedDynamicMethod => :hint,
UnexpectedError => :hint,
Expand Down Expand Up @@ -1019,6 +1027,7 @@ def self.strict
SetterReturnTypeMismatch => :error,
SyntaxError => :hint,
TypeArgumentMismatchError => :error,
UnannotatedEmptyCollection => :error,
UnexpectedBlockGiven => :error,
UnexpectedDynamicMethod => :information,
UnexpectedError => :information,
Expand Down Expand Up @@ -1075,6 +1084,7 @@ def self.lenient
SetterReturnTypeMismatch => nil,
SyntaxError => :hint,
TypeArgumentMismatchError => nil,
UnannotatedEmptyCollection => :hint,
UnexpectedBlockGiven => :hint,
UnexpectedDynamicMethod => nil,
UnexpectedError => :hint,
Expand Down
4 changes: 2 additions & 2 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,7 @@ def synthesize(node, hint: nil, condition: false)
# ok
else
unless hint == pair.type
pair.constr.typing.add_error Diagnostic::Ruby::FallbackAny.new(node: node)
pair.constr.typing.add_error Diagnostic::Ruby::UnannotatedEmptyCollection.new(node: node)
end
end
end
Expand Down Expand Up @@ -1704,7 +1704,7 @@ def synthesize(node, hint: nil, condition: false)
add_typing node, type: array
end
else
typing.add_error Diagnostic::Ruby::FallbackAny.new(node: node)
typing.add_error Diagnostic::Ruby::UnannotatedEmptyCollection.new(node: node)
add_typing node, type: AST::Builtin::Array.instance_type(AST::Builtin.any_type)
end
else
Expand Down
27 changes: 27 additions & 0 deletions sig/steep/diagnostic/ruby.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,33 @@ module Steep
def header_line: () -> String
end

# An empty array/hash has no type assertion
#
# They are typed as `Array[untyped]` or `Hash[untyped, untyped]`,
# which allows any element to be added.
#
# ```rb
# a = []
# b = {}
#
# a << 1
# a << ""
# ```
#
# Add type annotation to make your assumption explicit.
#
# ```rb
# a = [] #: Array[Integer]
# b = {} #: untyped
#
# a << 1
# a << "" # => Type error
# ```
#
class UnannotatedEmptyCollection < Base
def header_line: () -> String
end

ALL: Array[singleton(Base)]

type template = Hash[singleton(Base), LSPFormatter::severity?]
Expand Down
46 changes: 46 additions & 0 deletions test/type_check_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2483,4 +2483,50 @@ def test_untyped_hash
YAML
)
end

def test_empty_collection
run_type_check_test(
signatures: {
"a.rbs" => <<~RBS
RBS
},
code: {
"a.rb" => <<~RUBY
a = []
b = {}
x = [] #: Array[String]
y = {} #: Hash[String, untyped]
t = [] #: untyped
s = {} #: untyped
RUBY
},
expectations: <<~YAML
---
- file: a.rb
diagnostics:
- range:
start:
line: 1
character: 4
end:
line: 1
character: 6
severity: ERROR
message: Empty array doesn't have type annotation
code: Ruby::UnannotatedEmptyCollection
- range:
start:
line: 2
character: 4
end:
line: 2
character: 6
severity: ERROR
message: Empty hash doesn't have type annotation
code: Ruby::UnannotatedEmptyCollection
YAML
)
end
end
4 changes: 2 additions & 2 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ def foo: (Integer) -> void

assert_typing_error(typing, size: 2) do |errors|
errors[0].tap do |error|
assert_instance_of Diagnostic::Ruby::FallbackAny, error
assert_instance_of Diagnostic::Ruby::UnannotatedEmptyCollection, error
end

errors[1].tap do |error|
Expand Down Expand Up @@ -4369,7 +4369,7 @@ def test_empty_array_is_error

assert_equal 2, typing.errors.size
assert_all typing.errors do |error|
error.is_a?(Diagnostic::Ruby::FallbackAny)
error.is_a?(Diagnostic::Ruby::UnannotatedEmptyCollection)
end
end
end
Expand Down

0 comments on commit fd91c3f

Please sign in to comment.