File: //usr/lib/ruby/gems/3.2.0/gems/rbs-2.8.2/lib/rbs/validator.rb
# frozen_string_literal: true
module RBS
class Validator
attr_reader :env
attr_reader :resolver
attr_reader :definition_builder
def initialize(env:, resolver:)
@env = env
@resolver = resolver
@definition_builder = DefinitionBuilder.new(env: env)
end
def absolute_type(type, context:)
type.map_type_name do |type_name, _, type|
resolver.resolve(type_name, context: context) || yield(type)
end
end
# Validates presence of the relative type, and application arity match.
def validate_type(type, context:)
case type
when Types::ClassInstance, Types::Interface, Types::Alias
# @type var type: Types::ClassInstance | Types::Interface | Types::Alias
if type.name.namespace.relative?
type = _ = absolute_type(type, context: context) do |_|
NoTypeFoundError.check!(type.name.absolute!, env: env, location: type.location)
end
end
definition_builder.validate_type_name(type.name, type.location)
type_params = case type
when Types::ClassInstance
env.class_decls[type.name].type_params
when Types::Interface
env.interface_decls[type.name].decl.type_params
when Types::Alias
env.alias_decls[type.name].decl.type_params
end
InvalidTypeApplicationError.check!(
type_name: type.name,
args: type.args,
params: type_params.each.map(&:name),
location: type.location
)
when Types::ClassSingleton
definition_builder.validate_type_presence(type)
end
type.each_type do |type|
validate_type(type, context: context)
end
end
def validate_type_alias(entry:)
type_name = entry.decl.name
if type_alias_dependency.circular_definition?(type_name)
location = entry.decl.location or raise
raise RecursiveTypeAliasError.new(alias_names: [type_name], location: location)
end
if diagnostic = type_alias_regularity.nonregular?(type_name)
location = entry.decl.location or raise
raise NonregularTypeAliasError.new(diagnostic: diagnostic, location: location)
end
unless entry.decl.type_params.empty?
calculator = VarianceCalculator.new(builder: definition_builder)
result = calculator.in_type_alias(name: type_name)
if set = result.incompatible?(entry.decl.type_params)
set.each do |param_name|
param = entry.decl.type_params.find {|param| param.name == param_name } or raise
next if param.unchecked?
raise InvalidVarianceAnnotationError.new(
type_name: type_name,
param: param,
location: entry.decl.type.location
)
end
end
validate_type_params(
entry.decl.type_params,
type_name: type_name,
location: entry.decl.location&.aref(:type_params)
)
end
if block_given?
yield entry.decl.type
end
end
def validate_method_definition(method_def, type_name:)
method_def.types.each do |method_type|
unless method_type.type_params.empty?
loc = method_type.location&.aref(:type_params)
validate_type_params(
method_type.type_params,
type_name: type_name,
method_name: method_def.name,
location: loc
)
end
end
end
def validate_type_params(params, type_name: , method_name: nil, location:)
# @type var each_node: TSort::_EachNode[Symbol]
each_node = __skip__ = -> (&block) do
params.each do |param|
block[param.name]
end
end
# @type var each_child: TSort::_EachChild[Symbol]
each_child = __skip__ = -> (name, &block) do
if param = params.find {|p| p.name == name }
if b = param.upper_bound
b.free_variables.each do |tv|
block[tv]
end
end
end
end
TSort.each_strongly_connected_component(each_node, each_child) do |names|
if names.size > 1
params = names.map do |name|
params.find {|param| param.name == name} or raise
end
raise CyclicTypeParameterBound.new(
type_name: type_name,
method_name: method_name,
params: params,
location: location
)
end
end
end
def type_alias_dependency
@type_alias_dependency ||= TypeAliasDependency.new(env: env)
end
def type_alias_regularity
@type_alias_regularity ||= TypeAliasRegularity.validate(env: env)
end
end
end