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 @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::Resolver::TypeNameResolver.new(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_application_constraints(type_name, type_params, type_args, location:) if type_params.size == type_args.size subst = Interface::Substitution.build( type_params.map(&:name), type_args.map {|type| factory.type(type) } ) type_params.zip(type_args).each do |param, arg| arg or raise if param.upper_bound upper_bound_type = factory.type(param.upper_bound).subst(subst) arg_type = factory.type(arg) constraints = Subtyping::Constraints.empty checker.check( Subtyping::Relation.new(sub_type: arg_type, super_type: upper_bound_type), self_type: AST::Types::Self.instance, class_type: nil, instance_type: nil, constraints: constraints ).else do |result| @errors << Diagnostic::Signature::UnsatisfiableTypeApplication.new( type_name: type_name, type_arg: arg_type, type_param: Interface::TypeParam.new( name: param.name, upper_bound: upper_bound_type, variance: param.variance, unchecked: param.unchecked? ), location: location ) end end end end end def validate_type_application(type) name, type_params, type_args = case type when RBS::Types::ClassInstance [ type.name, builder.build_instance(type.name).type_params_decl, type.args ] when RBS::Types::Interface [ type.name, builder.build_interface(type.name).type_params_decl, type.args ] when RBS::Types::Alias entry = env.type_alias_decls[type.name] [ type.name, entry.decl.type_params, type.args ] end if name && type_params && type_args if !type_params.empty? && !type_args.empty? validate_type_application_constraints(name, type_params, type_args, location: type.location) end end type.each_type do |child| validate_type_application(child) end end def validate_type(type) Steep.logger.debug "#{Location.to_string type.location}: Validating #{type}..." validator.validate_type(type, context: nil) validate_type_application(type) 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:) # @type var relations: Array[[Subtyping::Relation[AST::Types::t], RBS::Definition::Ancestor::Instance]] relations = [] self_type = checker.factory.type(definition.self_type) if immediate_self_types && !immediate_self_types.empty? # @type var sts: Array[AST::Types::t] sts = immediate_self_types.map {|st| ancestor_to_type(st) } self_type = AST::Types::Intersection.build(types: sts.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) ancestor_ancestors.self_types or raise ancestor_ancestors.params or raise 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 each_method_type(definition) type_name = definition.type_name definition.methods.each_value do |method| if method.defined_in == type_name method.method_types.each do |method_type| yield method_type end end end end def each_variable_type(definition) type_name = definition.type_name definition.instance_variables.each_value do |var| if var.declared_in == type_name yield var.type end end definition.class_variables.each_value do |var| if var.declared_in == type_name yield var.type end end end def validate_definition_type(definition) each_method_type(definition) do |method_type| upper_bounds = method_type.type_params.each.with_object({}) do |param, hash| hash[param.name] = factory.type_opt(param.upper_bound) end checker.push_variable_bounds(upper_bounds) do method_type.each_type do |type| validate_type(type) end end end each_variable_type(definition) do |type| validate_type(type) end end def validate_one_class_decl(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| upper_bounds = definition.type_params_decl.each.with_object({}) do |param, bounds| bounds[param.name] = factory.type_opt(param.upper_bound) end checker.push_variable_bounds(upper_bounds) do 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: nil, instance_type: nil, class_type: nil, constraints: Subtyping::Constraints.empty) result2 = checker.check(relation.flip, self_type: nil, instance_type: nil, class_type: nil, 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 || raise, immediate_self_types: ancestors.self_types).each do |relation, ancestor| checker.check( relation, self_type: AST::Types::Self.instance, instance_type: AST::Types::Instance.instance, class_type: AST::Types::Class.instance, constraints: Subtyping::Constraints.empty ).else do raise if ancestor.source.is_a?(Symbol) @errors << Diagnostic::Signature::ModuleSelfTypeError.new( name: name, location: ancestor.source&.location || raise, ancestor: ancestor, relation: relation ) end end ancestors.each_ancestor do |ancestor| case ancestor when RBS::Definition::Ancestor::Instance validate_ancestor_application(name, ancestor) end end validate_definition_type(definition) end end builder.build_singleton(name).tap do |definition| entry = case definition.entry when RBS::Environment::ClassEntry, RBS::Environment::ModuleEntry definition.entry else raise end 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.instance, instance_type: AST::Types::Instance.instance, class_type: AST::Types::Class.instance, constraints: Subtyping::Constraints.empty ) result2 = checker.check( relation.flip, self_type: AST::Types::Self.instance, instance_type: AST::Types::Instance.instance, class_type: AST::Types::Class.instance, 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 definition.class_variables.each do |name, var| if var.declared_in == definition.type_name if (parent = var.parent_variable) && var.declared_in != parent.declared_in class_var = entry.decls.flat_map {|decl| decl.decl.members }.find do |member| member.is_a?(RBS::AST::Members::ClassVariable) && member.name == name end if class_var loc = class_var.location #: RBS::Location[untyped, untyped]? @errors << Diagnostic::Signature::ClassVariableDuplicationError.new( class_name: definition.type_name, other_class_name: parent.declared_in, variable_name: name, location: loc&.[](:name) || raise ) end end end end ancestors = builder.ancestor_builder.one_singleton_ancestors(name) ancestors.extended_modules or raise mixin_constraints(definition, ancestors.extended_modules, immediate_self_types: ancestors.self_types).each do |relation, ancestor| checker.check( relation, self_type: AST::Types::Self.instance , instance_type: AST::Types::Instance.instance, class_type: AST::Types::Class.instance, constraints: Subtyping::Constraints.empty ).else do raise if ancestor.source.is_a?(Symbol) @errors << Diagnostic::Signature::ModuleSelfTypeError.new( name: name, location: ancestor.source&.location || raise, ancestor: ancestor, relation: relation ) end end ancestors.each_ancestor do |ancestor| case ancestor when RBS::Definition::Ancestor::Instance validate_ancestor_application(name, ancestor) end end validate_definition_type(definition) end end end end def validate_one_class(name) entry = env.constant_entry(name) case entry when RBS::Environment::ClassEntry, RBS::Environment::ModuleEntry validate_one_class_decl(name) when RBS::Environment::ClassAliasEntry, RBS::Environment::ModuleAliasEntry validate_one_class_alias(name, entry) end end def validate_ancestor_application(name, ancestor) unless ancestor.args.empty? definition = case when ancestor.name.class? builder.build_instance(ancestor.name) when ancestor.name.interface? builder.build_interface(ancestor.name) else raise end location = case ancestor.source when :super primary_decl = env.class_decls[name].primary.decl primary_decl.is_a?(RBS::AST::Declarations::Class) or raise if super_class = primary_decl.super_class super_class.location else # Implicit super class (Object): this can be skipped in fact... primary_decl.location&.aref(:name) end else ancestor.source&.location end validate_type_application_constraints( ancestor.name, definition.type_params_decl, ancestor.args, location: location ) ancestor.args.each do |arg| validate_type(arg) end end end def validate_one_interface(name) rescue_validation_errors(name) do Steep.logger.debug "Validating interface `#{name}`..." Steep.logger.tagged "#{name}" do definition = builder.build_interface(name) upper_bounds = definition.type_params_decl.each.with_object({}) do |param, bounds| bounds[param.name] = factory.type_opt(param.upper_bound) end checker.push_variable_bounds(upper_bounds) do validate_definition_type(definition) ancestors = builder.ancestor_builder.one_interface_ancestors(name) ancestors.each_ancestor do |ancestor| case ancestor when RBS::Definition::Ancestor::Instance # Interface ancestor cannot be other than Interface ancestor.source.is_a?(Symbol) and raise defn = builder.build_interface(ancestor.name) validate_type_application_constraints( ancestor.name, defn.type_params_decl, ancestor.args, location: ancestor.source&.location || raise ) end end end end end end def validate_decl env.class_decls.each_key do |name| validate_one_class(name) end env.class_alias_decls.each do |name, entry| validate_one_class_alias(name, entry) 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, entry = env.type_alias_decls[name]) rescue_validation_errors(name) do Steep.logger.debug "Validating alias `#{name}`..." upper_bounds = entry.decl.type_params.each.with_object({}) do |param, bounds| bounds[param.name] = factory.type_opt(param.upper_bound) end validator.validate_type_alias(entry: entry) do |type| checker.push_variable_bounds(upper_bounds) do validate_type(entry.decl.type) end end end end def validate_one_class_alias(name, entry) rescue_validation_errors(name) do Steep.logger.debug "Validating class/module alias `#{name}`..." validator.validate_class_alias(entry: entry) end end def validate_alias env.type_alias_decls.each do |name, entry| validate_one_alias(name, entry) end end def rescue_validation_errors(type_name = nil) yield rescue RBS::BaseError => exn @errors << Diagnostic::Signature.from_rbs_error(exn, factory: factory) end end end end