Skip to content

Commit

Permalink
Merge pull request #1239 from soutaro/rbs-validation
Browse files Browse the repository at this point in the history
RBS validation
  • Loading branch information
soutaro authored Sep 30, 2024
2 parents a63fce4 + b2f0715 commit e254ac9
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 63 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rbs (3.6.0.pre.2)
rbs (3.6.0.pre.3)
logger
rdoc (6.7.0)
psych (>= 4.0.0)
Expand Down
1 change: 1 addition & 0 deletions lib/steep.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
require "steep/subtyping/variable_variance"

require "steep/diagnostic/helper"
require "steep/diagnostic/result_printer2"
require "steep/diagnostic/ruby"
require "steep/diagnostic/signature"
require "steep/diagnostic/lsp_formatter"
Expand Down
48 changes: 48 additions & 0 deletions lib/steep/diagnostic/result_printer2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Steep
module Diagnostic
module ResultPrinter2
def result_line(result)
case result
when Subtyping::Result::Failure
case result.error
when Subtyping::Result::Failure::UnknownPairError
nil
when Subtyping::Result::Failure::UnsatisfiedConstraints
"Unsatisfied constraints: #{result.relation}"
when Subtyping::Result::Failure::MethodMissingError
"Method `#{result.error.name}` is missing"
when Subtyping::Result::Failure::BlockMismatchError
"Incomaptible block: #{result.relation}"
when Subtyping::Result::Failure::ParameterMismatchError
if result.relation.params?
"Incompatible arity: #{result.relation.super_type} and #{result.relation.sub_type}"
else
"Incompatible arity: #{result.relation}"
end
when Subtyping::Result::Failure::PolyMethodSubtyping
"Unsupported polymorphic method comparison: #{result.relation}"
when Subtyping::Result::Failure::SelfBindingMismatch
"Incompatible block self type: #{result.relation}"
end
else
result.relation.to_s
end
end

def detail_lines
lines = StringIO.new.tap do |io|
failure_path = result.failure_path || []
failure_path.reverse_each.filter_map do |result|
result_line(result)
end.each.with_index(1) do |message, index|
io.puts "#{" " * (index)}#{message}"
end
end.string.chomp

unless lines.empty?
lines
end
end
end
end
end
45 changes: 0 additions & 45 deletions lib/steep/diagnostic/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,51 +61,6 @@ def detail_lines
end
end

module ResultPrinter2
def result_line(result)
case result
when Subtyping::Result::Failure
case result.error
when Subtyping::Result::Failure::UnknownPairError
nil
when Subtyping::Result::Failure::UnsatisfiedConstraints
"Unsatisfied constraints: #{result.relation}"
when Subtyping::Result::Failure::MethodMissingError
"Method `#{result.error.name}` is missing"
when Subtyping::Result::Failure::BlockMismatchError
"Incomaptible block: #{result.relation}"
when Subtyping::Result::Failure::ParameterMismatchError
if result.relation.params?
"Incompatible arity: #{result.relation.super_type} and #{result.relation.sub_type}"
else
"Incompatible arity: #{result.relation}"
end
when Subtyping::Result::Failure::PolyMethodSubtyping
"Unsupported polymorphic method comparison: #{result.relation}"
when Subtyping::Result::Failure::SelfBindingMismatch
"Incompatible block self type: #{result.relation}"
end
else
result.relation.to_s
end
end

def detail_lines
lines = StringIO.new.tap do |io|
failure_path = result.failure_path || []
failure_path.reverse_each.filter_map do |result|
result_line(result)
end.each.with_index(1) do |message, index|
io.puts "#{" " * (index)}#{message}"
end
end.string.chomp

unless lines.empty?
lines
end
end
end

class IncompatibleAssignment < Base
attr_reader :lhs_type
attr_reader :rhs_type
Expand Down
54 changes: 50 additions & 4 deletions lib/steep/diagnostic/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,16 @@ class UnsatisfiableTypeApplication < Base
attr_reader :type_name
attr_reader :type_arg
attr_reader :type_param
attr_reader :result

def initialize(type_name:, type_arg:, type_param:, location:)
include ResultPrinter2

def initialize(type_name:, type_arg:, type_param:, result:, location:)
super(location: location)
@type_name = type_name
@type_arg = type_arg
@type_param = type_param
@result = result
end

def header_line
Expand Down Expand Up @@ -248,14 +252,20 @@ def header_line
class ModuleSelfTypeError < Base
attr_reader :name
attr_reader :ancestor
attr_reader :relation
attr_reader :result

include ResultPrinter2

def initialize(name:, ancestor:, relation:, location:)
def initialize(name:, ancestor:, result:, location:)
super(location: location)

@name = name
@ancestor = ancestor
@relation = relation
@result = result
end

def relation
result.relation
end

def header_line
Expand Down Expand Up @@ -417,6 +427,40 @@ def header_line
end
end

class TypeParamDefaultReferenceError < Base
attr_reader :type_param

def initialize(type_param, location:)
super(location: location)
@type_param = type_param
end

def header_line
"The default type of `#{type_param.name}` cannot depend on optional type parameters"
end
end

class UnsatisfiableGenericsDefaultType < Base
attr_reader :param_name, :result

include ResultPrinter2

def initialize(param_name, result, location:)
super(location: location)
@param_name = param_name
@result = result
end

def relation
result.relation
end

def header_line
"The default type of `#{param_name}` doesn't satisfy upper bound constarint: #{relation}"
end
end


def self.from_rbs_error(error, factory:)
case error
when RBS::ParsingError
Expand Down Expand Up @@ -515,6 +559,8 @@ def self.from_rbs_error(error, factory:)
Diagnostic::Signature::InconsistentClassModuleAliasError.new(decl: error.alias_entry.decl)
when RBS::CyclicClassAliasDefinitionError
Diagnostic::Signature::CyclicClassAliasDefinitionError.new(decl: error.alias_entry.decl)
when RBS::TypeParamDefaultReferenceError
Diagnostic::Signature::TypeParamDefaultReferenceError.new(error.type_param, location: error.location)
else
raise error
end
Expand Down
44 changes: 42 additions & 2 deletions lib/steep/signature/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def validate_type_application_constraints(type_name, type_params, type_args, loc
unchecked: param.unchecked?,
default_type: factory.type_opt(param.default_type)
),
result: result,
location: location
)
end
Expand Down Expand Up @@ -252,6 +253,39 @@ def validate_definition_type(definition)
end
end

def validate_type_params(type_name, type_params)
if error_type_params = RBS::AST::TypeParam.validate(type_params)
error_type_params.each do |type_param|
default_type = type_param.default_type or raise
@errors << Diagnostic::Signature::TypeParamDefaultReferenceError.new(type_param, location: default_type.location)
end
end

upper_bounds = type_params.each.with_object({}) do |param, bounds| #$ Hash[Symbol, AST::Types::t?]
bounds[param.name] = factory.type_opt(param.upper_bound_type)
end

checker.push_variable_bounds(upper_bounds) do
type_params.each do |type_param|
param = checker.factory.type_param(type_param)

default_type = param.default_type or next
upper_bound = param.upper_bound or next

relation = Subtyping::Relation.new(sub_type: default_type, super_type: upper_bound)
result = checker.check(relation, self_type: nil, instance_type: nil, class_type: nil, constraints: Subtyping::Constraints.empty)

if result.failure?
@errors << Diagnostic::Signature::UnsatisfiableGenericsDefaultType.new(
type_param.name,
result,
location: (type_param.default_type || raise).location
)
end
end
end
end

def validate_one_class_decl(name, entry)
rescue_validation_errors(name) do
Steep.logger.debug { "Validating class definition `#{name}`..." }
Expand Down Expand Up @@ -310,7 +344,7 @@ def validate_one_class_decl(name, entry)
name: name,
location: ancestor.source&.location || raise,
ancestor: ancestor,
relation: relation
result: _1
)
end
end
Expand Down Expand Up @@ -405,7 +439,7 @@ def validate_one_class_decl(name, entry)
name: name,
location: ancestor.source&.location || raise,
ancestor: ancestor,
relation: relation
result: _1
)
end
end
Expand All @@ -419,6 +453,8 @@ def validate_one_class_decl(name, entry)
validate_definition_type(definition)
end
end

validate_type_params(name, entry.type_params)
end
end
end
Expand Down Expand Up @@ -480,6 +516,8 @@ def validate_one_interface(name)
Steep.logger.tagged "#{name}" do
definition = builder.build_interface(name)

validate_type_params(name, definition.type_params_decl)

upper_bounds = definition.type_params_decl.each.with_object({}) do |param, bounds|
bounds[param.name] = factory.type_opt(param.upper_bound_type)
end
Expand Down Expand Up @@ -575,6 +613,8 @@ def validate_one_alias(name, entry = env.type_alias_decls[name])
builder.validate_type_name(outer, entry.decl.location&.aref(:name))
end

validate_type_params(name, entry.decl.type_params)

upper_bounds = entry.decl.type_params.each.with_object({}) do |param, bounds|
bounds[param.name] = factory.type_opt(param.upper_bound_type)
end
Expand Down
15 changes: 15 additions & 0 deletions sample/sig/generics.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module LocationHelper : _WithLocation
interface _WithLocation
def location_method: () -> String
end

def hello: () -> void
end

class StringGeneric[X < Integer, Y < Integer = String]
def location_method: () -> Integer

include LocationHelper
extend LocationHelper
end

13 changes: 13 additions & 0 deletions sig/steep/diagnostic/result_printer2.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Steep
module Diagnostic
module ResultPrinter2 : _DiagnosticWithResult
interface _DiagnosticWithResult
def result: () -> Subtyping::Result::t
end

def result_line: (Subtyping::Result::t result) -> String?

def detail_lines: () -> String?
end
end
end
8 changes: 1 addition & 7 deletions sig/steep/diagnostic/ruby.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ module Steep
def detail_lines: () -> String?
end

module ResultPrinter2 : _DiagnosticWithResult
def result_line: (Subtyping::Result::t result) -> String?

def detail_lines: () -> String?
end

class IncompatibleAssignment < Base
attr_reader lhs_type: untyped

Expand Down Expand Up @@ -648,7 +642,7 @@ module Steep

attr_reader block_pair: [Interface::Block?, Interface::Block?]?

attr_reader result: Subtyping::Result::Base
attr_reader result: Subtyping::Result::t

include ResultPrinter2

Expand Down
Loading

0 comments on commit e254ac9

Please sign in to comment.