Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RBS validation #1239

Merged
merged 4 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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