diff --git a/docs/typed-libraries.md b/docs/typed-libraries.md index b3f4dc68d..23a05af5b 100644 --- a/docs/typed-libraries.md +++ b/docs/typed-libraries.md @@ -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]]`). diff --git a/packages/pyright-internal/src/analyzer/packageTypeVerifier.ts b/packages/pyright-internal/src/analyzer/packageTypeVerifier.ts index f003b1798..632fac19d 100644 --- a/packages/pyright-internal/src/analyzer/packageTypeVerifier.ts +++ b/packages/pyright-internal/src/analyzer/packageTypeVerifier.ts @@ -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;