Open KotlinIsland opened 3 years ago
There is a workaround but it's cumbersome:
from typing import TypeVar
T = TypeVar('T')
class assertEqual(Generic[T]):
def __init__(self, expected: T):
self.expected = expected
def __call__(self, actual: T) -> None:
if self.expected != actual:
raise Exception()
assertEqual(1)('') # type error
assertEqual(1)(2) # no type error
There's also another less-general option. According to PEP-483, type widening doesn't occur when the TypeVar has constraints. For example,
T = TypeVar('T', int, str)
def assertEqual(a: T, b: T) -> None:
if a != b:
raise Exception()
assertEqual('1', 1)
# Argument of type Literal[1] cannot be assigned to parameter "b" of "U@f" in function "f".
# Type "int" is incompatible with constrained type variable "str"
This only works when you can specified a closed set of type constraints. However, it suggests the possibility of adding a new parameter to the TypeVar
constructor to change the way type inference works, per-type variable.
This situation does come up occasionally (as evidenced by posted questions and bug reports). I don't know if it's common enough to merit a change to the type system.
Mypy's constraint solver uses a join operation to compute the final (widened) type (producing object
in your sample above). By contrast, pyright uses a union (producing str | int
in your example above). Both are technically correct solutions, but str | int
is more precise and is probably less surprising to most developers.
I'll note that TypeScript does widen in some circumstances but not in others. For example, it allows:
assertSomething({ x: 0 }, { y: 0 });
In this case, the solved type of T
is { x: number, y?: undefined} | { y: number, x?: undefined }
.
@erictraut basedmypy also uses a union.
[ts-toolbelt]() has a NoInfer
type that works the same as in kotlin
import {Function} from 'ts-toolbelt'
declare function assertSomething<T>(expected: T, actual: Function.NoInfer<T>): void
// Argument of type '{ y: number; }' is not assignable to parameter of type '{ x: number; }'.
assertSomething({ x: 0 }, { y: 0 });
the NoInfer
utility type has now been added to typescript
I believe there is a use case for generic inference that doesn't get as wide as possible.
Look at this example of an assertion function, you would never want to do an assertion between two different types, but there is currently no way to type this:
Here are some behaviors from other languages
TypeScript
In Typescript generic inference is narrowed to type level types(not down to instance level types) and is never widened:
Kotlin
Kotlin by default acts the same as Python, but there are annotations for changing the behavior of inference.
NoInfer
will exclude that usage from inferring the type.Exact
will require the type of the parameter is equal at a type level (Number
!=Int
)OnlyInputTypes
will require a type annotation if there is any difference in types between the usages of the generic: