diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/string.md b/crates/ty_python_semantic/resources/mdtest/annotations/string.md index ee8d795534..810927d7a3 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/string.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/string.md @@ -155,6 +155,16 @@ shouldn't panic. # Regression test for https://github.com/astral-sh/ty/issues/1865 # error: [fstring-type-annotation] stringified_fstring_with_conditional: "f'{1 if 1 else 1}'" +# error: [fstring-type-annotation] +stringified_fstring_with_boolean_expression: "f'{1 or 2}'" +# error: [fstring-type-annotation] +stringified_fstring_with_generator_expression: "f'{(i for i in range(5))}'" +# error: [fstring-type-annotation] +stringified_fstring_with_list_comprehension: "f'{[i for i in range(5)]}'" +# error: [fstring-type-annotation] +stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'" +# error: [fstring-type-annotation] +stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'" a: "1 or 2" b: "(x := 1)" diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index a38b0a7ded..43a0a3d0af 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -522,6 +522,11 @@ impl<'db> SemanticIndex<'db> { self.scopes_by_node[&node.node_key()] } + /// Returns the id of the scope that `node` creates, if it exists. + pub(crate) fn try_node_scope(&self, node: NodeWithScopeRef) -> Option { + self.scopes_by_node.get(&node.node_key()).copied() + } + /// Checks if there is an import of `__future__.annotations` in the global scope, which affects /// the logic for type inference. pub(super) fn has_future_annotations(&self) -> bool { diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index cd9398cd8d..76271b07ff 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7926,7 +7926,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let Some(first_comprehension) = comprehensions_iter.next() else { unreachable!("Comprehension must contain at least one generator"); }; - self.infer_standalone_expression(&first_comprehension.iter, TypeContext::default()); + self.infer_maybe_standalone_expression(&first_comprehension.iter, TypeContext::default()); if first_comprehension.is_async { EvaluationMode::Async @@ -7946,9 +7946,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let evaluation_mode = self.infer_first_comprehension_iter(generators); - let scope_id = self + let Some(scope_id) = self .index - .node_scope(NodeWithScopeRef::GeneratorExpression(generator)); + .try_node_scope(NodeWithScopeRef::GeneratorExpression(generator)) + else { + return Type::unknown(); + }; let scope = scope_id.to_scope_id(self.db(), self.file()); let inference = infer_scope_types(self.db(), scope); let yield_type = inference.expression_type(elt.as_ref()); @@ -8021,9 +8024,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_first_comprehension_iter(generators); - let scope_id = self + let Some(scope_id) = self .index - .node_scope(NodeWithScopeRef::ListComprehension(listcomp)); + .try_node_scope(NodeWithScopeRef::ListComprehension(listcomp)) + else { + return Type::unknown(); + }; let scope = scope_id.to_scope_id(self.db(), self.file()); let inference = infer_scope_types(self.db(), scope); let element_type = inference.expression_type(elt.as_ref()); @@ -8046,9 +8052,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_first_comprehension_iter(generators); - let scope_id = self + let Some(scope_id) = self .index - .node_scope(NodeWithScopeRef::DictComprehension(dictcomp)); + .try_node_scope(NodeWithScopeRef::DictComprehension(dictcomp)) + else { + return Type::unknown(); + }; let scope = scope_id.to_scope_id(self.db(), self.file()); let inference = infer_scope_types(self.db(), scope); let key_type = inference.expression_type(key.as_ref()); @@ -8071,9 +8080,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_first_comprehension_iter(generators); - let scope_id = self + let Some(scope_id) = self .index - .node_scope(NodeWithScopeRef::SetComprehension(setcomp)); + .try_node_scope(NodeWithScopeRef::SetComprehension(setcomp)) + else { + return Type::unknown(); + }; let scope = scope_id.to_scope_id(self.db(), self.file()); let inference = infer_scope_types(self.db(), scope); let element_type = inference.expression_type(elt.as_ref()); @@ -8165,14 +8177,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { builder.module(), ) } else { - builder.infer_standalone_expression(iter, tcx) + builder.infer_maybe_standalone_expression(iter, tcx) } .iterate(builder.db()) .homogeneous_element_type(builder.db()) }); for expr in ifs { - self.infer_standalone_expression(expr, TypeContext::default()); + self.infer_maybe_standalone_expression(expr, TypeContext::default()); } } @@ -10341,7 +10353,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let ty = if index == values.len() - 1 { builder.infer_expression(value, TypeContext::default()) } else { - builder.infer_standalone_expression(value, TypeContext::default()) + builder.infer_maybe_standalone_expression(value, TypeContext::default()) }; (ty, value.range())