Repository navigation
feat: complete and check the values of declared types - #13
Merged
Merged
Conversation
A value whose type lists what it can be, such as `---@alias State "busy"|"working"|"ready"`, got no suggestions outside a call: nothing opened after `local state = ` under a `---@type State`, and Ctrl+Space there returned nothing. The values are now offered where such a value starts on its line: - after the `=` of a `local` or an assignment, from the `---@type` above it or from what the assigned local or class field is declared as, - after the `=` of a field in a table typed as a class, from its `@field`, - after `return`, from the `@return` of the function, - after `==` and `~=`, from what the other side is declared as. Strings are written in quotes, and integers such as those of `1|2|3` as they are. A `boolean` lists `true` and `false`, and a type that lists values also lists the `nil` it allows, last, so `State?` ends with `nil` while `string?` lists nothing. Where a function is stored or returned, snippet clients get the function literal that arguments already get, with the parameters the `fun(...)` type names. Inside a string in one of those places, or on either side of the comparison, the string values replace the string's contents. A typed word filters the values, beside the names in scope. A local typed by `---@type` or `@param` keeps that type where it is assigned again. Types inferred from assigned values list nothing, since `local mode = 'dev'` tells what is stored today, and nothing is offered in front of a value that is already written. `=` and space are completion trigger characters for these items only: a request triggered by one of them returns nothing else, so no other space opens a list. Call arguments list integers, booleans and `nil` the same way. A typed `(` or `,` still opens the list only for a parameter that lists a string or an integer; the `true` and `false` of the many `boolean` parameters are listed on request or once a word is typed. Call snippets keep their quotes for parameters that list strings.
A `---@type` line that lists several types, as `---@type boolean, string?` does above `local ok, err = pcall(...)`, was read up to its first comma, and that one type was given to every name of the statement: `err` was a `boolean`. The line now types the names one by one, and a name past the end of the list keeps the type inferred from its value. One type still covers every name. Hovers, the values completion offers, the class a table constructor is checked against and `impossible-comparison` follow the type of the name at hand.
`assign-type-mismatch` only covered the fields of class tables, so a
variable took any value whatever its annotation said:
---@alias State 1 | 2 | 3 | false | true
---@type State
local state = "test"
state = "abc"
Both values are now reported, as "Cannot assign `"test"` to `state` of
type `State`". The rule checks:
- the values of a `local` or an assignment with a `---@type` above it,
- later assignments to a local declared with `---@type`,
- later assignments to a parameter documented with `@param`.
A mismatch is what it is for `return-type-mismatch`: a different kind
of value, or a literal the type does not list. Values whose type is not
known pass. A call at the end of the values gives each name the value
it returns at that position.
A local is compared with the type it is declared with, not with what
the guards around the assignment narrow it to, so `name = "x"` inside
`if not name then` passes for a `string?`. `= nil` needs a type that
allows it, while a `local` declared without a value is not reported.
Locals without an annotation take any value, since their type is
inferred from what they hold, and so do globals in assignments that
have no `---@type` above them. A value that a class field rejects is
reported once, for the field.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Three commits that make the server use the type a value is declared with: completion offers the values the type lists, and
assign-type-mismatchreports values the type does not take.Completion of the values a declared type lists
A value whose type lists what it can be, such as
---@alias State "busy"|"working"|"ready", got no suggestions outside a call. The values are now offered where such a value starts:=of alocalor an assignment, from the---@typeabove it or from what the assigned local or class field is declared as,=of a field in a table typed as a class, from its@field,return, from the@returnof the function,==and~=, from what the other side is declared as.Details:
trueandfalsefor aboolean, and thenila listing type allows, last.string?lists nothing.fun(...)type names.=and space are trigger characters for these items only; a request triggered by one of them returns nothing else.nilare listed the same way. A typed(or,still opens the list only for a parameter that lists a string or an integer.---@typelistsA
---@typeline that lists several types, as---@type boolean, string?does abovelocal ok, err = pcall(...), was read up to its first comma, and that one type was given to every name:errwas aboolean. The line now types the names one by one, and a name past the end of the list keeps its inferred type. One type still covers every name.Hovers, the completion above, the class a table constructor is checked against and
impossible-comparisonfollow the type of the name at hand.assign-type-mismatchfor typed variablesThe rule only covered the fields of class tables, so a variable took any value whatever its annotation said. It now also checks variables whose type is declared:
The rule checks:
localor an assignment with a---@typeabove it,---@type,@param.A mismatch is what it is for
return-type-mismatch: a different kind of value, or a literal the type does not list. Values whose type is not known pass.name = "x"insideif not name thenpasses for astring?.nil:= nilneeds a type that allows it; alocaldeclared without a value is not reported.---@typeabove them.Docs
docs/reference.mdhas a new "Typed variables" section.docs/rules.md, the rule description, the server README andcrates/qbx_lua_ls/docs/protocol.mdcover the new completion and the wider rule.Checks
cargo fmt --all -- --checkandcargo clippy --workspace --all-targets: clean.cargo test --workspace: passes at the head of the branch. The---@typelist commit also passes theqbx_luacatsandqbx_lua_lstests on its own.values_offer_what_their_declared_type_listsandvalues_assigned_to_typed_variables_are_checked, and a parser test for---@typelists.nilstored under a non-optional---@type. One is inqbx_medical/client/main.lua:51(---@type numberaboveHp = nil); the other is in a private resource.Stateexample above against a release build of the server over stdio.🤖 Generated with Claude Code