From 6bb80f998fab119cfbd0242c52c0d02d448b902d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 9 Oct 2024 20:16:48 +0900 Subject: [PATCH] Emit SingletonTypeMismatch when class/module mismatch Closes #1073 --- lib/steep/diagnostic/ruby.rb | 16 ++++++++ lib/steep/type_construction.rb | 68 ++++++++++++++++++++-------------- sig/steep/diagnostic/ruby.rbs | 8 ++++ test/type_construction_test.rb | 55 ++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 29 deletions(-) diff --git a/lib/steep/diagnostic/ruby.rb b/lib/steep/diagnostic/ruby.rb index 82b114e8..834db7d6 100644 --- a/lib/steep/diagnostic/ruby.rb +++ b/lib/steep/diagnostic/ruby.rb @@ -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 @@ -961,6 +974,7 @@ def self.default ReturnTypeMismatch => :error, SetterBodyTypeMismatch => :information, SetterReturnTypeMismatch => :information, + SingletonTypeMismatch => :error, SyntaxError => :hint, TypeArgumentMismatchError => :hint, UnexpectedBlockGiven => :warning, @@ -1017,6 +1031,7 @@ def self.strict ReturnTypeMismatch => :error, SetterBodyTypeMismatch => :error, SetterReturnTypeMismatch => :error, + SingletonTypeMismatch => :error, SyntaxError => :hint, TypeArgumentMismatchError => :error, UnexpectedBlockGiven => :error, @@ -1073,6 +1088,7 @@ def self.lenient ReturnTypeMismatch => :warning, SetterBodyTypeMismatch => nil, SetterReturnTypeMismatch => nil, + SingletonTypeMismatch => nil, SyntaxError => :hint, TypeArgumentMismatchError => nil, UnexpectedBlockGiven => :hint, diff --git a/lib/steep/type_construction.rb b/lib/steep/type_construction.rb index c1ee2a80..36056681 100644 --- a/lib/steep/type_construction.rb +++ b/lib/steep/type_construction.rb @@ -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 @@ -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, diff --git a/sig/steep/diagnostic/ruby.rbs b/sig/steep/diagnostic/ruby.rbs index f49e4c97..c88d43fc 100644 --- a/sig/steep/diagnostic/ruby.rbs +++ b/sig/steep/diagnostic/ruby.rbs @@ -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 diff --git a/test/type_construction_test.rb b/test/type_construction_test.rb index 9f799e95..2b587096 100644 --- a/test/type_construction_test.rb +++ b/test/type_construction_test.rb @@ -1358,7 +1358,7 @@ def bar with_standard_construction(checker, source) do |construction, typing| construction.synthesize(source.node) - assert_typing_error(typing, size: 2) do |errors| + assert_typing_error(typing, size: 3) do |errors| assert_any!(errors) do |error| assert_instance_of Diagnostic::Ruby::IncompatibleAssignment, error assert_equal parse_type("::String"), error.rhs_type @@ -1370,6 +1370,11 @@ def bar assert_equal parse_type("::String"), error.actual assert_equal parse_type("::A::String"), error.expected end + + assert_any!(errors) do |error| + assert_instance_of Diagnostic::Ruby::SingletonTypeMismatch, error + assert_equal "::A", error.name.to_s + end end end end @@ -5068,6 +5073,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)