Skip to content

Commit

Permalink
Implement validator
Browse files Browse the repository at this point in the history
  • Loading branch information
soutaro committed Sep 5, 2024
1 parent 78d3cf7 commit 2a6e27b
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 17 deletions.
48 changes: 41 additions & 7 deletions lib/rbs/cli/validate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def run
private

def validate_class_module_definition
@env.class_decls.each do |name, decl|
@env.class_decls.each do |name, entry|
RBS.logger.info "Validating class/module definition: `#{name}`..."
@builder.build_instance(name).each_type do |type|
@validator.validate_type type, context: nil
Expand All @@ -107,30 +107,47 @@ def validate_class_module_definition
@errors.add(error)
end

case decl
case entry
when Environment::ClassEntry
decl.decls.each do |decl|
entry.decls.each do |decl|
if super_class = decl.decl.super_class
super_class.args.each do |arg|
void_type_context_validator(arg, true)
no_self_type_validator(arg)
no_classish_type_validator(arg)
@validator.validate_type(arg, context: nil)
end

if super_entry = @env.normalized_class_entry(super_class.name)
InvalidTypeApplicationError.check!(type_name: super_class.name, args: super_class.args, params: super_entry.type_params, location: super_class.location)
end
end
end
when Environment::ModuleEntry
decl.decls.each do |decl|
entry.decls.each do |decl|
decl.decl.self_types.each do |self_type|
self_type.args.each do |arg|
void_type_context_validator(arg, true)
no_self_type_validator(arg)
no_classish_type_validator(arg)
@validator.validate_type(arg, context: nil)
end

self_params =
if self_type.name.class?
@env.normalized_module_entry(self_type.name)&.type_params
else
@env.interface_decls[self_type.name]&.decl&.type_params
end

if self_params
InvalidTypeApplicationError.check!(type_name: self_type.name, params: self_params, args: self_type.args, location: self_type.location)
end
end
end
end

d = decl.primary.decl
d = entry.primary.decl

@validator.validate_type_params(
d.type_params,
Expand All @@ -140,13 +157,21 @@ def validate_class_module_definition

d.type_params.each do |param|
if ub = param.upper_bound_type
void_type_context_validator(ub)
void_type_context_validator(ub)
no_self_type_validator(ub)
no_classish_type_validator(ub)
@validator.validate_type(ub, context: nil)
end

if dt = param.default_type
void_type_context_validator(dt)
no_self_type_validator(dt)
no_classish_type_validator(dt)
@validator.validate_type(dt, context: nil)
end
end

decl.decls.each do |d|
entry.decls.each do |d|
d.decl.each_member do |member|
case member
when AST::Members::MethodDefinition
Expand All @@ -163,6 +188,15 @@ def validate_class_module_definition
void_type_context_validator(arg, true)
end
end
params =
if member.name.class?
module_decl = @env.normalized_module_entry(member.name) or raise
module_decl.type_params
else
interface_decl = @env.interface_decls.fetch(member.name)
interface_decl.decl.type_params
end
InvalidTypeApplicationError.check!(type_name: member.name, params: params, args: member.args, location: member.location)
when AST::Members::Var
void_type_context_validator(member.type)
if member.is_a?(AST::Members::ClassVariable)
Expand Down
3 changes: 2 additions & 1 deletion lib/rbs/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,11 @@ def initialize(type_name:, params:, ancestors:)
end

def apply(args, location:)
# Assume default types of type parameters are already added to `args`
InvalidTypeApplicationError.check!(
type_name: type_name,
args: args,
params: params,
params: params.map { AST::TypeParam.new(name: _1, variance: :invariant, upper_bound: nil, location: nil, default_type: nil) },
location: location
)

Expand Down
9 changes: 7 additions & 2 deletions lib/rbs/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,23 @@ class InvalidTypeApplicationError < DefinitionError
attr_reader :type_name
attr_reader :args
attr_reader :params
attr_reader :type_params
attr_reader :location

def initialize(type_name:, args:, params:, location:)
@type_name = type_name
@args = args
@params = params
@type_params = params
@params = params.map(&:name)
@location = location
super "#{Location.to_string location}: #{type_name} expects parameters [#{params.join(", ")}], but given args [#{args.join(", ")}]"
end

def self.check!(type_name:, args:, params:, location:)
unless args.size == params.size
min_arity = params.count { _1.default_type.nil? }
max_arity = params.size

unless min_arity <= args.size && args.size <= max_arity
raise new(type_name: type_name, args: args, params: params, location: location)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rbs/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def validate_type(type, context:)
InvalidTypeApplicationError.check!(
type_name: type.name,
args: type.args,
params: type_params.each.map(&:name),
params: type_params,
location: type.location
)
end
Expand Down
5 changes: 3 additions & 2 deletions sig/errors.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ module RBS
attr_reader type_name: TypeName
attr_reader args: Array[Types::t]
attr_reader params: Array[Symbol]
attr_reader type_params: Array[AST::TypeParam]
attr_reader location: Location[untyped, untyped]?

def initialize: (type_name: TypeName, args: Array[Types::t], params: Array[Symbol], location: Location[untyped, untyped]?) -> void
def initialize: (type_name: TypeName, args: Array[Types::t], params: Array[AST::TypeParam], location: Location[untyped, untyped]?) -> void

def self.check!: (type_name: TypeName, args: Array[Types::t], params: Array[Symbol], location: Location[untyped, untyped]?) -> void
def self.check!: (type_name: TypeName, args: Array[Types::t], params: Array[AST::TypeParam], location: Location[untyped, untyped]?) -> void
end

class RecursiveAncestorError < DefinitionError
Expand Down
126 changes: 122 additions & 4 deletions test/rbs/cli_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,14 @@ def test_validate_with_cyclic_type_parameter_bound_2
with_cli do |cli|
Dir.mktmpdir do |dir|
(Pathname(dir) + 'a.rbs').write(<<~RBS)
class Foo[A < _Each[B]]
def foo: [X < _Foo[Y]] () -> X
class Foo[A < _Each[B]]
def foo: [X < _Foo[Y]] () -> X
def bar: [X < _Foo[Y], Y < _Bar[Z], Z < _Baz[X]] () -> void
end
def bar: [X < _Foo[Y], Y < _Bar[Z], Z < _Baz[X]] () -> void
end
class B
end
RBS

assert_raises SystemExit do
Expand Down Expand Up @@ -432,6 +435,121 @@ def bar: [X < _Foo[Y], Y < _Bar[Z], Z < _Baz[X]] () -> void
end
end

def test_validate__generics_default
with_cli do |cli|
Dir.mktmpdir do |dir|
(Pathname(dir) + 'a.rbs').write(<<~RBS)
module A[T = Integer]
end
class B[S = String]
end
interface _C[T = Symbol]
end
class Foo[T = A]
type t = A
def foo: () -> A
def self.bar: () -> B
end
class Bar < B
include A
extend A
include _C
extend _C
end
module Baz : A, _C
end
RBS

cli.run(["-I", dir, "validate"])
end
end
end

def test_validate__generics_default2
with_cli do |cli|
Dir.mktmpdir do |dir|
dir = Pathname(dir)

(dir + 'a.rbs').write(<<~RBS)
module A[T = Integer]
end
class B[S = String]
end
interface _C[T = Symbol]
end
RBS

(dir + "x0.rbs").write(<<~RBS)
class X0[T = A[Symbol, Symbol]]
end
RBS

(dir + "x1.rbs").write(<<~RBS)
class X1 < B[String, untyped]
end
RBS

(dir + "x2.rbs").write(<<~RBS)
class X2
include A[String, untyped]
end
RBS

(dir + "x3.rbs").write(<<~RBS)
class X3
include _C[String, untyped]
end
RBS

(dir + "x4.rbs").write(<<~RBS)
class X4
prepend A[String, untyped]
end
RBS

(dir + "x5.rbs").write(<<~RBS)
class X5
extend A[String, untyped]
end
RBS

(dir + "x6.rbs").write(<<~RBS)
class X6
extend _C[String, untyped]
end
RBS

(dir + "x7.rbs").write(<<~RBS)
module X7 : A[String, untyped]
end
RBS


assert_raises SystemExit do
cli.run(["-I", dir.to_s, "validate"])
end

assert_include stdout.string, "/x0.rbs:1:13...1:30: ::A expects parameters [T = ::Integer], but given args [::Symbol, ::Symbol] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x1.rbs:1:11...1:29: ::B expects parameters [S = ::String], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x2.rbs:2:2...2:28: ::A expects parameters [T = ::Integer], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x3.rbs:2:2...2:29: ::_C expects parameters [T = ::Symbol], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x4.rbs:2:2...2:28: ::A expects parameters [T = ::Integer], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x5.rbs:2:2...2:27: ::A expects parameters [T = ::Integer], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x6.rbs:2:2...2:28: ::_C expects parameters [T = ::Symbol], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
assert_include stdout.string, "/x7.rbs:1:12...1:30: ::A expects parameters [T = ::Integer], but given args [::String, untyped] (RBS::InvalidTypeApplicationError)"
end
end
end

def test_validate_multiple
with_cli do |cli|
Dir.mktmpdir do |dir|
Expand Down
26 changes: 26 additions & 0 deletions test/validator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,30 @@ class Baz = Numeric
end
end
end

def test_validate__generics_default
SignatureManager.new do |manager|
manager.add_file("foo.rbs", <<-EOF)
class A[T = String]
end
class Foo
def foo: () -> A
end
EOF

manager.build do |env|
root = nil

resolver = RBS::Resolver::TypeNameResolver.new(env)
validator = RBS::Validator.new(env: env, resolver: resolver)

validator.validate_type(parse_type("::A"), context: root)
validator.validate_type(parse_type("::A[Integer]"), context: root)
assert_raises(RBS::InvalidTypeApplicationError) do
validator.validate_type(parse_type("::A[Integer, untyped]"), context: root)
end
end
end
end
end

0 comments on commit 2a6e27b

Please sign in to comment.