Changed code that converts types to textual representation to prepend a tilde ("~") character for the inferred type of a "self" or "cls" parameter.

This commit is contained in:
Eric Traut 2021-09-24 20:01:48 -07:00
parent 91b2b76e94
commit 3fa5299614
4 changed files with 54 additions and 9 deletions

View File

@ -297,3 +297,29 @@ def add_one(value: _StrOrFloat) -> _StrOrFloat:
```
Notice that the type of variable `sum` is reported with asterisks (`*`). This indicates that internally the type checker is tracking the type as conditional. In this particular example, it indicates that `sum` is a `str` type if the parameter `value` is a `str` but is a `float` if `value` is a `float`. By tracking these conditional types, the type checker can verify that the return type is consistent with the return type `_StrOrFloat`.
### Inferred type of self and cls parameters
When a type annotation for a methods `self` or `cls` parameter is omitted, pyright will infer its type based on the class that contains the method. The inferred type is internally represented as a type variable that is bound to the class.
Within the function, the type of `self` is printed with a tilde preceding the class name. This indicates that the type is a TypeVar bound to the class rather than the class itself. Outside of the function, this TypeVar is resolved based on the usage.
```python
class Parent:
def method1(self):
reveal_type(self) # ~Parent
return self
@classmethod
def method2(cls):
reveal_type(cls) # Type[~Parent]
return cls
class Child(Parent):
...
reveal_type(Child().method1()) # Child
reveal_type(Child.method2()) # Type[Child]
```

View File

@ -53,9 +53,13 @@ export const enum PrintTypeFlags {
// Expand type aliases to display their individual parts?
ExpandTypeAlias = 1 << 5,
// Add "*" for types that are conditionally constrained when
// Omit "*" for types that are conditionally constrained when
// used with constrained TypeVars.
OmitConditionalConstraint = 1 << 6,
// Omit "~" for synthesized type variables that represent the
// type of an unannotated self or cls parameter.
OmitSelfClsTypeIndicator = 1 << 7,
}
export type FunctionReturnTypeCallback = (type: FunctionType) => Type;
@ -317,14 +321,24 @@ export function printType(
return type.details.recursiveTypeAliasName;
}
// If it's a synthesized type var used to implement `self` or `cls` types,
// print the type with a special character that indicates that the type
// is internally represented as a TypeVar.
if (type.details.boundType) {
const boundTypeString = printType(
let boundTypeString = printType(
type.details.boundType,
printTypeFlags & ~PrintTypeFlags.ExpandTypeAlias,
returnTypeCallback,
recursionTypes
);
if (
(printTypeFlags & PrintTypeFlags.OmitSelfClsTypeIndicator) === 0 &&
!isAnyOrUnknown(type.details.boundType)
) {
boundTypeString = `~${boundTypeString}`;
}
if (TypeBase.isInstantiable(type)) {
return `Type[${boundTypeString}]`;
}
@ -549,7 +563,12 @@ export function printFunctionParts(
const paramType = FunctionType.getEffectiveParameterType(type, index);
const paramTypeString =
recursionTypes.length < maxTypeRecursionCount
? printType(paramType, printTypeFlags, returnTypeCallback, recursionTypes)
? printType(
paramType,
printTypeFlags | PrintTypeFlags.OmitSelfClsTypeIndicator,
returnTypeCallback,
recursionTypes
)
: '';
paramString += ': ' + paramTypeString;

View File

@ -8,14 +8,14 @@ class Foo:
@classmethod
def bar(cls, other: type):
if issubclass(other, cls):
t1: Literal["Type[Foo]"] = reveal_type(other)
t1: Literal["Type[~Foo]"] = reveal_type(other)
if issubclass(other, (int, cls)):
t2: Literal["Type[Foo] | Type[int]"] = reveal_type(other)
t2: Literal["Type[~Foo] | Type[int]"] = reveal_type(other)
def baz(self, other: object):
if isinstance(other, self.__class__):
t1: Literal["Foo"] = reveal_type(other)
t1: Literal["~Foo"] = reveal_type(other)
if isinstance(other, (int, self.__class__)):
t2: Literal["Foo | int"] = reveal_type(other)
t2: Literal["~Foo | int"] = reveal_type(other)

View File

@ -1,4 +1,4 @@
# This sample tests the use of a generic property class.
# This sample tests the use of a generic descriptor class.
from typing import Any, Callable, Generic, Literal, Optional, Type, TypeVar, overload
@ -72,4 +72,4 @@ class B:
b = B()
t_b1: Literal["str"] = reveal_type(b.foo)
t_b2: Literal["Minimal[B, str]"] = reveal_type(B.foo)
t_b2: Literal["Minimal[~B, str]"] = reveal_type(B.foo)