Skip to content

Commit fbeeb05

Browse files
authored
[ty] Don't show hover for expressions with no inferred type (#21924)
1 parent 4fdb4e8 commit fbeeb05

File tree

7 files changed

+78
-49
lines changed

7 files changed

+78
-49
lines changed

crates/ty_ide/src/goto.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ impl<'db> Definitions<'db> {
295295

296296
impl GotoTarget<'_> {
297297
pub(crate) fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
298-
let ty = match self {
298+
match self {
299299
GotoTarget::Expression(expression) => expression.inferred_type(model),
300300
GotoTarget::FunctionDef(function) => function.inferred_type(model),
301301
GotoTarget::ClassDef(class) => class.inferred_type(model),
@@ -317,7 +317,7 @@ impl GotoTarget<'_> {
317317
} => {
318318
// We don't currently support hovering the bare `.` so there is always a name
319319
let module = import_name(module_name, *component_index);
320-
model.resolve_module_type(Some(module), *level)?
320+
model.resolve_module_type(Some(module), *level)
321321
}
322322
GotoTarget::StringAnnotationSubexpr {
323323
string_expr,
@@ -334,16 +334,16 @@ impl GotoTarget<'_> {
334334
} else {
335335
// TODO: force the typechecker to tell us its secrets
336336
// (it computes but then immediately discards these types)
337-
return None;
337+
None
338338
}
339339
}
340340
GotoTarget::BinOp { expression, .. } => {
341341
let (_, ty) = ty_python_semantic::definitions_for_bin_op(model, expression)?;
342-
ty
342+
Some(ty)
343343
}
344344
GotoTarget::UnaryOp { expression, .. } => {
345345
let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?;
346-
ty
346+
Some(ty)
347347
}
348348
// TODO: Support identifier targets
349349
GotoTarget::PatternMatchRest(_)
@@ -353,10 +353,8 @@ impl GotoTarget<'_> {
353353
| GotoTarget::TypeParamParamSpecName(_)
354354
| GotoTarget::TypeParamTypeVarTupleName(_)
355355
| GotoTarget::NonLocal { .. }
356-
| GotoTarget::Globals { .. } => return None,
357-
};
358-
359-
Some(ty)
356+
| GotoTarget::Globals { .. } => None,
357+
}
360358
}
361359

362360
/// Try to get a simplified display of this callable type by resolving overloads

crates/ty_ide/src/hover.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3610,6 +3610,20 @@ def function():
36103610
");
36113611
}
36123612

3613+
#[test]
3614+
fn hover_tuple_assignment_target() {
3615+
let test = CursorTest::builder()
3616+
.source(
3617+
"test.py",
3618+
r#"
3619+
(x, y)<CURSOR> = "test", 10
3620+
"#,
3621+
)
3622+
.build();
3623+
3624+
assert_snapshot!(test.hover(), @"Hover provided no content");
3625+
}
3626+
36133627
impl CursorTest {
36143628
fn hover(&self) -> String {
36153629
use std::fmt::Write;

crates/ty_ide/src/inlay_hints.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,17 +362,19 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> {
362362
Expr::Name(name) => {
363363
if let Some(rhs) = self.assignment_rhs {
364364
if name.ctx.is_store() {
365-
let ty = expr.inferred_type(&self.model);
366-
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
365+
if let Some(ty) = expr.inferred_type(&self.model) {
366+
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
367+
}
367368
}
368369
}
369370
source_order::walk_expr(self, expr);
370371
}
371372
Expr::Attribute(attribute) => {
372373
if let Some(rhs) = self.assignment_rhs {
373374
if attribute.ctx.is_store() {
374-
let ty = expr.inferred_type(&self.model);
375-
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
375+
if let Some(ty) = expr.inferred_type(&self.model) {
376+
self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed);
377+
}
376378
}
377379
}
378380
source_order::walk_expr(self, expr);

crates/ty_ide/src/semantic_tokens.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ impl<'db> SemanticTokenVisitor<'db> {
273273
}
274274

275275
// Fall back to type-based classification.
276-
let ty = name.inferred_type(self.model);
276+
let ty = name.inferred_type(self.model).unwrap_or(Type::unknown());
277277
let name_str = name.id.as_str();
278278
self.classify_from_type_and_name_str(ty, name_str)
279279
}
@@ -302,7 +302,9 @@ impl<'db> SemanticTokenVisitor<'db> {
302302
let parsed = parsed_module(db, definition.file(db));
303303
let ty = parameter.node(&parsed.load(db)).inferred_type(&model);
304304

305-
if let Type::TypeVar(type_var) = ty {
305+
if let Some(ty) = ty
306+
&& let Type::TypeVar(type_var) = ty
307+
{
306308
match type_var.typevar(db).kind(db) {
307309
TypeVarKind::TypingSelf => {
308310
return Some((SemanticTokenType::SelfParameter, modifiers));
@@ -344,9 +346,9 @@ impl<'db> SemanticTokenVisitor<'db> {
344346
_ => None,
345347
};
346348

347-
if let Some(value) = value {
348-
let value_ty = value.inferred_type(&model);
349-
349+
if let Some(value) = value
350+
&& let Some(value_ty) = value.inferred_type(&model)
351+
{
350352
if value_ty.is_class_literal()
351353
|| value_ty.is_subclass_of()
352354
|| value_ty.is_generic_alias()
@@ -710,12 +712,12 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
710712
for alias in &import.names {
711713
if let Some(asname) = &alias.asname {
712714
// For aliased imports (from X import Y as Z), classify Z based on what Y is
713-
let ty = alias.inferred_type(self.model);
715+
let ty = alias.inferred_type(self.model).unwrap_or(Type::unknown());
714716
let (token_type, modifiers) = self.classify_from_alias_type(ty, asname);
715717
self.add_token(asname, token_type, modifiers);
716718
} else {
717719
// For direct imports (from X import Y), use semantic classification
718-
let ty = alias.inferred_type(self.model);
720+
let ty = alias.inferred_type(self.model).unwrap_or(Type::unknown());
719721
let (token_type, modifiers) =
720722
self.classify_from_alias_type(ty, &alias.name);
721723
self.add_token(&alias.name, token_type, modifiers);
@@ -835,7 +837,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
835837
self.visit_expr(&attr.value);
836838

837839
// Then add token for the attribute name (e.g., 'path' in 'os.path')
838-
let ty = expr.inferred_type(self.model);
840+
let ty = expr.inferred_type(self.model).unwrap_or(Type::unknown());
839841
let (token_type, modifiers) =
840842
Self::classify_from_type_for_attribute(ty, &attr.attr);
841843
self.add_token(&attr.attr, token_type, modifiers);

crates/ty_python_semantic/src/semantic_model.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ impl<'db> SemanticModel<'db> {
196196

197197
/// Returns completions for symbols available in a `object.<CURSOR>` context.
198198
pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec<Completion<'db>> {
199-
let ty = node.value.inferred_type(self);
199+
let Some(ty) = node.value.inferred_type(self) else {
200+
return Vec::new();
201+
};
202+
200203
all_members(self.db, ty)
201204
.into_iter()
202205
.map(|member| Completion {
@@ -400,7 +403,7 @@ pub trait HasType {
400403
///
401404
/// ## Panics
402405
/// May panic if `self` is from another file than `model`.
403-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db>;
406+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>>;
404407
}
405408

406409
pub trait HasDefinition {
@@ -412,26 +415,24 @@ pub trait HasDefinition {
412415
}
413416

414417
impl HasType for ast::ExprRef<'_> {
415-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
418+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
416419
let index = semantic_index(model.db, model.file);
417420
// TODO(#1637): semantic tokens is making this crash even with
418421
// `try_expr_ref_in_ast` guarding this, for now just use `try_expression_scope_id`.
419422
// The problematic input is `x: "float` (with a dangling quote). I imagine the issue
420423
// is we're too eagerly setting `is_string_annotation` in inference.
421-
let Some(file_scope) = index.try_expression_scope_id(&model.expr_ref_in_ast(*self)) else {
422-
return Type::unknown();
423-
};
424+
let file_scope = index.try_expression_scope_id(&model.expr_ref_in_ast(*self))?;
424425
let scope = file_scope.to_scope_id(model.db, model.file);
425426

426-
infer_scope_types(model.db, scope).expression_type(*self)
427+
infer_scope_types(model.db, scope).try_expression_type(*self)
427428
}
428429
}
429430

430431
macro_rules! impl_expression_has_type {
431432
($ty: ty) => {
432433
impl HasType for $ty {
433434
#[inline]
434-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
435+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
435436
let expression_ref = ExprRef::from(self);
436437
expression_ref.inferred_type(model)
437438
}
@@ -474,7 +475,7 @@ impl_expression_has_type!(ast::ExprSlice);
474475
impl_expression_has_type!(ast::ExprIpyEscapeCommand);
475476

476477
impl HasType for ast::Expr {
477-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
478+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
478479
match self {
479480
Expr::BoolOp(inner) => inner.inferred_type(model),
480481
Expr::Named(inner) => inner.inferred_type(model),
@@ -525,9 +526,9 @@ macro_rules! impl_binding_has_ty_def {
525526

526527
impl HasType for $ty {
527528
#[inline]
528-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
529+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
529530
let binding = HasDefinition::definition(self, model);
530-
binding_type(model.db, binding)
531+
Some(binding_type(model.db, binding))
531532
}
532533
}
533534
};
@@ -541,12 +542,12 @@ impl_binding_has_ty_def!(ast::ExceptHandlerExceptHandler);
541542
impl_binding_has_ty_def!(ast::TypeParamTypeVar);
542543

543544
impl HasType for ast::Alias {
544-
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
545+
fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
545546
if &self.name == "*" {
546-
return Type::Never;
547+
return Some(Type::Never);
547548
}
548549
let index = semantic_index(model.db, model.file);
549-
binding_type(model.db, index.expect_single_definition(self))
550+
Some(binding_type(model.db, index.expect_single_definition(self)))
550551
}
551552
}
552553

@@ -584,7 +585,7 @@ mod tests {
584585

585586
let function = ast.suite()[0].as_function_def_stmt().unwrap();
586587
let model = SemanticModel::new(&db, foo);
587-
let ty = function.inferred_type(&model);
588+
let ty = function.inferred_type(&model).unwrap();
588589

589590
assert!(ty.is_function_literal());
590591

@@ -603,7 +604,7 @@ mod tests {
603604

604605
let class = ast.suite()[0].as_class_def_stmt().unwrap();
605606
let model = SemanticModel::new(&db, foo);
606-
let ty = class.inferred_type(&model);
607+
let ty = class.inferred_type(&model).unwrap();
607608

608609
assert!(ty.is_class_literal());
609610

@@ -624,7 +625,7 @@ mod tests {
624625
let import = ast.suite()[0].as_import_from_stmt().unwrap();
625626
let alias = &import.names[0];
626627
let model = SemanticModel::new(&db, bar);
627-
let ty = alias.inferred_type(&model);
628+
let ty = alias.inferred_type(&model).unwrap();
628629

629630
assert!(ty.is_class_literal());
630631

crates/ty_python_semantic/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ impl<'db> Type<'db> {
878878
Self::Dynamic(DynamicType::Any)
879879
}
880880

881-
pub(crate) const fn unknown() -> Self {
881+
pub const fn unknown() -> Self {
882882
Self::Dynamic(DynamicType::Unknown)
883883
}
884884

crates/ty_python_semantic/src/types/ide_support.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ pub fn definitions_for_name<'db>(
155155
// https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
156156
if matches!(name_str, "float" | "complex")
157157
&& let Some(expr) = node.expr_name()
158-
&& let Some(union) = expr.inferred_type(&SemanticModel::new(db, file)).as_union()
158+
&& let Some(ty) = expr.inferred_type(model)
159+
&& let Some(union) = ty.as_union()
159160
&& is_float_or_complex_annotation(db, union, name_str)
160161
{
161162
return union
@@ -234,7 +235,10 @@ pub fn definitions_for_attribute<'db>(
234235
let mut resolved = Vec::new();
235236

236237
// Determine the type of the LHS
237-
let lhs_ty = attribute.value.inferred_type(model);
238+
let Some(lhs_ty) = attribute.value.inferred_type(model) else {
239+
return resolved;
240+
};
241+
238242
let tys = match lhs_ty {
239243
Type::Union(union) => union.elements(model.db()).to_vec(),
240244
_ => vec![lhs_ty],
@@ -374,7 +378,9 @@ pub fn definitions_for_keyword_argument<'db>(
374378
call_expr: &ast::ExprCall,
375379
) -> Vec<ResolvedDefinition<'db>> {
376380
let db = model.db();
377-
let func_type = call_expr.func.inferred_type(model);
381+
let Some(func_type) = call_expr.func.inferred_type(model) else {
382+
return Vec::new();
383+
};
378384

379385
let Some(keyword_name) = keyword.arg.as_ref() else {
380386
return Vec::new();
@@ -498,7 +504,9 @@ pub fn call_signature_details<'db>(
498504
model: &SemanticModel<'db>,
499505
call_expr: &ast::ExprCall,
500506
) -> Vec<CallSignatureDetails<'db>> {
501-
let func_type = call_expr.func.inferred_type(model);
507+
let Some(func_type) = call_expr.func.inferred_type(model) else {
508+
return Vec::new();
509+
};
502510

503511
// Use into_callable to handle all the complex type conversions
504512
if let Some(callable_type) = func_type
@@ -507,7 +515,9 @@ pub fn call_signature_details<'db>(
507515
{
508516
let call_arguments =
509517
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
510-
splatted_value.inferred_type(model)
518+
splatted_value
519+
.inferred_type(model)
520+
.unwrap_or(Type::unknown())
511521
});
512522
let bindings = callable_type
513523
.bindings(model.db())
@@ -564,7 +574,7 @@ pub fn call_type_simplified_by_overloads(
564574
call_expr: &ast::ExprCall,
565575
) -> Option<String> {
566576
let db = model.db();
567-
let func_type = call_expr.func.inferred_type(model);
577+
let func_type = call_expr.func.inferred_type(model)?;
568578

569579
// Use into_callable to handle all the complex type conversions
570580
let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db);
@@ -579,7 +589,9 @@ pub fn call_type_simplified_by_overloads(
579589

580590
// Hand the overload resolution system as much type info as we have
581591
let args = CallArguments::from_arguments_typed(&call_expr.arguments, |_, splatted_value| {
582-
splatted_value.inferred_type(model)
592+
splatted_value
593+
.inferred_type(model)
594+
.unwrap_or(Type::unknown())
583595
});
584596

585597
// Try to resolve overloads with the arguments/types we have
@@ -612,8 +624,8 @@ pub fn definitions_for_bin_op<'db>(
612624
model: &SemanticModel<'db>,
613625
binary_op: &ast::ExprBinOp,
614626
) -> Option<(Vec<ResolvedDefinition<'db>>, Type<'db>)> {
615-
let left_ty = binary_op.left.inferred_type(model);
616-
let right_ty = binary_op.right.inferred_type(model);
627+
let left_ty = binary_op.left.inferred_type(model)?;
628+
let right_ty = binary_op.right.inferred_type(model)?;
617629

618630
let Ok(bindings) = Type::try_call_bin_op(model.db(), left_ty, binary_op.op, right_ty) else {
619631
return None;
@@ -639,7 +651,7 @@ pub fn definitions_for_unary_op<'db>(
639651
model: &SemanticModel<'db>,
640652
unary_op: &ast::ExprUnaryOp,
641653
) -> Option<(Vec<ResolvedDefinition<'db>>, Type<'db>)> {
642-
let operand_ty = unary_op.operand.inferred_type(model);
654+
let operand_ty = unary_op.operand.inferred_type(model)?;
643655

644656
let unary_dunder_method = match unary_op.op {
645657
ast::UnaryOp::Invert => "__invert__",

0 commit comments

Comments
 (0)