Static type checker
castwide opened this issue · comments
Gem version 0.33.0 introduces the first version of the static type checker. You can use it from the command line or add it to your editor's diagnostics.
Command Line
Run it from the command line:
cd /path/to/project
solargraph typecheck
The output will list all methods that have missing or invalid @return
and @param
tags.
To check a specific file only, use solargraph typecheck ./file.rb
. (The type checker will still map the rest of the workspace for better accuracy.)
The optional --strict
argument uses static code analysis to ensure that methods and params are tagged with the correct types. This option is highly experimental. You can expect a lot of false positives and potential bugs.
Language Server Diagnostics
You can get typecheck diagnostics from the language server with the typecheck
reporter. Enable it by adding it to your workspace's .solargraph.yml:
reporters:
- typecheck
For strict type checking (warning: expect bugs):
reporters:
- typecheck:strict
This is very much a work in progress. Any feedback is appreciated.
The normal type check only checks @return
and @param
tags, but strict type checks analyze code paths.
class Example
# Type check is validated here; both @param and @return have resolvable types
# @param num [Integer]
# @return [self]
def use_integer(num)
self
end
end
# This method call will report an error in strict type checks (Integer expected)
Example.new.use_integer('string')
Gem 0.34.0 includes some updates to the type checker, including support for validating overload tags and preliminary support for validating duck types in strict mode.
These are the proposed levels for type checking:
Normal
@return
and@param
tags are optional- Resolve namespaces in all type tags
- Ignore all undefined types
Typed
@return
and@param
tags are optional- Resolve namespaces in all type tags
- Validate existing
@return
tags against inferred types - Validate existing
@type
tags against inferred types - Validate existing
@param
tags against arguments - Loose
@return
tag matches - Ignore all undefined types
Strict
@param
tags are optional- All methods must have either a
@return
tag or an inferred type - Resolve namespaces in all type tags
- Validate existing
@return
tags against inferred types - Validate existing
@param
tags against arguments - Validate existing
@type
tags against inferred types - Strict
@return
tag matches - Validate method calls
- Ignore undefined types from external sources
Strong
@return
and@param
tags are required- Resolve namespaces in all type tags
- Validate
@return
tags against inferred types - Validate
@param
tags against arguments - Validate existing
@type
tags against inferred types - Strict
@return
tag matches - Validate method calls
- Ignore undefined types from external sources
Other notes:
- Handling undefined types should be similar to handling
Any
in TypeScript. Every level is capable of allowing undefined types with varying degrees of strictness. - Ignoring undefined types from external sources ensures that type checking does not report errors from untyped vendor code, such as gem dependencies.
- A possible level higher than "strong" could validate types from external sources as well.
Array
andHash
parameters go largely ignored in validation. This means thatArray
,Array<String>
, andArray<Integer>
would all be considered the same type for validation purposes. Validation of type parameters is a possibility in the future.
A possible level higher than "strong" could validate types from external sources as well.
Call it "unreasonable", haha.
Glad to see continued progress on this!
A possible level higher than "strong" could validate types from external sources as well.
That could be useful for API stubs. We generate API stubs - a lot of it have @param
and @return
types, but the code base predated YARD so we're still backfilling. But the stubs also have no content in the methods, so little to infer from the code.
The v0.39
branch includes the current version of the type checker.
To run it from the command line, use solargraph typecheck
. The --level
option sets the check level.
To test diagnostics in the language server, add typecheck
to the reporters
section in .solargraph.yml
:
reporters:
- typecheck
You can also add an argument for the check level, e.g., typecheck:typed
or typecheck:strict
.
# @type [Array]
resources = Dir.chdir(path) do # error: Declared type Array does not match inferred type Object for variable resources
Dir['**/*'].reject(&::File.method(:directory?))
end
should support cast annotation from superclass to subclass.
and the Dir.chdir block return the value block returns, the infer can work better
@castwide how to silence the infer error? check level is strict
module A
# @return [Hash{String => Hash, Array}]
def index # A#index return type could not be inferred
@index ||= begin
recur = lambda do |i, resources|
table = {}
table[i] = recur[i + 1, resources]
table
end
recur[0, @resources]
end
end
end
@SolaWing There's no way to silence specific errors. That might be a good feature to add. For now the only workaround is to reduce the check level. Typed doesn't report errors for uninferred return types.
Also, regarding your Dir.chdir
example:
- The type checker should definitely recognize that
Array
is a subclass ofObject
. I'll look into why that example doesn't work. - I'm working on a mechanism to include the results of yielded blocks in type inference.
The type checker now handles inheritance in either direction when checking tagged vs. inferred types. Array -> Object
and Object -> Array
are both valid; Array -> String
is not.
Released in v0.39.0.
More information: https://solargraph.org/guides/type-checking
since the checker supported upcast and downcast, add a Object type to declare type, can suppress the wrong infer or wrong extern annotation type.
just for someone what strict check but struggle with typechecker.