Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle type[Any] correctly #14876

Merged
merged 24 commits into from
Dec 10, 2024
Merged

Handle type[Any] correctly #14876

merged 24 commits into from
Dec 10, 2024

Conversation

dcreager
Copy link
Member

@dcreager dcreager commented Dec 9, 2024

This adds support for type[Any], which represents an unknown type (not an instance of an unknown type), and type, which we are choosing to interpret as type[object].

Closes #14546

@AlexWaygood AlexWaygood added the red-knot Multi-file analysis & type inference label Dec 9, 2024
Copy link
Contributor

github-actions bot commented Dec 9, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@dcreager dcreager force-pushed the dcreager/type-of-any branch from ba56f82 to 839f7a4 Compare December 9, 2024 16:57
@dcreager dcreager force-pushed the dcreager/type-of-any branch from 839f7a4 to 4ac0df3 Compare December 9, 2024 19:17
@dcreager dcreager marked this pull request as ready for review December 9, 2024 19:45
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

* main:
  [`airflow`] Add fix to remove deprecated keyword arguments (`AIR302`) (#14887)
  Improve mdtests style (#14884)
  Reference `suppress-dummy-regex-options` in documentation of rules supporting it (#14888)
  [`flake8-bugbear`] `itertools.batched()` without explicit `strict` (`B911`) (#14408)
  [`ruff`] Mark autofix for `RUF052` as always unsafe (#14824)
  [red-knot] Improve type inference for except handlers (#14838)
  More typos found by codespell (#14880)
  [red-knot] move standalone expression_ty to TypeInferenceBuilder::file_expression_ty (#14879)
  [`ruff`] Do not simplify `round()` calls (`RUF046`) (#14832)
  Stop referring to early ruff versions (#14862)
  Fix a typo in `class.rs` (#14877)
  [`flake8-pyi`] Also remove `self` and `cls`'s annotation (`PYI034`) (#14801)
  [`pyupgrade`] Remove unreachable code in `UP015` implementation (#14871)
  [`flake8-bugbear`] Skip `B028` if `warnings.warn` is called with `*args` or `**kwargs` (#14870)
Copy link
Member Author

@dcreager dcreager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Initial responses, still need to make the type ⇒ type[object] change and add the equivalent_to tests)

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types/mro.rs Outdated Show resolved Hide resolved
Comment on lines -608 to -611
(Type::Unknown | Type::Any | Type::Todo(_), _) => false,
(_, Type::Unknown | Type::Any | Type::Todo(_)) => false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed these since they (and the new Subclass(Any) form) are handled by the is_fully_static checks above.

crates/red_knot_python_semantic/src/types.rs Show resolved Hide resolved
@@ -1027,7 +1084,8 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(_)
| Type::SliceLiteral(_)
| Type::KnownInstance(_) => true,
Type::ClassLiteral(_) | Type::SubclassOf(_) | Type::Instance(_) => {
Type::SubclassOf(SubclassOfType { base }) => matches!(base, ClassBase::Class(_)),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SubclassOf(Any) is not fully static

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! In addition to the additional unit tests I mentioned inline, this PR seems a good candidate to run the quickcheck property-based tests for core type algebra invariants. These don't run in CI because they are slow, but it would be good to see whether they catch any issues here. See https://github.com/astral-sh/ruff/blob/main/crates/red_knot_python_semantic/src/types/property_tests.rs

EDIT: just realized that in order to get value from that on this PR, we should also add SubclassOf types to the arbitrary_core_type function there!

crates/red_knot_python_semantic/src/types.rs Show resolved Hide resolved
Comment on lines 821 to 835
(
Type::Instance(InstanceType { class: self_class }),
Type::Instance(InstanceType {
class: target_class,
}),
) if {
let self_known = self_class.known(db);
matches!(
self_known,
Some(KnownClass::NoneType | KnownClass::NoDefaultType)
) && self_known == target_class.known(db)
} =>
{
true
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gets a little hard to read 😄 how about writing it like this?

Suggested change
(
Type::Instance(InstanceType { class: self_class }),
Type::Instance(InstanceType {
class: target_class,
}),
) if {
let self_known = self_class.known(db);
matches!(
self_known,
Some(KnownClass::NoneType | KnownClass::NoDefaultType)
) && self_known == target_class.known(db)
} =>
{
true
}
(Type::Instance(self_instance), Type::Instance(target_instance)) => {
let Some(self_known_class) = self_instance.class.known(db) else {
return false;
};
if !matches!(
self_known_class,
KnownClass::NoneType | KnownClass::NoDefaultType
) {
return false;
}
Some(self_known_class) == target_instance.class.known(db)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to be careful here; these return false in the suggested change are not correct. They would have to be return self == other, and I'm not sure we want to duplicate that condition in various places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argh, great spot. thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had also considered handling each of the special cases with a separate if statement + early return. Then it would possibly be clearer that self == other is the fall-back / general case. But we had other large match blocks elsewhere that made me think that might be preferred house style. I will take a stab at the early return pattern and see if it's more readable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will take a stab at the early return pattern and see if it's more readable.

Now using early return. lmkwyt

Comment on lines 847 to 851
) if object_class.is_known(db, KnownClass::Object)
&& type_class.is_known(db, KnownClass::Type) =>
{
true
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) if object_class.is_known(db, KnownClass::Object)
&& type_class.is_known(db, KnownClass::Type) =>
{
true
}
) => {
object_class.is_known(db, KnownClass::Object)
&& type_class.is_known(db, KnownClass::Type)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should be safe because a Type::Instance can't be == to a Type::SubclassOf.

crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
crates/red_knot_python_semantic/src/types.rs Outdated Show resolved Hide resolved
@sharkdp
Copy link
Contributor

sharkdp commented Dec 10, 2024

In addition to the additional unit tests I mentioned inline, this PR seems a good candidate to run the quickcheck property-based tests for core type algebra invariants. These don't run in CI because they are slow, but it would be good to see whether they catch any issues here. See https://github.com/astral-sh/ruff/blob/main/crates/red_knot_python_semantic/src/types/property_tests.rs

I already did this. I didn't mention it here because they are currently broken (surprise!). I'll put up a small PR with a fix.

Edit: #14897

* main:
  [red-knot] split call-outcome enums to their own submodule (#14898)
  [red-knot] Property tests: account non-fully-static types (#14897)
  ruff: add worktree support to build.rs (#14896)
Copy link
Contributor

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all the updates and the new tests!

FWIW, I did successfully run the property tests on your branch after cherry-picking my fix in #14897 and applying the following patch, where the 4=>3 change is a weird way to suppress the creation of intersection types to prevent running into #14899:

diff --git a/crates/red_knot_python_semantic/src/types/property_tests.rs b/crates/red_knot_python_semantic/src/types/property_tests.rs
index 15f4d5648..e6b223034 100644
--- a/crates/red_knot_python_semantic/src/types/property_tests.rs
+++ b/crates/red_knot_python_semantic/src/types/property_tests.rs
@@ -64,6 +64,10 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty {
         Ty::BuiltinClassLiteral("int"),
         Ty::BuiltinClassLiteral("bool"),
         Ty::BuiltinClassLiteral("object"),
+        Ty::SubclassOfAny,
+        Ty::SubclassOfBuiltinClass("object"),
+        Ty::SubclassOfBuiltinClass("int"),
+        Ty::SubclassOfBuiltinClass("bool"),
     ])
     .unwrap()
     .clone()
@@ -78,7 +82,7 @@ fn arbitrary_type(g: &mut Gen, size: u32) -> Ty {
     if size == 0 {
         arbitrary_core_type(g)
     } else {
-        match u32::arbitrary(g) % 4 {
+        match u32::arbitrary(g) % 3 {
             0 => arbitrary_core_type(g),
             1 => Ty::Union(
                 (0..*g.choose(&[2, 3]).unwrap())

Copy link
Member Author

@dcreager dcreager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did successfully run the property tests on your branch

Thanks @sharkdp, was just looking at the property tests myself and running into the assignable bug

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!!

@dcreager dcreager merged commit d4126f6 into main Dec 10, 2024
21 checks passed
@dcreager dcreager deleted the dcreager/type-of-any branch December 10, 2024 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[red-knot] support type[Any]
4 participants