Skip to content

Commit c9b9676

Browse files
committed
Updated package type verifier to differentiate between "unknown" and "ambiguous" types.
1 parent 99fda8c commit c9b9676

File tree

4 files changed

+345
-172
lines changed

4 files changed

+345
-172
lines changed

docs/typed-libraries.md

+31-13
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ These recommendations are intended to provide the following benefits:
1313
## Inlined Type Annotations and Type Stubs
1414
[PEP 561](https://www.python.org/dev/peps/pep-0561/) documents several ways type information can be delivered for a library: inlined type annotations, type stub files included in the package, a separate companion type stub package, and type stubs in the typeshed repository. Some of these options fall short on delivering the benefits above. We therefore provide the following more specific guidance to library authors.
1515

16-
*All libraries should include inlined type annotations for the functions, classes, methods, and constants that comprise the public interface for the library.*
16+
All libraries should include inlined type annotations for the functions, classes, methods, and constants that comprise the public interface for the library.
1717

1818
Inlined type annotations should be included directly within the source code that ships with the package. Of the options listed in PEP 561, inlined type annotations offer the most benefits. They typically require the least effort to add and maintain, they are always consistent with the implementation, and docstrings and default parameter values are readily available, allowing language servers to enhance the development experience.
1919

20-
There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Python.
21-
22-
*Libraries that expose symbols implemented in languages other than Python should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.*
20+
There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Python. Libraries that expose symbols implemented in languages other than Python should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.
2321

2422
In many existing type stubs (such as those found in typeshed), default parameter values are replaced with with “...” and all docstrings are removed. We recommend that default values and docstrings remain within the type stub file so language servers can display this information to developers.
2523

@@ -74,22 +72,33 @@ Variables:
7472

7573
Type annotations can be omitted in a few specific cases where the type is obvious from the context:
7674

77-
* A class or instance variable does not require an annotation if the class inherits from another class that has provided an annotation for the variable of the same name. The type is inherited from the parent class in this case.
7875
* Constants that are assigned simple literal values (e.g. `RED = '#F00'` or `MAX_TIMEOUT = 50` or `room_temperature: Final = 20`). A constant is a symbol that is assigned only once and is either annotated with `Final` or is named in all-caps. A constant that is not assigned a simple literal value requires explicit annotations, preferably with a `Final` annotation (e.g. `WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']`).
7976
* Enum values within an Enum class do not require annotations because they take on the type of the Enum class.
8077
* Type aliases do not require annotations. A type alias is a symbol that is defined at a module level with a single assignment where the assigned value is an instantiable type, as opposed to a class instance (e.g. `Foo = Callable[[Literal["a", "b"]], Union[int, str]]` or `Bar = Optional[MyGenericClass[int]]`).
8178
* The “self” parameter in an instance method and the “cls” parameter in a class method do not require an explicit annotation.
8279
* The return type for an `__init__` method does not need to be specified, since it is always `None`.
8380
* The following module-level symbols do not require type annotations: `__all__`,`__author__`, `__copyright__`, `__email__`, `__license__`, `__title__`, `__uri__`, `__version__`.
8481
* The following class-level symbols do not require type annotations: `__class__`, `__dict__`, `__doc__`, `__module__`, `__slots__`.
82+
* A variable is assigned in only one location using a simple assignment expression and the right-hand side of the assignment is a literal value (e.g. `1`, `3.14`, `"hi"`, or `MyEnum.Value`) or an identifier that has a known type that doesn't depend on type narrowing logic.
83+
84+
85+
### Ambiguous Types
86+
87+
When a symbol is missing a type annotation, a type checker may be able to infer its type based on contextual information. However, type inference rules are not standardized and differ between type checkers. A symbol is said to have an “ambiguous type” if its type may be inferred differently between different Python type checkers. This can lead to a bad experience for consumers of the library.
88+
89+
Ambiguous types can be avoided by providing explicit type annotations.
90+
8591

86-
### Examples of known and unknown types
92+
### Examples of known, ambiguous and unknown types
8793
```python
8894

89-
# Variable with unknown type
95+
# Variable with known type (unambiguous because it uses a literal assignment)
96+
a = 3
97+
98+
# Variable with ambiguous type
9099
a = [3, 4, 5]
91100

92-
# Variable with known type
101+
# Variable with known (declared) type
93102
a: List[int] = [3, 4, 5]
94103

95104
# Type alias with partially unknown type (because type
@@ -148,7 +157,7 @@ class MyClass:
148157
# Class with partially unknown type
149158
class MyClass:
150159
# Missing type annotation for class variable
151-
height = 2.0
160+
height = None
152161

153162
# Missing input parameter annotations
154163
def __init__(self, name, age):
@@ -161,22 +170,31 @@ class MyClass:
161170
...
162171

163172
# Class with partially unknown type
164-
class BaseClass:
173+
class BaseClass1:
165174
# Missing type annotation
166-
height = 2.0
175+
height: = 2.0
167176

168177
# Missing type annotation
169178
def get_stuff(self):
170179
...
171180

172181
# Class with known type (because it overrides all symbols
173182
# exposed by BaseClass that have incomplete types)
174-
class DerivedClass(BaseClass):
183+
class DerivedClass1(BaseClass1):
175184
height: float
176185

177186
def get_stuff(self) -> str:
178187
...
179188

189+
# Class with known type
190+
class BaseClass2:
191+
height: float = 2.0
192+
193+
# Class with ambiguous type
194+
class DerivedClass2(BaseClass2):
195+
# Missing type annotation, could be inferred as float or int
196+
height = 1
197+
180198
# Class with partially unknown type because base class
181199
# (dict) is generic, and type arguments are not specified.
182200
class DictSubclass(dict):
@@ -189,7 +207,7 @@ Pyright provides a feature that allows library authors to verify type completene
189207

190208
`pyright --verifytypes <lib>`
191209

192-
Pyright will analyze the library, identify all symbols that comprise the interface to the library and emit errors for any symbols whose types are unknown. It also produces a “type completeness score” which is the percentage of symbols with known types.
210+
Pyright will analyze the library, identify all symbols that comprise the interface to the library and emit errors for any symbols whose types are ambiguous or unknown. It also produces a “type completeness score” which is the percentage of symbols with known types.
193211

194212
To see additional details (including a full list of symbols in the library), append the `--verbose` option.
195213

packages/pyright-internal/src/analyzer/packageTypeReport.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ export enum SymbolCategory {
2323
TypeAlias,
2424
}
2525

26+
// The order of these is important. Status values with higher numbers are
27+
// considered "worse" than status values with lower numbers.
2628
export const enum TypeKnownStatus {
27-
Known,
28-
PartiallyUnknown,
29-
Unknown,
29+
Known = 0, // Type is fully known (declared)
30+
Ambiguous = 1, // Type is inferred and potentially ambiguous (may differ by type checker)
31+
PartiallyUnknown = 2, // Part of the type is unknown
32+
Unknown = 3, // The type is completely unknown
3033
}
3134

3235
export interface SymbolInfo {

0 commit comments

Comments
 (0)