Skip to content

Conversation

@MatthewMckee4
Copy link
Contributor

@MatthewMckee4 MatthewMckee4 commented Mar 18, 2025

Summary

fixes #15048
We want to handle more types from Type::KnownInstance

Test Plan

Add tests for each type added explicitly in the match

@MatthewMckee4
Copy link
Contributor Author

@AlexWaygood what do you think of this to start?

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2025

mypy_primer results

No ecosystem changes detected ✅

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.

This looks pretty good! Just Protocol needs attention.

@MatthewMckee4
Copy link
Contributor Author

I'm not fully sure about the names BareSpecialFormAtLeastOne and BareSpecialFormExactlyOne, @AlexWaygood @carljm what do you think?

@carljm
Copy link
Contributor

carljm commented Mar 19, 2025

The split you've made looks good. I think the names you've chosen are clear and I wouldn't argue with them, but it would probably also be ok to be a bit less verbose and go with RequiresOneArgument and RequiresArguments.

@MatthewMckee4 MatthewMckee4 marked this pull request as ready for review March 19, 2025 00:40
@dhruvmanila dhruvmanila added the ty Multi-file analysis & type inference label Mar 19, 2025
/// Some types always require exactly one argument when used in a type expression
RequiresOneArgument(Type<'db>),
/// The `Protocol` type is invalid in type expressions
ProtocolInTypeExpression,
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I feel like the InTypeExpression part of this is redundant with the enum name InvalidTypeExpression; I would just name this variant Protocol. But I see there is already precedent with ClassVarInTypeExpression and FinalInTypeExpression, so I'll go ahead and merge it this way for consistency.

@carljm
Copy link
Contributor

carljm commented Mar 19, 2025

Thank you for the PR!!

@carljm carljm merged commit 3a5f1d4 into astral-sh:main Mar 19, 2025
23 checks passed
Comment on lines +3295 to 3307
Type::KnownInstance(
KnownInstanceType::TypingSelf
| KnownInstanceType::ReadOnly
| KnownInstanceType::TypeAlias
| KnownInstanceType::NotRequired
| KnownInstanceType::Concatenate
| KnownInstanceType::TypeIs
| KnownInstanceType::TypeGuard
| KnownInstanceType::Unpack
| KnownInstanceType::Required,
) => Ok(todo_type!(
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
)),
Copy link
Member

@AlexWaygood AlexWaygood Mar 19, 2025

Choose a reason for hiding this comment

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

I think the only two of these that are valid in type expressions when they are not parameterized are TypingSelf and TypeAlias:

  • ReadOnly, NotRequired, TypeIs, TypeGuard, Unpack and Required all require exactly one argument
  • Concatenate requires at least two arguments

Copy link
Member

Choose a reason for hiding this comment

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

ReadOnly, NotRequired and Required are also type qualifiers, so they are only valid in annotation expressions, not type expressions, even when they do have the necessary number of arguments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

arent ReadOnly, NotRequired, TypeIs, TypeGuard, Unpack and Required only allowed in certain places though? Like how ClassVar and Final are invalid here?

Copy link
Member

Choose a reason for hiding this comment

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

That's correct. But the KnownInstance branches of this method are essentially asking the question, "Is this symbol valid in a type expression if it appears without any type arguments?". And the answer is definitely "no" for all of ReadOnly, NotRequired, TypeIs, TypeGuard, Unpack and Required. It's true that these symbols wouldn't even be allowed in a all type expressions even if they did have the correct number of type arguments. But that's not the question that this method is asking ;)

Copy link
Member

Choose a reason for hiding this comment

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

In terms of the exact error messages we should emit, I think probably:

  • ReadOnly, NotRequired, Required => "Type qualifier `{}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
  • TypeIs, TypeGuard, Unpack => "`{}` requires exactly one argument when used in a type expression"
    • you could reuse your existing InvalidTypeExpression::RequiresOneArgument variant for these
  • Concatenate => "`{}` requires at least two arguments when used in a type expression"
    • you could maybe rework the InvalidTypeExpression::BareAnnotated variant so that it can be used to report errors for Concatenate as well as Annotated

Would you be interested in filing a followup PR? :-)

It could also be nice to have more precise todo_type!() messages for the TypingSelf and TypeAlias variants: todo_type!("Support for `typing.Self`") and todo_type!("Support for `typing.TypeAlias`"), respectively

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah ill get started on that, thanks

Copy link
Contributor Author

@MatthewMckee4 MatthewMckee4 Mar 19, 2025

Choose a reason for hiding this comment

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

ReadOnly, NotRequired, Required => "Type qualifier {} is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"

Am i right in saying that these should be the same message as FinalInTypeExpression and ClassVarInTypeExpression?

Copy link
Member

Choose a reason for hiding this comment

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

Amazing, thank you! You're doing great work :D

Copy link
Member

Choose a reason for hiding this comment

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

Am i right in saying that these should be the same message as FinalInTypeExpression and ClassVarInTypeExpression?

Not quite. ClassVar, Final, Required, NotRequired and ReadOnly are all type qualifiers, so they're all valid in annotation expressions but invalid in type expressions. Nonetheless, I think it would be nice to have different error messages because of the fact that they differ in the number of arguments they accept when they appear in annotation expressions:

  • Final can be used both with and without type arguments in an annotation expression
  • ClassVar it's... sort-of unclear whether it's valid without type arguments or not (see discussion at https://discuss.python.org/t/bare-classvar-annotation/81705)
  • All the rest need exactly one argument when they appear in annotation expressions

So that implies that for the error messages, Final and ClassVar should have this error message:

"Type qualifier `{}` is not allowed in type expressions (only in annotation expressions)"

But for the other three, they should have this error message:

"Type qualifier `{}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"

@MatthewMckee4 MatthewMckee4 deleted the make-in-type-expression-exhaustive branch March 19, 2025 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[red-knot] Make Type::in_type_expression() exhaustive

4 participants