PY-24834 Implement the strict semantics of union types

Under the registry flag "python.typing.strict.unions", enabled by default.

"Strict" union is considered a subtype of another type only if *all* its
members are subtypes of this type. Similarly, accessing an attribute on
a union type considered safe only if *all* its members contain this attribute.
It holds even if one of the union members is Any, i.e. "str | Any" is still
incompatible with "int", and it's not safe to access a non-str attribute on
it.

This is how all other type checkers treat union types. Historically, we
use so called "weak unions", where the above rules were relaxed so that
it was enough to have at least one compatible member type. It was
necessary because back then there was no way to declare function overloads,
type narrowing was limited (no pattern matching, no TypeIs/TypeGuard),
so proper strict unions, as in the type theory, would cause too many false
positives.

Co-authored-by: Aleksandr.Govenko <aleksandr.govenko@jetbrains.com>

GitOrigin-RevId: 8a676d810548bf71ac0ff3497e59d8694e03e4db
This commit is contained in:
Mikhail Golubev
2025-08-12 17:19:34 +00:00
committed by intellij-monorepo-bot
co-authored by Aleksandr.Govenko
parent 706a1960d5
commit 3e7b16f248
25 changed files with 296 additions and 64 deletions
@@ -936,6 +936,7 @@ INSP.unresolved.refs.class.object.has.no.attribute=''{0}'' object has no attribu
INSP.unresolved.refs.import.resolves.to.its.containing.file=Import resolves to its containing file
INSP.unresolved.refs.class.does.not.define.operator=Class ''{0}'' does not define ''{1}'', so the ''{2}'' operator cannot be used on its instances
INSP.unresolved.refs.ignore.references.label=Ignored references:
INSP.unresolved.refs.unresolved.attribute.in.union.type=Some members of ''{0}'' don''t have attribute ''{1}''
unresolved.docstring.param.reference=Function ''{0}'' does not have a parameter ''{1}''
unresolved.import.reference=No module named ''{0}''