Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions crates/ty_ide/src/goto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ impl<'db> Definitions<'db> {

impl GotoTarget<'_> {
pub(crate) fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
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),
Expand All @@ -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,
Expand All @@ -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(_)
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions crates/ty_ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3610,6 +3610,20 @@ def function():
");
}

#[test]
fn hover_tuple_assignment_target() {
let test = CursorTest::builder()
.source(
"test.py",
r#"
(x, y)<CURSOR> = "test", 10
"#,
)
.build();

assert_snapshot!(test.hover(), @"Hover provided no content");
}

impl CursorTest {
fn hover(&self) -> String {
use std::fmt::Write;
Expand Down
10 changes: 6 additions & 4 deletions crates/ty_ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,17 +362,19 @@ 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);
}
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);
Expand Down
18 changes: 10 additions & 8 deletions crates/ty_ide/src/semantic_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
35 changes: 18 additions & 17 deletions crates/ty_python_semantic/src/semantic_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ impl<'db> SemanticModel<'db> {

/// Returns completions for symbols available in a `object.<CURSOR>` context.
pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec<Completion<'db>> {
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 {
Expand Down Expand Up @@ -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<Type<'db>>;
}

pub trait HasDefinition {
Expand All @@ -412,26 +415,24 @@ 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<Type<'db>> {
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)
}
}

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<Type<'db>> {
let expression_ref = ExprRef::from(self);
expression_ref.inferred_type(model)
}
Expand Down Expand Up @@ -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<Type<'db>> {
match self {
Expr::BoolOp(inner) => inner.inferred_type(model),
Expr::Named(inner) => inner.inferred_type(model),
Expand Down Expand Up @@ -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<Type<'db>> {
let binding = HasDefinition::definition(self, model);
binding_type(model.db, binding)
Some(binding_type(model.db, binding))
}
}
};
Expand All @@ -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<Type<'db>> {
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)))
}
}

Expand Down Expand Up @@ -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());

Expand All @@ -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());

Expand All @@ -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());

Expand Down
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
32 changes: 22 additions & 10 deletions crates/ty_python_semantic/src/types/ide_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -374,7 +378,9 @@ pub fn definitions_for_keyword_argument<'db>(
call_expr: &ast::ExprCall,
) -> Vec<ResolvedDefinition<'db>> {
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();
Expand Down Expand Up @@ -498,7 +504,9 @@ pub fn call_signature_details<'db>(
model: &SemanticModel<'db>,
call_expr: &ast::ExprCall,
) -> Vec<CallSignatureDetails<'db>> {
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
Expand All @@ -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())
Expand Down Expand Up @@ -564,7 +574,7 @@ pub fn call_type_simplified_by_overloads(
call_expr: &ast::ExprCall,
) -> Option<String> {
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);
Expand All @@ -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
Expand Down Expand Up @@ -612,8 +624,8 @@ pub fn definitions_for_bin_op<'db>(
model: &SemanticModel<'db>,
binary_op: &ast::ExprBinOp,
) -> Option<(Vec<ResolvedDefinition<'db>>, 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;
Expand All @@ -639,7 +651,7 @@ pub fn definitions_for_unary_op<'db>(
model: &SemanticModel<'db>,
unary_op: &ast::ExprUnaryOp,
) -> Option<(Vec<ResolvedDefinition<'db>>, 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__",
Expand Down
Loading