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

Add Hint for Type Narrowing When Using Optional Types #1

Merged
merged 27 commits into from
Sep 26, 2024

Conversation

hemant-iitkgp
Copy link
Owner

Fixes python#17036

The added note makes it easier for new mypy users to understand where the errors are coming from and suggests a potentially simple fix.
Note: You can use "if <variable_name> is not None" check to guard against a None value.

srittau and others added 27 commits September 12, 2024 07:26
This PR resolves an issue where the Cobertura XML report generated by
MyPy was missing the lines-covered and lines-valid attributes in the
<coverage> element.

Changes made:

- Added the lines-covered and lines-valid attributes to the <coverage>
element in the Cobertura XML report.
- Updated the CoberturaReportSuite test suite to validate that these
attributes are correctly included in the generated XML.

Fixes #17689
There was confusion about the fullnames of type variables in nested
generic classes. A type variable could be defined internally as
`m.OuterClass.T`, but it was sometimes accessed as `m.T`. The root cause
was that the semantic analyzer didn't initialize the attribute that
refers to the enclosing class consistently.

Fixes #17596. Fixes #17630.
The rvalue expression isn't semantically analyzed, so we can't rely on
the `fullname` attribute to check if there is a reference to
`Annotated`. Instead, use a lookup function provided by the caller to
determine the fullname.

Error reporting in the second argument to `Annotated` is still
inconsistent, but this seems lower priority. I'll create a follow-up
issue about (or update an existing issue if one exists).

Fixes #17751.
…17782)

Fix this conformance test:
```
class ShouldBeCovariant5[T]:
    def __init__(self, x: T) -> None:
        self._x = x

    @Property
    def x(self) -> T:
        return self._x

vo5_1: ShouldBeCovariant5[float] = ShouldBeCovariant5[int](1)  # OK
vo5_2: ShouldBeCovariant5[int] = ShouldBeCovariant5[float](1)  # E
```

My fix is to treat such attributes as not settable when inferring
variance.

Link:

https://github.com/python/typing/blob/main/conformance/tests/generics_variance_inference.py#L79
Fix this conformance test:
```
@DataClass(frozen=True)
class ShouldBeCovariant4[T]:
    x: T

vo4_1: ShouldBeCovariant4[float] = ShouldBeCovariant4[int](1)  # OK
vo4_2: ShouldBeCovariant4[int] = ShouldBeCovariant4[float](1)  # E
```
Link:

https://github.com/python/typing/blob/main/conformance/tests/generics_variance_inference.py#L66
Closes #17570. This is my first contribution to mypy! 🐍 
Added an error code for overlapping function signatures.
Test in check-errorcodes.test is a derivative of this post:
https://stackoverflow.com/q/69341607

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Previously we only inferred variance based on member types, but if a
base class has explicit variance for some type variables, we need to
consider it as well.
I think the PEP 695 syntax is supported well enough now to enable it by
default.
…ables (#17802)

Fixes #17792. Related to #17791.

Currently,
[`pretty_callable`](https://github.com/python/mypy/blob/5dfc7d941253553ab77836e9845cb8fdfb9d23a9/mypy/messages.py#L2862)
only renders `TypeVar` upper bounds if they are of type `Instance`:

https://github.com/python/mypy/blob/5dfc7d941253553ab77836e9845cb8fdfb9d23a9/mypy/messages.py#L2943-L2949

However, there are some types that can appear as `TypeVar` upper bounds
which are not represented by `Instance`, such as `UnionType` and
`CallableType`.

This PR allows such non-`Instance` upper bounds to be rendered as well.

## Effect
Consider the below code. 
Playground link:
https://mypy-play.net/?mypy=1.11.2&python=3.12&enable-incomplete-feature=NewGenericSyntax&gist=ba30c820cc3668e0919dadf2f391ff4b
```python
from collections.abc import Callable
from typing import Any, overload

### No matching overloads

@overload
def f1[T: int](x: T) -> T: ...
@overload
def f1[T: Callable[..., None]](x: T) -> T: ...
@overload
def f1[T: tuple[int]](x: T) -> T: ...
@overload
def f1[T: None](x: T) -> T: ...
@overload
def f1[T: type[int]](x: T) -> T: ...
@overload
def f1[T: bytes | bytearray](x: T) -> T: ...
def f1(x): return x

f1(1.23)

### Mismatching conditional definitions

if input():
    def f2[T](x: T) -> T:
        return x
else:
    def f2[T: Callable[..., None]](x: T) -> T:
        return x
```

### Before
* In the first error on line 20, all overloads aside from the first one
are displayed as `def [T] f1(x: T) -> T` (upper bound missing).
Duplicate entries are suppressed.
* In the second error on line 28, the second definition is displayed as
`def [T] f2(x: T) -> T` (upper bound missing), and is removed as an
apparent duplicate of the first.
```none
main.py:20: error: No overload variant of "f1" matches argument type "float"  [call-overload]
main.py:20: note: Possible overload variants:
main.py:20: note:     def [T: int] f1(x: T) -> T
main.py:20: note:     def [T] f1(x: T) -> T
main.py:28: error: All conditional function variants must have identical signatures  [misc]
main.py:28: note: Original:
main.py:28: note:     def [T] f2(x: T) -> T
main.py:28: note: Redefinition:
Found 2 errors in 1 file (checked 1 source file)
```

### After
* All type var upper bounds are rendered.
```none
main.py:20: error: No overload variant of "f1" matches argument type "float"  [call-overload]
main.py:20: note: Possible overload variants:
main.py:20: note:     def [T: int] f1(x: T) -> T
main.py:20: note:     def [T: Callable[..., None]] f1(x: T) -> T
main.py:20: note:     def [T: tuple[int]] f1(x: T) -> T
main.py:20: note:     def [T: None] f1(x: T) -> T
main.py:20: note:     def [T: type[int]] f1(x: T) -> T
main.py:20: note:     def [T: bytes | bytearray] f1(x: T) -> T
main.py:28: error: All conditional function variants must have identical signatures  [misc]
main.py:28: note: Original:
main.py:28: note:     def [T] f2(x: T) -> T
main.py:28: note: Redefinition:
main.py:28: note:     def [T: Callable[..., None]] f2(x: T) -> T
Found 2 errors in 1 file (checked 1 source file)
```
…accepting single ParamSpec (#17770)

Fixes #17765

The offender for this crash appears to be this snippet:


https://github.com/python/mypy/blob/72c413d2352da5ce1433ef241faca8f40fa1fe27/mypy/semanal.py#L5905-L5910

This branch triggers when applying type args to a type that is generic
with respect to a single `ParamSpec`. It allows double brackets to be
omitted when providing a parameter specification by wrapping all of the
provided type arguments into a single parameter specification argument
(i.e. equating `Foo[int, int]` to `Foo[[int, int]]`). This wrapping
occurs *unless*:
* there is only a single type argument, and it resolves to `Any` (e.g.
`Foo[Any]`)
* **there is only a single type argument**, and it's a bracketed
parameter specification or a `ParamSpec` (e.g. `Foo[[int, int]]`)

The problem occurs when multiple type arguments provided and at least
one of them is a bracketed parameter specification, as in `Foo[[int,
int], str]`.

Per the rules above, since there is more than 1 type argument, mypy
attempts to wrap the arguments into a single parameter specification.
This results in the attempted creation of a `Parameters` instance that
contains another `Parameters` instance, which triggers this assert
inside `Parameters.__init__`:


https://github.com/python/mypy/blob/72c413d2352da5ce1433ef241faca8f40fa1fe27/mypy/types.py#L1634

I think a reasonable solution is to forgo wrapping the type arguments
into a single `Parameters` if **any** of the provided type arguments are
a `Parameters`/`ParamSpecType`. That is, don't transform `Foo[A1, A2,
...]` to `Foo[[A1, A2, ...]]` if any of `A1, A2, ...` are a parameter
specification.

This change brings the crash case inline with mypy's current behavior
for a similar case:
```python
# Current behavior
P = ParamSpec("P")
class C(Generic[P]): ...
c: C[int, [int, str], str]  # E: Nested parameter specifications are not allowed
```

Before this change:
```python
P = ParamSpec("P")
class C(Generic[P]): ...
class D(C[int, [int, str], str]): ... # !!! CRASH !!!
```
After this change:
```python
P = ParamSpec("P")
class C(Generic[P]): ...
class D(C[int, [int, str], str]): ... # E: Nested parameter specifications are not allowed
````
…17323)

Fixes #14571.

When type checking a call of a `ParamSpec`-typed callable, currently
there is an incorrect "fast path" (if there are two arguments of shape
`(*args: P.args, **kwargs: P.kwargs)`, accept), which breaks with
`Concatenate` (such call was accepted even for `Concatenate[int, P]`).

Also there was no checking that args and kwargs are actually present:
since `*args` and `**kwargs` are not required, their absence was
silently accepted.
Fix variance inference in this fragment from a typing conformance test:
```
class ClassA[T1, T2, T3](list[T1]):
    def method1(self, a: T2) -> None:
        ...

    def method2(self) -> T3:
        ...
```

Previously T2 was incorrectly inferred as invariant due to `list` having
methods that return `Self`. Be more flexible with return types to allow
inferring contravariance for type variables even if there are `Self`
return types, in particular.

We could probably make this even more lenient, but after thinking about
this for a while, I wasn't sure what the most general rule would be, so
I decided to just make a tweak to support the likely most common use
case (which is probably actually not that common either).

Link to conformance test:

https://github.com/python/typing/blob/main/conformance/tests/generics_variance_inference.py#L15C1-L20C12
Provide examples using both syntax variants, and give both of the syntax
variants similar prominence. It's likely that both syntax variants will
continue to be widely used for a long time.

Also adjust terminology (e.g. use 'type parameter' / 'type argument'
more consistently), since otherwise some descriptions would be unclear.

I didn't update examples outside the generics chapter. I'll do this in a
follow-up PR.

Work on #17810.

---------

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
Finish work started in #17816. 

Document `type` statement when discussing type aliases.

Update some examples to have both old-style and new-style variants. In
less common scenarios, examples only use a single syntax variant to
reduce verbosity. Also update some examples to generally use more modern
features.

Closes #17810.
Closes #16891

### Before
```python
from typing import Literal

def f1(a: str) -> Literal[""]:
    return a and exit()  # E: Incompatible return value type (got "str", expected "Literal['']")

def f2(a: int) -> Literal[0]:
    return a and exit()  # E: Incompatible return value type (got "int", expected "Literal[0]")

def f3(a: bytes) -> Literal[b""]:
    return a and exit()  # E: Incompatible return value type (got "bytes", expected "Literal[b'']")
```
### After
```none
Success: no issues found in 1 source file
```
The explanation of self types uses upper bounds, so it makes sense to
discuss them earlier.

I also made some other minor documentation tweaks to improve clarity.
Use `collections.abc.Iterable`, for example, instead of
`typing.Iterable`, unless we are discussing support for older Python
versions. The latter has been deprecated since Python 3.9. Also update a
few other out-of-date things that I happened to notice.

Part of the motivation is that Python 3.8 will reach end of life in
October, so we can soon start mostly assuming that users are on 3.9 or
newer (even if we'll continue supporting 3.8 for a while still).

---------

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Soon 4 out of 5 supported Python versions will support the `X | Y`
syntax, so we can make it more prominent (Python 3.13 will be out 
soon, and 3.8 will reach end of life). Use it in most examples.

The syntax is available starting from Python 3.10, but it can be used
in annotations in earlier Python versions as well if using
`from __future__ import annotations`.

---------

Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
@hemant-iitkgp hemant-iitkgp merged commit 561fded into hemant-iitkgp:master Sep 26, 2024
@hemant-iitkgp
Copy link
Owner Author

#1

Cases like following:
code.py
class A:
pass

class B:
def bar(self):
pass

x: A | B
x.bar()

terminal:

main.py:9: error: Item "A" of "A | B" has no attribute "bar" [union-attr]
main.py:9: note: You can use "if <variable_name> is not None" check to guard against a None value.

has also been handled, specifically to narrow down None data type cases and rest as usual like the case of user defined data types A & B in above case.
now the terminal is showing following message:

index1.py:9: error: Item "A" of "A | B" has no attribute "bar" [union-attr]

no note!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better error message when union may not have been narrowed