Steep provides several templates to configure diagnostics for Ruby code.
You can use these templates or customize them to suit your needs via #configure_code_diagnostics
method in Steepfile
.
The following templates are available:
Ruby.all_error
- This template reports everything as an error.
Ruby.default
- This template detects inconsistencies between RBS and Ruby code APIs.
Ruby.lenient
- This template detects inconsistent definition in Ruby code with respect to your RBS definition.
Ruby.silent
- This template reports nothing.
Ruby.strict
- This template helps you keeping your codebase (almost) type-safe.
You can start with this template to review the problems reported on the project, and you can ignore some kind of errors.
A type annotation has a syntax error.
# @type var foo: () ->
test.rb:1:2: [error] Type annotation has a syntax error: Syntax error caused by token `pEOF`
│ Diagnostic ID: Ruby::AnnotationSyntaxError
│
└ # @type method foo: () ->
~~~~~~~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | error | - |
A method call has an argument that has an incompatible type to the type of the parameter.
'1' + 1
test.rb:1:6: [error] Cannot pass a value of type `::Integer` as an argument of type `::string`
│ ::Integer <: ::string
│ ::Integer <: (::String | ::_ToStr)
│ ::Integer <: ::String
│ ::Numeric <: ::String
│ ::Object <: ::String
│ ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ '1' + 1
~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
The type of the block body is incompatible with the expected type.
class Foo
def foo: () { () -> Integer } -> void
end
Foo.new.foo { "" }
test.rb:1:12: [warning] Cannot allow block body have type `::String` because declared as type `::Integer`
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::BlockBodyTypeMismatch
│
└ Foo.new.foo { "" }
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | information | - |
A method call passes an object as a block, but the type is incompatible with the method type.
multi = ->(x, y) { x * y } #: ^(Integer, Integer) -> Integer
[1, 2, 3].map(&multi)
test.rb:2:14: [error] Cannot pass a value of type `^(::Integer, ::Integer) -> ::Integer` as a block-pass-argument of type `^(::Integer) -> U(1)`
│ ^(::Integer, ::Integer) -> ::Integer <: ^(::Integer) -> U(1)
│ (Params are incompatible)
│
│ Diagnostic ID: Ruby::BlockTypeMismatch
│
└ [1, 2, 3].map(&multi)
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | information | - |
A break
statement has a value that has an incompatible type to the type of the destination.
123.tap { break "" }
test.rb:1:10: [error] Cannot break with a value of type `::String` because type `::Integer` is assumed
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::BreakTypeMismatch
│
└ 123.tap { break "" }
~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | hint | - |
A class (or module) definition in Ruby code has a module (or class) in RBS.
module Object
end
class Kernel
end
test.rb:1:7: [error] ::Object is declared as a class in RBS
│ Diagnostic ID: Ruby::ClassModuleMismatch
│
└ module Object
~~~~~~
test.rb:4:6: [error] ::Kernel is declared as a module in RBS
│ Diagnostic ID: Ruby::ClassModuleMismatch
│
└ class Kernel
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | - | - |
The method has a parameter with different kind from the RBS definition.
class Foo
def foo: (String?) -> void
end
class Foo
def foo(x=nil)
end
end
test.rb:2:10: [hint] The method parameter has different kind from the declaration `((::String | nil)) -> void`
│ Diagnostic ID: Ruby::DifferentMethodParameterKind
│
└ def foo(x=nil)
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
Unable to determine the type of an expression for any reason.
@foo
test.rb:1:0: [error] Cannot detect the type of the expression
│ Diagnostic ID: Ruby::FallbackAny
│
└ @foo
~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | hint | - | - |
The type assertion cannot hold.
array = [] #: Array[Integer]
hash = array #: Hash[Symbol, String]
test.rb:2:7: [error] Assertion cannot hold: no relationship between inferred type (`::Array[::Integer]`) and asserted type (`::Hash[::Symbol, ::String]`)
│ Diagnostic ID: Ruby::FalseAssertion
│
└ hash = array #: Hash[Symbol, String]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
A break
statement without a value is used to leave from a block that requires non-nil type.
123.tap { break }
test.rb:1:10: [error] Breaking without a value may result an error because a value of type `::Integer` is expected
│ nil <: ::Integer
│
│ Diagnostic ID: Ruby::ImplicitBreakValueMismatch
│
└ 123.tap { break }
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | - | - |
Detected a branch local annotation is incompatible with outer context.
a = [1,2,3]
if _ = 1
# @type var a: String
a + ""
end
test.rb:5:2: [error] Type annotation about `a` is incompatible since ::String <: ::Array[::Integer] doesn't hold
│ ::String <: ::Array[::Integer]
│ ::Object <: ::Array[::Integer]
│ ::BasicObject <: ::Array[::Integer]
│
│ Diagnostic ID: Ruby::IncompatibleAnnotation
│
└ a + ""
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
Argument forwarding ...
cannot be done safely, because of:
- The arguments are incompatible, or
- The blocks are incompatible
class Foo
def foo: (*Integer) -> void
def bar: (*String) -> void
end
class Foo
def foo(*args)
end
def bar(...)
foo(...)
end
end
test.rb:8:8: [error] Cannot forward arguments to `foo`:
│ (*::Integer) <: (*::String)
│ ::String <: ::Integer
│ ::Object <: ::Integer
│
│ Diagnostic ID: Ruby::IncompatibleArgumentForwarding
│
└ foo(...)
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | information | - |
An assignment has a right hand side value that has an incompatible type to the type of the left hand side.
# @type var x: Integer
x = "string"
test.rb:2:0: [error] Cannot assign a value of type `::String` to a variable of type `::Integer`
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::IncompatibleAssignment
│
└ x = "string"
~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | hint | - |
A method call needs more keyword arguments.
class Foo
def foo: (a: untyped, b: untyped) -> void
end
Foo.new.foo(a: 1)
test.rb:5:8: [error] More keyword arguments are required: b
│ Diagnostic ID: Ruby::InsufficientKeywordArguments
│
└ Foo.new.foo(a: 1)
~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
An method call needs more positional arguments.
class Foo
def foo: (a, b) -> void
end
Foo.new.foo(1)
test.rb:1:8: [error] More positional arguments are required
│ Diagnostic ID: Ruby::InsufficientPositionalArguments
│
└ Foo.new.foo(1)
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
A type application needs more type arguments.
class Foo
def foo: [T, S] (T, S) -> [T, S]
end
Foo.new.foo(1, 2) #$ Integer
test.rb:8:0: [error] Requires 2 types, but 1 given: `[T, S] (T, S) -> [T, S]`
│ Diagnostic ID: Ruby::InsufficientTypeArgument
│
└ Foo.new.foo(1, 2) #$ Integer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
steep:ignore
comment is invalid.
# steep:ignore:start
test.rb:1:0: [error] Invalid ignore comment
│ Diagnostic ID: Ruby::InvalidIgnoreComment
│
└ # steep:ignore:start
~~~~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | warning | warning | - |
The method definition has missing parameters with respect to the RBS definition.
class Foo
def foo: (String, String) -> void
end
class Foo
def foo(x)
end
end
test.rb:2:9: [error] Method parameters are incompatible with declaration `(::String, ::String) -> void`
│ Diagnostic ID: Ruby::MethodArityMismatch
│
└ def foo(x)
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
The type of the method body has different type from the RBS definition.
class Foo
def foo: () -> String
end
class Foo
def foo = 123
end
test.rb:2:6: [error] Cannot allow method body have type `::Integer` because declared as type `::String`
│ ::Integer <: ::String
│ ::Numeric <: ::String
│ ::Object <: ::String
│ ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::MethodBodyTypeMismatch
│
└ def foo = 123
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | warning | - |
A def
syntax doesn't have method type because the module/class is undefined in RBS.
class UndeclaredClass
def to_s = 123
end
test.rb:2:6: [error] Method `to_s` is defined in undeclared module
│ Diagnostic ID: Ruby::MethodDefinitionInUndeclaredModule
│
└ def to_s = 123
~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | information | hint | - |
The class/module definition doesn't have a def
syntax for the method.
class Foo
def foo: () -> String
end
class Foo
attr_reader :foo
end
test.rb:1:6: [hint] Cannot find implementation of method `::Foo#foo`
│ Diagnostic ID: Ruby::MethodDefinitionMissing
│
└ class Foo
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | hint | - | - | - |
The method definition has an extra parameter with respect to the RBS definition.
class Foo
def foo: (String) -> void
end
class Foo
def foo(x, y)
end
end
test.rb:2:13: [error] The method parameter is incompatible with the declaration `(::String) -> void`
│ Diagnostic ID: Ruby::MethodParameterMismatch
│
└ def foo(x, y)
~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | warning | - |
Deprecated Related to the @type method
annotation.
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
The #to_ary
of RHS of multiple assignment is called, but returns not tuple nor Array.
class Foo
def to_ary: () -> Integer
end
a, b = Foo.new()
test.rb:1:6: [error] Cannot convert `::Foo` to Array or tuple (`#to_ary` returns `::Integer`)
│ Diagnostic ID: Ruby::MultipleAssignmentConversionError
│
└ a,b = Foo.new
~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
A method call calls a method that is not defined on the receiver.
"".non_existent_method
test.rb:1:3: [error] Type `::String` does not have method `non_existent_method`
│ Diagnostic ID: Ruby::NoMethod
│
└ "".non_existent_method
~~~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
Type hint is given to a proc/lambda but it was ignored.
- Because the hint is incompatible to
::Proc
type - More than one proc type is included in the hint
# @type var proc: (^(::Integer) -> ::String) | (^(::String, ::String) -> ::Integer)
proc = -> (x) { x.to_s }
test.rb:2:7: [error] The type hint given to the block is ignored: `(^(::Integer) -> ::String | ^(::String, ::String) -> ::Integer)`
│ Diagnostic ID: Ruby::ProcHintIgnored
│
└ proc = -> (x) { x.to_s }
~~~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | - | - |
The block parameter has non-proc type.
-> (&block) do
# @type var block: Integer
end
test.rb:1:4: [error] Proc type is expected but `::Integer` is specified
│ Diagnostic ID: Ruby::ProcTypeExpected
│
└ -> (&block) do
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
RBS embedded in the Ruby code has validation error.
a = 1 #: Int
test.rb:1:9: [error] Cannot find type `::Int`
│ Diagnostic ID: Ruby::RBSError
│
└ a = 1 #: Int
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | information | information | - |
A method that requires a block is called without a block.
class Foo
def foo: { () -> void } -> void
end
Foo.new.foo
test.rb:1:8: [error] The method cannot be called without a block
│ Diagnostic ID: Ruby::RequiredBlockMissing
│
└ Foo.new.foo
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | hint | - |
A return
statement has a value that has an incompatible type to the return type of the method.
class Foo
def foo: () -> Integer
end
class Foo
def foo
return "string"
end
end
test.rb:3:2: [error] The method cannot return a value of type `::String` because declared as type `::Integer`
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::ReturnTypeMismatch
│
└ return "string"
~~~~~~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | warning | - |
Setter method, which has a name ending with =
, has different type from the method type.
This is a special diagnostic for setter methods because the return value is not used with ordinal call syntax.
class Foo
# Assume `name=` has method type of `(String) -> String`
def name=(value)
@value = value
value.strip!
end
end
test.rb:2:6: [information] Setter method `name=` cannot have type `(::String | nil)` because declared as type `::String`
│ (::String | nil) <: ::String
│ nil <: ::String
│
│ Diagnostic ID: Ruby::SetterBodyTypeMismatch
│
└ def name=(value)
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | information | - | - |
Setter method, which has a name ending with =
, returns different type from the method type.
This is a special diagnostic for setter methods because the return value is not used with ordinal call syntax.
class Foo
def name=: (String) -> String
end
class Foo
def name=(value)
return if value.empty?
@value = value
end
end
test.rb:3:4: [information] The setter method `name=` cannot return a value of type `nil` because declared as type `::String`
│ nil <: ::String
│
│ Diagnostic ID: Ruby::SetterReturnTypeMismatch
│
└ return if value.empty?
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | information | - | - |
The Ruby code has a syntax error.
if x == 1
puts "Hello"
test.rb:2:14: [error] SyntaxError: unexpected token $end
│ Diagnostic ID: Ruby::SyntaxError
│
└ puts "Hello"
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | information | information | - |
The type application doesn't satisfy generic constraints.
class Foo
def foo: [T < Numeric] (T) -> T
end
Foo.new.foo("") #$ String
test.rb:7:19: [error] Cannot pass a type `::String` as a type parameter `T < ::Numeric`
│ ::String <: ::Numeric
│ ::Object <: ::Numeric
│ ::BasicObject <: ::Numeric
│
│ Diagnostic ID: Ruby::TypeArgumentMismatchError
│
└ Foo.new.foo("") #$ String
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
An empty array/hash has no type assertion.
They are typed as Array[untyped]
or Hash[untyped, untyped]
,
which allows any element to be added.
a = []
b = {}
a << 1
a << ""
Add type annotation to make your assumption explicit.
a = [] #: Array[Integer]
b = {} #: untyped
a << 1
a << "" # => Type error
a = []
b = {}
test.rb:1:4: [error] Empty array doesn't have type annotation
│ Diagnostic ID: Ruby::UnannotatedEmptyCollection
│
└ a = []
~~
test.rb:2:4: [error] Empty hash doesn't have type annotation
│ Diagnostic ID: Ruby::UnannotatedEmptyCollection
│
└ b = {}
~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | hint | - |
A def
syntax doesn't have corresponding RBS method definition.
class Foo
end
class Foo
def undeclared = nil
end
test.rb:2:6: [error] Method `::Foo#undeclared` is not declared in RBS
│ Diagnostic ID: Ruby::UndeclaredMethodDefinition
│
└ def undeclared = nil
~~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | warning | information | - |
A method that doesn't accept block is called with a block.
class Foo
def foo: () -> void
end
Foo.new.foo { 123 }
test.rb:1:12: [warning] The method cannot be called with a block
│ Diagnostic ID: Ruby::UnexpectedBlockGiven
│
└ Foo.new.foo { 123 }
~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | hint | - |
A @dynamic
annotation has unknown method name.
Note that this diagnostic emits only if the class definition in RBS has method definitions.
class Foo
def foo: () -> void
end
class Foo
# @dynamic foo, bar
end
test.rb:1:6: [error] @dynamic annotation contains unknown method name `bar`
│ Diagnostic ID: Ruby::UnexpectedDynamicMethod
│
└ class Foo
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | - | - |
Unexpected error is raised during type checking. Maybe a bug.
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | hint | - |
Detected a break
or next
statement in invalid context.
break
test.rb:1:0: [error] Cannot jump from here
│ Diagnostic ID: Ruby::UnexpectedJump
│
└ break
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
A break
or next
statement has a value, but the value will be ignored.
while true
next 3
end
test.rb:2:2: [error] The value given to next will be ignored
│ Diagnostic ID: Ruby::UnexpectedJumpValue
│
└ next 3
~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
A method call has an extra keyword argument.
class Foo
def foo: (x: untyped) -> void
end
Foo.new.foo(x: 1, y: 2)
test.rb:7:18: [error] Unexpected keyword argument
│ Diagnostic ID: Ruby::UnexpectedKeywordArgument
│
└ Foo.new.foo(x: 1, y: 2)
~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
A method call has an extra positional argument.
class Foo
def foo: (untyped) -> void
end
Foo.new.foo(1, 2)
test.rb:7:15: [error] Unexpected positional argument
│ Diagnostic ID: Ruby::UnexpectedPositionalArgument
│
└ Foo.new.foo(1, 2)
~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
A method definition has super
syntax while no super method is defined in RBS.
class Foo
def foo: () -> void
end
class Foo
def foo = super
end
test.rb:2:12: [information] No superclass method `foo` defined
│ Diagnostic ID: Ruby::UnexpectedSuper
│
└ def foo = super
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | information | - | - |
An extra type application is given to a method call.
class Foo
def foo: [T] (T) -> T
end
Foo.new.foo(1) #$ Integer, Integer
test.rb:8:27: [error] Unexpected type arg is given to method type `[T] (T) -> T`
│ Diagnostic ID: Ruby::UnexpectedTypeArgument
│
└ Foo.new.foo(1) #$ Integer, Integer
~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | - | - |
A method definition without block has yield
syntax.
class Foo
def foo: () -> void
end
class Foo
def foo
yield
end
end
test.rb:3:4: [hint] Cannot detect the type of the expression
│ Diagnostic ID: Ruby::FallbackAny
│
└ yield
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | information | - |
A constant is not defined in the RBS definition.
FOO
test.rb:1:0: [error] Cannot find the declaration of constant: `FOO`
│ Diagnostic ID: Ruby::UnknownConstant
│
└ FOO
~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | hint | - |
Short explanation ending with .
$foo
test.rb:1:0: [error] Cannot find the declaration of global variable: `$foo`
│ Diagnostic ID: Ruby::UnknownGlobalVariable
│
└ $foo
~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | warning | hint | - |
An instance variable is not defined in RBS definition.
class Foo
def foo
@foo = 'foo'
end
end
test.rb:3:4: [error] Cannot find the declaration of instance variable: `@foo`
│ Diagnostic ID: Ruby::UnknownInstanceVariable
│
└ @foo = 'foo'
~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | information | hint | - |
An unknown key is given to record type.
{ name: "soutaro", email: "[email protected]" } #: { name: String }
test.rb:1:19: [error] Unknown key `:email` is given to a record type
│ Diagnostic ID: Ruby::UnknownRecordKey
│
└ { name: "soutaro", email: "[email protected]" } #: { name: String }
~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | information | hint | - |
A conditional always/never hold.
if false
1
end
test.rb:1:0: [error] The branch is unreachable
│ Diagnostic ID: Ruby::UnreachableBranch
│
└ if false
~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | hint | - |
A branch has a type other than bot
, but unreachable.
This diagnostic skips the bot
branch because we often have else
branch to make the code defensive.
x = 1
case x
when Integer
"one"
when String
"two"
when Symbol
raise "Unexpected value"
end
test.rb:5:0: [error] The branch may evaluate to a value of `::String` but unreachable
│ Diagnostic ID: Ruby::UnreachableValueBranch
│
└ when String
~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | warning | hint | hint | - |
A method call has type errors, no more specific explanation cannot be reported.
3 + "foo"
test.rb:1:0: [error] Cannot find compatible overloading of method `+` of type `::Integer`
│ Method types:
│ def +: (::Integer) -> ::Integer
│ | (::Float) -> ::Float
│ | (::Rational) -> ::Rational
│ | (::Complex) -> ::Complex
│
│ Diagnostic ID: Ruby::UnresolvedOverloading
│
└ 3 + "foo"
~~~~~~~~~
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | error | information | - |
Failed to solve constraint collected from a method call typing.
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | error | hint | hint | - |
The syntax is not currently supported by Steep.
all_error | strict | default | lenient | silent |
---|---|---|---|---|
error | information | hint | hint | - |