Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static type checker #192

Closed
castwide opened this issue Jun 18, 2019 · 14 comments
Closed

Static type checker #192

castwide opened this issue Jun 18, 2019 · 14 comments

Comments

@castwide
Copy link
Owner

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.

@thomthom
Copy link

The output will list all methods that have missing or invalid @return and @param tags.

This checks if there is a type defined for params and return tags in the doc-comments? But it doesn't Analyse code paths to see if incorrect type might be passed to the methods?

@castwide
Copy link
Owner Author

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')

@castwide
Copy link
Owner Author

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.

@castwide
Copy link
Owner Author

castwide commented Jan 20, 2020

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 and Hash parameters go largely ignored in validation. This means that Array, Array<String>, and Array<Integer> would all be considered the same type for validation purposes. Validation of type parameters is a possibility in the future.

@taylorthurlow
Copy link

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!

@thomthom
Copy link

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.

@castwide
Copy link
Owner Author

castwide commented Jan 27, 2020

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.

@SolaWing
Copy link
Contributor

SolaWing commented Feb 4, 2020

@castwide

        # @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

@SolaWing
Copy link
Contributor

SolaWing commented Feb 6, 2020

@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

@castwide
Copy link
Owner Author

castwide commented Feb 7, 2020

@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.

@castwide
Copy link
Owner Author

castwide commented Feb 7, 2020

Also, regarding your Dir.chdir example:

  1. The type checker should definitely recognize that Array is a subclass of Object. I'll look into why that example doesn't work.
  2. I'm working on a mechanism to include the results of yielded blocks in type inference.

@castwide
Copy link
Owner Author

castwide commented Feb 9, 2020

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.

@castwide
Copy link
Owner Author

castwide commented Apr 26, 2020

Released in v0.39.0.

More information: https://solargraph.org/guides/type-checking

@SolaWing
Copy link
Contributor

SolaWing commented Sep 7, 2021

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.

@castwide castwide unpinned this issue Nov 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants