[ty] Implement lsp support for string annotations#21577
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
This comment was marked as resolved.
This comment was marked as resolved.
|
| // to look up a node in the AST to prevent panics, because these sub-AST nodes | ||
| // are not in the File's AST! | ||
| let source = source_text(self.db, self.file); | ||
| let string_literal = string_expr.as_single_part_string()?; |
There was a problem hiding this comment.
Pre-empting Micha: yes this does fail for various things but the type checker also does this so it's fine
e2862e6 to
0ab5389
Compare
| // The type checker knows the type of the full annotation but nothing else | ||
| if AnyNodeRef::from(&*submod.body) == subnode { | ||
| string_expr.inferred_type(model) | ||
| } else { | ||
| // TODO: force the typechecker to tell us its secrets | ||
| // (it computes but then immediately discards these types) | ||
| return None; | ||
| } |
There was a problem hiding this comment.
This is like, the one caveat of this design. Fixing this would require awful hacks or more non-trivial changes to inference.
There was a problem hiding this comment.
(awful hacks would be like... looking up types by-name)
There was a problem hiding this comment.
Okay, maybe it doesn't do yet what I thought it would.
Could we change ExpressionNodeKey to be an enum:
enum ExpressionNodeKey {
Primary(NodeKey),
StringifiedAnnotation(Box<{ parent: NodeKey, inner: NodeKey }>)
}(It might even ahve to be a ThinVec if stringified annotationos can be nested multiple levels.
We could then start storing the inferred expressions in type inference and look them up here
Happy to leave this as a follow up
There was a problem hiding this comment.
They cannot be nested, thankfully.
I agree this would be great, are NodeKeys this stable?
(Definitely followup material since it could have more non-trivial perf impact)
There was a problem hiding this comment.
NodeKey's are stable for as long as the source text doesn't change. We simply iterate over the AST and assign an index to each node (see parsed_module)
|
I want to make it clear that this is a "high risk" change in that any bugs in this could easily lead to LSP panics. However we could potentially have this functionality toggled behind a feature flag (literally just make |
|
The new string-annotations tracking in inference looks good to me! Didn't review the rest. |
|
I'm reasonably confident with the rest of the code. If we wanna get this into the release tomorrow this would be fine to merge without further review (but I'll wait for review for now). |
0ab5389 to
108ab4b
Compare
MichaReiser
left a comment
There was a problem hiding this comment.
This is a huge lift and is great. I think it might even be possible (without too much work), to start storing types for the individual expression parts, see my inline comment. But let's do this in a separate PR
| pub fn node_in_ast<'a>(&'a self, node: ast::AnyNodeRef<'a>) -> ast::AnyNodeRef<'a> { | ||
| if let Some(string_annotation) = &self.in_string_annotation_expr { | ||
| (&**string_annotation).into() | ||
| } else { | ||
| node | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
I'm not sure I fully understand those methods and when I'm supposed to use them.
Like in what sense are they safe where the in-stringified-annotation nodes aren't? When should I use this method over using the ast node directly?
There was a problem hiding this comment.
The rough heuristic in my mind is "if the node might be handed to the inference subsystem (i.e. trying to query it's scope or type)" (which SemanticModel gatekeeps). If you ever hand these sub-AST nodes to the inference subsystem it will immediately panic because the node isn't part of the file's AST.
| // to look up a node in the AST to prevent panics, because these sub-AST nodes | ||
| // are not in the File's AST! | ||
| let source = source_text(self.db, self.file); | ||
| let string_literal = string_expr.as_single_part_string()?; |
| let file_scope = index.expression_scope_id(&model.expr_ref_in_ast(*self)); | ||
| let scope = file_scope.to_scope_id(model.db, model.file); | ||
|
|
||
| infer_scope_types(model.db, scope).expression_type(*self) |
There was a problem hiding this comment.
So just for my understanding. We now store the expression types of each expression in a stringified literal within the string-literal's scope?
This is cool!
| // The type checker knows the type of the full annotation but nothing else | ||
| if AnyNodeRef::from(&*submod.body) == subnode { | ||
| string_expr.inferred_type(model) | ||
| } else { | ||
| // TODO: force the typechecker to tell us its secrets | ||
| // (it computes but then immediately discards these types) | ||
| return None; | ||
| } |
There was a problem hiding this comment.
Okay, maybe it doesn't do yet what I thought it would.
Could we change ExpressionNodeKey to be an enum:
enum ExpressionNodeKey {
Primary(NodeKey),
StringifiedAnnotation(Box<{ parent: NodeKey, inner: NodeKey }>)
}(It might even ahve to be a ThinVec if stringified annotationos can be nested multiple levels.
We could then start storing the inferred expressions in type inference and look them up here
Happy to leave this as a follow up
* main: [ty] Implement `typing.override` (astral-sh#21627) [ty] Avoid expression reinference for diagnostics (astral-sh#21267) [ty] Improve autocomplete suppressions of keywords in variable bindings [ty] Only suggest completions based on text before the cursor Implement goto-definition and find-references for global/nonlocal statements (astral-sh#21616) [ty] Inlay Hint edit follow up (astral-sh#21621) [ty] Implement lsp support for string annotations (astral-sh#21577) [ty] Add 'remove unused ignore comment' code action (astral-sh#21582) [ty] Refactor `CheckSuppressionContext` to use `DiagnosticGuard` (astral-sh#21587) [ty] Improve several "Did you mean?" suggestions (astral-sh#21597) [ty] Add more and update existing projects in `ty_benchmark` (astral-sh#21536) [ty] fix ty playground initialization and vite optimization issues (astral-sh#21471)
Fixes astral-sh/ty#1009
Summary
This adds support for:
There are 3 major things being introduced here:
TypeInferenceBuilder::string_annotationsis aFxHashSetof exprs which were determined to be string annotations during inference. It's bubbled up inextrasto hopefully minimize the overhead as in most contexts it's empty.SemanticModel::enter_string_annotationchecks if the expr was marked byTypeInferenceBuilder::string_annotationsand then parses the subast and produces a sub-SemanticModel that setsSemanticModel::in_string_annotation_expr. This expr will be used by the model whenever we need to query e.g. the scope of the current expression (otherwise the code will constantly panic as the subast nodes are not in the current File's AST)in_string_annotation_exprset).GotoTarget::StringAnnotationSubexpr(and a semantic-tokens impl) which involves invokingSemanticModel::enter_string_annotationbefore invoking the same kind of subroutine a normal expression would."int"but not the parts of"int | str"). This is shippable IMO.Test Plan
Messed around in IDE, wrote a ton of tests.