Changed the behavior of the package type verifier so it does not flag unannotated class or instance variables if a parent class provides a type annotation for a variable of the same name. The type is inherited in this case. Also updated the library guidance to reflect this change.

This commit is contained in:
Eric Traut 2022-02-11 10:25:48 -08:00
parent 2090c5f09b
commit 92b4028cd5
2 changed files with 25 additions and 2 deletions

View File

@ -74,6 +74,7 @@ Variables:
Type annotations can be omitted in a few specific cases where the type is obvious from the context:
* 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.
* 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']`).
* Enum values within an Enum class do not require annotations because they take on the type of the Enum class.
* 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]]`).

View File

@ -35,6 +35,7 @@ import { isDunderName, isPrivateOrProtectedName } from './symbolNameUtils';
import {
ClassType,
FunctionType,
isClass,
isInstantiableClass,
isModule,
isUnknown,
@ -440,7 +441,8 @@ export class PackageTypeVerifier {
scopeName: string,
symbolTable: SymbolTable,
scopeType: ScopeType,
publicSymbolMap: PublicSymbolMap
publicSymbolMap: PublicSymbolMap,
overrideSymbolCallback?: (name: string, symbol: Symbol) => Symbol
): boolean {
if (this._shouldIgnoreType(report, scopeName)) {
return true;
@ -467,6 +469,9 @@ export class PackageTypeVerifier {
return;
}
if (overrideSymbolCallback) {
symbol = overrideSymbolCallback(name, symbol);
}
const symbolType = this._program.getTypeForSymbol(symbol);
const typedDecls = symbol.getTypedDeclarations();
@ -949,7 +954,24 @@ export class PackageTypeVerifier {
type.details.fullName,
type.details.fields,
ScopeType.Class,
publicSymbolMap
publicSymbolMap,
(name: string, symbol: Symbol) => {
// If the symbol within this class is lacking a type declaration,
// see if we can find a same-named symbol in a parent class with
// a type declaration.
if (!symbol.hasTypedDeclarations()) {
for (const mroClass of type.details.mro.slice(1)) {
if (isClass(mroClass)) {
const overrideSymbol = mroClass.details.fields.get(name);
if (overrideSymbol && overrideSymbol.hasTypedDeclarations()) {
return overrideSymbol;
}
}
}
}
return symbol;
}
)
) {
symbolInfo.typeKnownStatus = TypeKnownStatus.PartiallyUnknown;