module Steep
module Signature
class Validator
Location = RBS::Location
Declarations = RBS::AST::Declarations
attr_reader :checker
def initialize(checker:)
@checker = checker
@errors = []
end
def has_error?
!no_error?
end
def no_error?
@errors.empty?
end
def each_error(&block)
if block_given?
@errors.each(&block)
else
enum_for :each_error
end
end
def env
checker.factory.env
end
def builder
checker.factory.definition_builder
end
def type_name_resolver
@type_name_resolver ||= RBS::TypeNameResolver.from_env(env)
end
def validator
@validator ||= RBS::Validator.new(env: env, resolver: type_name_resolver)
end
def factory
checker.factory
end
def validate
@errors = []
validate_decl
validate_const
validate_global
validate_alias
end
def validate_type(type)
Steep.logger.debug "#{Location.to_string type.location}: Validating #{type}..."
validator.validate_type type, context: [RBS::Namespace.root]
end
def ancestor_to_type(ancestor)
case ancestor
when RBS::Definition::Ancestor::Instance
args = ancestor.args.map {|type| checker.factory.type(type) }
case
when ancestor.name.interface?
AST::Types::Name::Interface.new(name: ancestor.name, args: args, location: nil)
when ancestor.name.class?
AST::Types::Name::Instance.new(name: ancestor.name, args: args, location: nil)
else
raise "#{ancestor.name}"
end
else
raise "Unexpected ancestor: #{ancestor.inspect}"
end
end
def mixin_constraints(definition, mixin_ancestors, immediate_self_types:)
relations = []
self_type = checker.factory.type(definition.self_type)
if immediate_self_types && !immediate_self_types.empty?
self_type = AST::Types::Intersection.build(
types: immediate_self_types.map {|st| ancestor_to_type(st) }.push(self_type),
location: nil
)
end
mixin_ancestors.each do |ancestor|
args = ancestor.args.map {|type| checker.factory.type(type) }
ancestor_ancestors = builder.ancestor_builder.one_instance_ancestors(ancestor.name)
self_constraints = ancestor_ancestors.self_types.map do |self_ancestor|
s = Interface::Substitution.build(ancestor_ancestors.params, args)
ancestor_to_type(self_ancestor).subst(s)
end
self_constraints.each do |constraint|
relations << [
Subtyping::Relation.new(sub_type: self_type, super_type: constraint),
ancestor
]
end
end
relations
end
def validate_one_class(name)
rescue_validation_errors(name) do
Steep.logger.debug { "Validating class definition `#{name}`..." }
Steep.logger.tagged "#{name}" do
builder.build_instance(name).tap do |definition|
definition.instance_variables.each do |name, var|
if parent = var.parent_variable
var_type = checker.factory.type(var.type)
parent_type = checker.factory.type(parent.type)
relation = Subtyping::Relation.new(sub_type: var_type, super_type: parent_type)
result1 = checker.check(
relation,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
)
result2 = checker.check(
relation.flip,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
)
unless result1.success? and result2.success?
@errors << Diagnostic::Signature::InstanceVariableTypeError.new(
name: name,
location: var.type.location,
var_type: var_type,
parent_type: parent_type
)
end
end
end
ancestors = builder.ancestor_builder.one_instance_ancestors(name)
mixin_constraints(definition, ancestors.included_modules, immediate_self_types: ancestors.self_types).each do |relation, ancestor|
checker.check(
relation,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
).else do
@errors << Diagnostic::Signature::ModuleSelfTypeError.new(
name: name,
location: ancestor.source&.location || raise,
ancestor: ancestor,
relation: relation
)
end
end
definition.each_type do |type|
validate_type type
end
end
builder.build_singleton(name).tap do |definition|
definition.instance_variables.each do |name, var|
if parent = var.parent_variable
var_type = checker.factory.type(var.type)
parent_type = checker.factory.type(parent.type)
relation = Subtyping::Relation.new(sub_type: var_type, super_type: parent_type)
result1 = checker.check(
relation,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
)
result2 = checker.check(
relation.flip,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
)
unless result1.success? and result2.success?
@errors << Diagnostic::Signature::InstanceVariableTypeError.new(
name: name,
location: var.type.location,
var_type: var_type,
parent_type: parent_type
)
end
end
end
ancestors = builder.ancestor_builder.one_singleton_ancestors(name)
mixin_constraints(definition, ancestors.extended_modules, immediate_self_types: ancestors.self_types).each do |relation, ancestor|
checker.check(
relation,
self_type: AST::Types::Self.new,
instance_type: AST::Types::Instance.new,
class_type: AST::Types::Class.new,
constraints: Subtyping::Constraints.empty
).else do
@errors << Diagnostic::Signature::ModuleSelfTypeError.new(
name: name,
location: ancestor.source&.location || raise,
ancestor: ancestor,
relation: relation
)
end
end
definition.each_type do |type|
validate_type type
end
end
end
end
end
def validate_one_interface(name)
rescue_validation_errors(name) do
Steep.logger.debug "Validating interface `#{name}`..."
Steep.logger.tagged "#{name}" do
builder.build_interface(name).each_type do |type|
validate_type type
end
end
end
end
def validate_decl
env.class_decls.each_key do |name|
validate_one_class(name)
end
env.interface_decls.each_key do |name|
validate_one_interface(name)
end
end
def validate_const
env.constant_decls.each do |name, entry|
validate_one_constant(name, entry)
end
end
def validate_one_constant(name, entry)
rescue_validation_errors do
Steep.logger.debug "Validating constant `#{name}`..."
builder.ensure_namespace!(name.namespace, location: entry.decl.location)
validate_type entry.decl.type
end
end
def validate_global
env.global_decls.each do |name, entry|
validate_one_global(name, entry)
end
end
def validate_one_global(name, entry)
rescue_validation_errors do
Steep.logger.debug "Validating global `#{name}`..."
validate_type entry.decl.type
end
end
def validate_one_alias(name)
rescue_validation_errors(name) do
Steep.logger.debug "Validating alias `#{name}`..."
builder.expand_alias(name).tap do |type|
validate_type(type)
end
end
end
def validate_alias
env.alias_decls.each do |name, entry|
validate_one_alias(name)
end
end
def rescue_validation_errors(type_name = nil)
yield
rescue RBS::ErrorBase => exn
@errors << Diagnostic::Signature.from_rbs_error(exn, factory: factory)
end
end
end
end