diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 746aee725f547..635082de5dfa2 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -67,6 +67,6 @@ def _(flag: bool): def __call__(self) -> int: ... a = NonCallable() - # error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)" - reveal_type(a()) # revealed: Unknown | int + # error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)" + reveal_type(a()) # revealed: int | Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md b/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md index f6cb268312292..3f435d8e5d700 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md +++ b/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md @@ -19,14 +19,17 @@ def _(flag: bool): x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int" ``` -## Partial declarations +## Incompatible declarations for 2 (out of 3) types ```py -def _(flag: bool): - if flag: +def _(flag1: bool, flag2: bool): + if flag1: + x: str + elif flag2: x: int - x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int" + # Here, the declared type for `x` is `int | str | Unknown`. + x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int" ``` ## Incompatible declarations with bad assignment @@ -42,3 +45,31 @@ def _(flag: bool): # error: [invalid-assignment] x = b"foo" ``` + +## No errors + +Currently, we avoid raising the conflicting-declarations for the following cases: + +### Partial declarations + +```py +def _(flag: bool): + if flag: + x: int + + x = 1 +``` + +### Partial declarations in try-except + +Refer to + +```py +def _(): + try: + x: int = 1 + except: + x = 2 + + x = 3 +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 0a1c99513b3f5..3bb335cacf180 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -292,8 +292,8 @@ type DeclaredTypeResult<'db> = Result, (Type<'db>, Box<[Type<'db>]>)>; /// `Ok(declared_type)`. If there are conflicting declarations, returns /// `Err((union_of_declared_types, conflicting_declared_types))`. /// -/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type (and may -/// conflict with other declarations.) +/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type but it +/// will not be considered to be conflicting with any other types. /// /// # Panics /// Will panic if there are no declarations and no `undeclared_ty` is provided. This is a logic @@ -304,27 +304,31 @@ fn declarations_ty<'db>( declarations: DeclarationsIterator<'_, 'db>, undeclared_ty: Option>, ) -> DeclaredTypeResult<'db> { - let decl_types = declarations.map(|declaration| declaration_ty(db, declaration)); + let mut declaration_types = declarations.map(|declaration| declaration_ty(db, declaration)); - let mut all_types = undeclared_ty.into_iter().chain(decl_types); - - let first = all_types.next().expect( - "declarations_ty must not be called with zero declarations and no may-be-undeclared", - ); + let Some(first) = declaration_types.next() else { + if let Some(undeclared_ty) = undeclared_ty { + // Short-circuit to return the undeclared type if there are no declarations. + return Ok(undeclared_ty); + } + panic!("declarations_ty must not be called with zero declarations and no undeclared_ty"); + }; let mut conflicting: Vec> = vec![]; - let declared_ty = if let Some(second) = all_types.next() { - let mut builder = UnionBuilder::new(db).add(first); - for other in [second].into_iter().chain(all_types) { - if !first.is_equivalent_to(db, other) { - conflicting.push(other); - } - builder = builder.add(other); + let mut builder = UnionBuilder::new(db).add(first); + for other in declaration_types { + if !first.is_equivalent_to(db, other) { + conflicting.push(other); } - builder.build() - } else { - first - }; + builder = builder.add(other); + } + // Avoid considering the undeclared type for the conflicting declaration diagnostics. It + // should still be part of the declared type. + if let Some(undeclared_ty) = undeclared_ty { + builder = builder.add(undeclared_ty); + } + let declared_ty = builder.build(); + if conflicting.is_empty() { Ok(declared_ty) } else { @@ -447,6 +451,10 @@ pub enum Type<'db> { } impl<'db> Type<'db> { + pub const fn is_unknown(&self) -> bool { + matches!(self, Type::Unknown) + } + pub const fn is_never(&self) -> bool { matches!(self, Type::Never) } diff --git a/crates/ruff_benchmark/benches/red_knot.rs b/crates/ruff_benchmark/benches/red_knot.rs index abcf73ffa85c2..aebc2e55b3403 100644 --- a/crates/ruff_benchmark/benches/red_knot.rs +++ b/crates/ruff_benchmark/benches/red_knot.rs @@ -31,15 +31,10 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[ "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined", @@ -47,7 +42,6 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[ "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined", - "error[lint:conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None", "warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined", ];