diff --git a/crates/ty_ide/src/goto.rs b/crates/ty_ide/src/goto.rs index 217f8b420b..e0f298024d 100644 --- a/crates/ty_ide/src/goto.rs +++ b/crates/ty_ide/src/goto.rs @@ -295,7 +295,7 @@ impl<'db> Definitions<'db> { impl GotoTarget<'_> { pub(crate) fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { - let ty = match self { + match self { GotoTarget::Expression(expression) => expression.inferred_type(model), GotoTarget::FunctionDef(function) => function.inferred_type(model), GotoTarget::ClassDef(class) => class.inferred_type(model), @@ -317,7 +317,7 @@ impl GotoTarget<'_> { } => { // We don't currently support hovering the bare `.` so there is always a name let module = import_name(module_name, *component_index); - model.resolve_module_type(Some(module), *level)? + model.resolve_module_type(Some(module), *level) } GotoTarget::StringAnnotationSubexpr { string_expr, @@ -334,16 +334,16 @@ impl GotoTarget<'_> { } else { // TODO: force the typechecker to tell us its secrets // (it computes but then immediately discards these types) - return None; + None } } GotoTarget::BinOp { expression, .. } => { let (_, ty) = ty_python_semantic::definitions_for_bin_op(model, expression)?; - ty + Some(ty) } GotoTarget::UnaryOp { expression, .. } => { let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?; - ty + Some(ty) } // TODO: Support identifier targets GotoTarget::PatternMatchRest(_) @@ -353,10 +353,8 @@ impl GotoTarget<'_> { | GotoTarget::TypeParamParamSpecName(_) | GotoTarget::TypeParamTypeVarTupleName(_) | GotoTarget::NonLocal { .. } - | GotoTarget::Globals { .. } => return None, - }; - - Some(ty) + | GotoTarget::Globals { .. } => None, + } } /// Try to get a simplified display of this callable type by resolving overloads diff --git a/crates/ty_ide/src/hover.rs b/crates/ty_ide/src/hover.rs index 8f9add508a..8430a46edc 100644 --- a/crates/ty_ide/src/hover.rs +++ b/crates/ty_ide/src/hover.rs @@ -3610,6 +3610,20 @@ def function(): "); } + #[test] + fn hover_tuple_assignment_target() { + let test = CursorTest::builder() + .source( + "test.py", + r#" + (x, y) = "test", 10 + "#, + ) + .build(); + + assert_snapshot!(test.hover(), @"Hover provided no content"); + } + impl CursorTest { fn hover(&self) -> String { use std::fmt::Write; diff --git a/crates/ty_ide/src/inlay_hints.rs b/crates/ty_ide/src/inlay_hints.rs index f3dd178786..d0742f58ec 100644 --- a/crates/ty_ide/src/inlay_hints.rs +++ b/crates/ty_ide/src/inlay_hints.rs @@ -362,8 +362,9 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> { Expr::Name(name) => { if let Some(rhs) = self.assignment_rhs { if name.ctx.is_store() { - let ty = expr.inferred_type(&self.model); - self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed); + if let Some(ty) = expr.inferred_type(&self.model) { + self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed); + } } } source_order::walk_expr(self, expr); @@ -371,8 +372,9 @@ impl<'a> SourceOrderVisitor<'a> for InlayHintVisitor<'a, '_> { Expr::Attribute(attribute) => { if let Some(rhs) = self.assignment_rhs { if attribute.ctx.is_store() { - let ty = expr.inferred_type(&self.model); - self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed); + if let Some(ty) = expr.inferred_type(&self.model) { + self.add_type_hint(expr, rhs, ty, !self.in_no_edits_allowed); + } } } source_order::walk_expr(self, expr); diff --git a/crates/ty_ide/src/semantic_tokens.rs b/crates/ty_ide/src/semantic_tokens.rs index 5667d1506f..79b37265aa 100644 --- a/crates/ty_ide/src/semantic_tokens.rs +++ b/crates/ty_ide/src/semantic_tokens.rs @@ -273,7 +273,7 @@ impl<'db> SemanticTokenVisitor<'db> { } // Fall back to type-based classification. - let ty = name.inferred_type(self.model); + let ty = name.inferred_type(self.model).unwrap_or(Type::unknown()); let name_str = name.id.as_str(); self.classify_from_type_and_name_str(ty, name_str) } @@ -302,7 +302,9 @@ impl<'db> SemanticTokenVisitor<'db> { let parsed = parsed_module(db, definition.file(db)); let ty = parameter.node(&parsed.load(db)).inferred_type(&model); - if let Type::TypeVar(type_var) = ty { + if let Some(ty) = ty + && let Type::TypeVar(type_var) = ty + { match type_var.typevar(db).kind(db) { TypeVarKind::TypingSelf => { return Some((SemanticTokenType::SelfParameter, modifiers)); @@ -344,9 +346,9 @@ impl<'db> SemanticTokenVisitor<'db> { _ => None, }; - if let Some(value) = value { - let value_ty = value.inferred_type(&model); - + if let Some(value) = value + && let Some(value_ty) = value.inferred_type(&model) + { if value_ty.is_class_literal() || value_ty.is_subclass_of() || value_ty.is_generic_alias() @@ -710,12 +712,12 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> { for alias in &import.names { if let Some(asname) = &alias.asname { // For aliased imports (from X import Y as Z), classify Z based on what Y is - let ty = alias.inferred_type(self.model); + let ty = alias.inferred_type(self.model).unwrap_or(Type::unknown()); let (token_type, modifiers) = self.classify_from_alias_type(ty, asname); self.add_token(asname, token_type, modifiers); } else { // For direct imports (from X import Y), use semantic classification - let ty = alias.inferred_type(self.model); + let ty = alias.inferred_type(self.model).unwrap_or(Type::unknown()); let (token_type, modifiers) = self.classify_from_alias_type(ty, &alias.name); self.add_token(&alias.name, token_type, modifiers); @@ -835,7 +837,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> { self.visit_expr(&attr.value); // Then add token for the attribute name (e.g., 'path' in 'os.path') - let ty = expr.inferred_type(self.model); + let ty = expr.inferred_type(self.model).unwrap_or(Type::unknown()); let (token_type, modifiers) = Self::classify_from_type_for_attribute(ty, &attr.attr); self.add_token(&attr.attr, token_type, modifiers); diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 54ca0ba74f..049a0e6092 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -196,7 +196,10 @@ impl<'db> SemanticModel<'db> { /// Returns completions for symbols available in a `object.` context. pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec> { - let ty = node.value.inferred_type(self); + let Some(ty) = node.value.inferred_type(self) else { + return Vec::new(); + }; + all_members(self.db, ty) .into_iter() .map(|member| Completion { @@ -400,7 +403,7 @@ pub trait HasType { /// /// ## Panics /// May panic if `self` is from another file than `model`. - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db>; + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option>; } pub trait HasDefinition { @@ -412,18 +415,16 @@ pub trait HasDefinition { } impl HasType for ast::ExprRef<'_> { - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { let index = semantic_index(model.db, model.file); // TODO(#1637): semantic tokens is making this crash even with // `try_expr_ref_in_ast` guarding this, for now just use `try_expression_scope_id`. // The problematic input is `x: "float` (with a dangling quote). I imagine the issue // is we're too eagerly setting `is_string_annotation` in inference. - let Some(file_scope) = index.try_expression_scope_id(&model.expr_ref_in_ast(*self)) else { - return Type::unknown(); - }; + let file_scope = index.try_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) + infer_scope_types(model.db, scope).try_expression_type(*self) } } @@ -431,7 +432,7 @@ macro_rules! impl_expression_has_type { ($ty: ty) => { impl HasType for $ty { #[inline] - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { let expression_ref = ExprRef::from(self); expression_ref.inferred_type(model) } @@ -474,7 +475,7 @@ impl_expression_has_type!(ast::ExprSlice); impl_expression_has_type!(ast::ExprIpyEscapeCommand); impl HasType for ast::Expr { - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { match self { Expr::BoolOp(inner) => inner.inferred_type(model), Expr::Named(inner) => inner.inferred_type(model), @@ -525,9 +526,9 @@ macro_rules! impl_binding_has_ty_def { impl HasType for $ty { #[inline] - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { let binding = HasDefinition::definition(self, model); - binding_type(model.db, binding) + Some(binding_type(model.db, binding)) } } }; @@ -541,12 +542,12 @@ impl_binding_has_ty_def!(ast::ExceptHandlerExceptHandler); impl_binding_has_ty_def!(ast::TypeParamTypeVar); impl HasType for ast::Alias { - fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { + fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option> { if &self.name == "*" { - return Type::Never; + return Some(Type::Never); } let index = semantic_index(model.db, model.file); - binding_type(model.db, index.expect_single_definition(self)) + Some(binding_type(model.db, index.expect_single_definition(self))) } } @@ -584,7 +585,7 @@ mod tests { let function = ast.suite()[0].as_function_def_stmt().unwrap(); let model = SemanticModel::new(&db, foo); - let ty = function.inferred_type(&model); + let ty = function.inferred_type(&model).unwrap(); assert!(ty.is_function_literal()); @@ -603,7 +604,7 @@ mod tests { let class = ast.suite()[0].as_class_def_stmt().unwrap(); let model = SemanticModel::new(&db, foo); - let ty = class.inferred_type(&model); + let ty = class.inferred_type(&model).unwrap(); assert!(ty.is_class_literal()); @@ -624,7 +625,7 @@ mod tests { let import = ast.suite()[0].as_import_from_stmt().unwrap(); let alias = &import.names[0]; let model = SemanticModel::new(&db, bar); - let ty = alias.inferred_type(&model); + let ty = alias.inferred_type(&model).unwrap(); assert!(ty.is_class_literal()); diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 54beaf3037..53e2cfa837 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -878,7 +878,7 @@ impl<'db> Type<'db> { Self::Dynamic(DynamicType::Any) } - pub(crate) const fn unknown() -> Self { + pub const fn unknown() -> Self { Self::Dynamic(DynamicType::Unknown) } diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 5162e8a13d..3b19705951 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -155,7 +155,8 @@ pub fn definitions_for_name<'db>( // https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex if matches!(name_str, "float" | "complex") && let Some(expr) = node.expr_name() - && let Some(union) = expr.inferred_type(&SemanticModel::new(db, file)).as_union() + && let Some(ty) = expr.inferred_type(model) + && let Some(union) = ty.as_union() && is_float_or_complex_annotation(db, union, name_str) { return union @@ -234,7 +235,10 @@ pub fn definitions_for_attribute<'db>( let mut resolved = Vec::new(); // Determine the type of the LHS - let lhs_ty = attribute.value.inferred_type(model); + let Some(lhs_ty) = attribute.value.inferred_type(model) else { + return resolved; + }; + let tys = match lhs_ty { Type::Union(union) => union.elements(model.db()).to_vec(), _ => vec![lhs_ty], @@ -374,7 +378,9 @@ pub fn definitions_for_keyword_argument<'db>( call_expr: &ast::ExprCall, ) -> Vec> { let db = model.db(); - let func_type = call_expr.func.inferred_type(model); + let Some(func_type) = call_expr.func.inferred_type(model) else { + return Vec::new(); + }; let Some(keyword_name) = keyword.arg.as_ref() else { return Vec::new(); @@ -498,7 +504,9 @@ pub fn call_signature_details<'db>( model: &SemanticModel<'db>, call_expr: &ast::ExprCall, ) -> Vec> { - let func_type = call_expr.func.inferred_type(model); + let Some(func_type) = call_expr.func.inferred_type(model) else { + return Vec::new(); + }; // Use into_callable to handle all the complex type conversions if let Some(callable_type) = func_type @@ -507,7 +515,9 @@ pub fn call_signature_details<'db>( { let call_arguments = CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| { - splatted_value.inferred_type(model) + splatted_value + .inferred_type(model) + .unwrap_or(Type::unknown()) }); let bindings = callable_type .bindings(model.db()) @@ -564,7 +574,7 @@ pub fn call_type_simplified_by_overloads( call_expr: &ast::ExprCall, ) -> Option { let db = model.db(); - let func_type = call_expr.func.inferred_type(model); + let func_type = call_expr.func.inferred_type(model)?; // Use into_callable to handle all the complex type conversions let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db); @@ -579,7 +589,9 @@ pub fn call_type_simplified_by_overloads( // Hand the overload resolution system as much type info as we have let args = CallArguments::from_arguments_typed(&call_expr.arguments, |_, splatted_value| { - splatted_value.inferred_type(model) + splatted_value + .inferred_type(model) + .unwrap_or(Type::unknown()) }); // Try to resolve overloads with the arguments/types we have @@ -612,8 +624,8 @@ pub fn definitions_for_bin_op<'db>( model: &SemanticModel<'db>, binary_op: &ast::ExprBinOp, ) -> Option<(Vec>, Type<'db>)> { - let left_ty = binary_op.left.inferred_type(model); - let right_ty = binary_op.right.inferred_type(model); + let left_ty = binary_op.left.inferred_type(model)?; + let right_ty = binary_op.right.inferred_type(model)?; let Ok(bindings) = Type::try_call_bin_op(model.db(), left_ty, binary_op.op, right_ty) else { return None; @@ -639,7 +651,7 @@ pub fn definitions_for_unary_op<'db>( model: &SemanticModel<'db>, unary_op: &ast::ExprUnaryOp, ) -> Option<(Vec>, Type<'db>)> { - let operand_ty = unary_op.operand.inferred_type(model); + let operand_ty = unary_op.operand.inferred_type(model)?; let unary_dunder_method = match unary_op.op { ast::UnaryOp::Invert => "__invert__",