Skip to content

Commit

Permalink
Emit SingletonTypeMismatch when class/module mismatch
Browse files Browse the repository at this point in the history
Closes #1073
  • Loading branch information
tk0miya committed Oct 9, 2024
1 parent 3b4c928 commit 0b26a4d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 28 deletions.
16 changes: 16 additions & 0 deletions lib/steep/diagnostic/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,19 @@ def header_line
end
end

class SingletonTypeMismatch < Base
attr_reader :name

def initialize(node:, name:)
super(node: node, location: node.loc.name) # steep:ignore NoMethod
@name = name
end

def header_line
"Singleton object is incompatible with declaration `#{name}`"
end
end

class MethodArityMismatch < Base
attr_reader :method_type

Expand Down Expand Up @@ -961,6 +974,7 @@ def self.default
ReturnTypeMismatch => :error,
SetterBodyTypeMismatch => :information,
SetterReturnTypeMismatch => :information,
SingletonTypeMismatch => :error,
SyntaxError => :hint,
TypeArgumentMismatchError => :hint,
UnexpectedBlockGiven => :warning,
Expand Down Expand Up @@ -1017,6 +1031,7 @@ def self.strict
ReturnTypeMismatch => :error,
SetterBodyTypeMismatch => :error,
SetterReturnTypeMismatch => :error,
SingletonTypeMismatch => :error,
SyntaxError => :hint,
TypeArgumentMismatchError => :error,
UnexpectedBlockGiven => :error,
Expand Down Expand Up @@ -1073,6 +1088,7 @@ def self.lenient
ReturnTypeMismatch => :warning,
SetterBodyTypeMismatch => nil,
SetterReturnTypeMismatch => nil,
SingletonTypeMismatch => nil,
SyntaxError => :hint,
TypeArgumentMismatchError => nil,
UnexpectedBlockGiven => :hint,
Expand Down
68 changes: 40 additions & 28 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -357,35 +357,40 @@ def for_module(node, new_module_name)
end

if implement_module_name
module_entry = checker.factory.definition_builder.env.normalized_module_entry(implement_module_name.name) or raise

module_context = module_context.update(
instance_type: AST::Types::Intersection.build(
types: [
AST::Builtin::Object.instance_type,
*module_entry.self_types.map {|module_self|
type = case
when module_self.name.interface?
RBS::Types::Interface.new(
name: module_self.name,
args: module_self.args,
location: module_self.location
)
when module_self.name.class?
RBS::Types::ClassInstance.new(
name: module_self.name,
args: module_self.args,
location: module_self.location
)
else
raise
end
checker.factory.type(type)
},
module_context.instance_type
].compact
module_entry = checker.factory.definition_builder.env.normalized_module_entry(implement_module_name.name)
if module_entry
module_context = module_context.update(
instance_type: AST::Types::Intersection.build(
types: [
AST::Builtin::Object.instance_type,
*module_entry.self_types.map {|module_self|
type = case
when module_self.name.interface?
RBS::Types::Interface.new(
name: module_self.name,
args: module_self.args,
location: module_self.location
)
when module_self.name.class?
RBS::Types::ClassInstance.new(
name: module_self.name,
args: module_self.args,
location: module_self.location
)
else
raise
end
checker.factory.type(type)
},
module_context.instance_type
].compact
)
)
)
elsif checker.factory.definition_builder.env.normalized_class_entry(implement_module_name.name)
typing.add_error(
Diagnostic::Ruby::SingletonTypeMismatch.new(node: node, name: new_module_name)
)
end
end

if annots.instance_type
Expand Down Expand Up @@ -472,6 +477,13 @@ def for_class(node, new_class_name, super_class_name)
if super_class_name && implement_module_name.name == absolute_name(super_class_name)
module_context = module_context.update(instance_definition: nil, module_definition: nil)
end

if !checker.factory.definition_builder.env.normalized_class_entry(implement_module_name.name) &&
checker.factory.definition_builder.env.normalized_module_entry(implement_module_name.name)
typing.add_error(
Diagnostic::Ruby::SingletonTypeMismatch.new(node: node, name: new_class_name)
)
end
else
module_context = module_context.update(
instance_type: AST::Builtin::Object.instance_type,
Expand Down
8 changes: 8 additions & 0 deletions sig/steep/diagnostic/ruby.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ module Steep
def header_line: () -> ::String
end

class SingletonTypeMismatch < Base
attr_reader name: untyped

def initialize: (node: untyped, name: untyped) -> void

def header_line: () -> ::String
end

class MethodArityMismatch < Base
attr_reader method_type: untyped

Expand Down
48 changes: 48 additions & 0 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5068,6 +5068,54 @@ module Module1
end
end

def test_module_type_mismatch
with_checker <<-EOF do |checker|
class SampleModule
end
EOF
source = parse_ruby(<<-EOF)
module SampleModule
end
EOF

with_standard_construction(checker, source) do |construction, typing|
construction.synthesize(source.node)

assert_typing_error(typing, size: 1) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::SingletonTypeMismatch, error
assert_equal '::SampleModule', error.name.to_s
end
end
end
end
end

def test_class_type_mismatch
with_checker <<-EOF do |checker|
module SampleClass
end
class SampleModule
end
EOF
source = parse_ruby(<<-EOF)
class SampleClass
end
EOF

with_standard_construction(checker, source) do |construction, typing|
construction.synthesize(source.node)

assert_typing_error(typing, size: 1) do |errors|
assert_any!(errors) do |error|
assert_instance_of Diagnostic::Ruby::SingletonTypeMismatch, error
assert_equal '::SampleClass', error.name.to_s
end
end
end
end
end

def test_module_no_rbs
with_checker do |checker|
source = parse_ruby(<<-EOF)
Expand Down

0 comments on commit 0b26a4d

Please sign in to comment.