in packages/pyright-internal/src/analyzer/typeEvaluator.ts [4227:4696]
function getTypeFromMemberAccessWithBaseType(
node: MemberAccessNode,
baseTypeResult: TypeResult,
usage: EvaluatorUsage,
flags: EvaluatorFlags
): TypeResult {
let baseType = baseTypeResult.type;
const memberName = node.memberName.value;
let diag = new DiagnosticAddendum();
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
let type: Type | undefined;
let isIncomplete = !!baseTypeResult.isIncomplete;
let isAsymmetricDescriptor: boolean | undefined;
// If the base type was incomplete and unbound, don't proceed
// because false positive errors will be generated.
if (baseTypeResult.isIncomplete && isUnbound(baseTypeResult.type)) {
return { type: UnknownType.create(), node, isIncomplete: true };
}
// Handle the special case where the expression is an actual
// UnionType special form.
if (isUnion(baseType) && TypeBase.isSpecialForm(baseType)) {
if (objectType) {
baseType = objectType;
}
}
const getTypeFromNoneBase = () => {
if (noneType && isInstantiableClass(noneType)) {
const typeResult = getTypeFromObjectMember(
node.memberName,
noneType,
memberName,
usage,
diag,
/* memberAccessFlags */ undefined,
baseTypeResult.bindToType
);
return typeResult;
}
return undefined;
};
if (isParamSpec(baseType) && baseType.paramSpecAccess) {
baseType = makeTopLevelTypeVarsConcrete(baseType);
}
switch (baseType.category) {
case TypeCategory.Any:
case TypeCategory.Unknown: {
type = baseType;
break;
}
case TypeCategory.Never: {
type = UnknownType.create();
break;
}
case TypeCategory.TypeVar: {
if (baseType.details.isParamSpec) {
if (memberName === 'args') {
const paramNode = ParseTreeUtils.getEnclosingParameter(node);
if (!paramNode || paramNode.category !== ParameterCategory.VarArgList) {
addError(Localizer.Diagnostic.paramSpecArgsUsage(), node);
return { type: UnknownType.create(), node, isIncomplete };
}
return { type: TypeVarType.cloneForParamSpecAccess(baseType, 'args'), node, isIncomplete };
}
if (memberName === 'kwargs') {
const paramNode = ParseTreeUtils.getEnclosingParameter(node);
if (!paramNode || paramNode.category !== ParameterCategory.VarArgDictionary) {
addError(Localizer.Diagnostic.paramSpecKwargsUsage(), node);
return { type: UnknownType.create(), node, isIncomplete };
}
return { type: TypeVarType.cloneForParamSpecAccess(baseType, 'kwargs'), node, isIncomplete };
}
if (!isIncomplete) {
addDiagnostic(
fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.paramSpecUnknownMember().format({ name: memberName }),
node
);
}
return { type: UnknownType.create(), node, isIncomplete };
}
if (flags & EvaluatorFlags.ExpectingType) {
if (!isIncomplete) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.typeVarNoMember().format({
type: printType(baseType),
name: memberName,
}),
node.leftExpression
);
}
return { type: UnknownType.create(), node, isIncomplete };
}
if (baseType.details.recursiveTypeAliasName) {
return { type: UnknownType.create(), node, isIncomplete: true };
}
return getTypeFromMemberAccessWithBaseType(
node,
{
type: makeTopLevelTypeVarsConcrete(baseType),
node,
bindToType: baseType,
isIncomplete,
},
usage,
EvaluatorFlags.None
);
}
case TypeCategory.Class: {
if (TypeBase.isInstantiable(baseType)) {
const typeResult = getTypeFromClassMember(
node.memberName,
baseType,
memberName,
usage,
diag,
MemberAccessFlags.None,
baseTypeResult.bindToType
);
type = typeResult?.type;
if (typeResult?.isIncomplete) {
isIncomplete = true;
}
if (typeResult?.isAsymmetricDescriptor) {
isAsymmetricDescriptor = true;
}
} else if (
ClassType.isBuiltIn(baseType, 'type') &&
objectType &&
isClassInstance(objectType) &&
!baseTypeResult.isSuperCall
) {
// Handle the case where the base type is an instance of 'type'. We'll
// treat it as an instantiable subclass of 'object'.
const typeResult = getTypeFromClassMember(
node.memberName,
ClassType.cloneAsInstantiable(objectType),
memberName,
usage,
diag,
MemberAccessFlags.None,
baseTypeResult.bindToType
? (convertToInstance(baseTypeResult.bindToType) as ClassType | TypeVarType)
: undefined
);
type = typeResult?.type;
if (typeResult?.isIncomplete) {
isIncomplete = true;
}
if (typeResult?.isAsymmetricDescriptor) {
isAsymmetricDescriptor = true;
}
} else {
// Handle the special case of 'name' and 'value' members within an enum.
if (ClassType.isEnumClass(baseType)) {
const literalValue = baseType.literalValue;
if (literalValue instanceof EnumLiteral) {
if (memberName === 'name' || memberName === '_name_') {
const strClass = getBuiltInType(node, 'str');
if (isInstantiableClass(strClass)) {
return {
node,
type: ClassType.cloneAsInstance(
ClassType.cloneWithLiteral(strClass, literalValue.itemName)
),
isIncomplete,
};
}
} else if (memberName === 'value' || memberName === '_value_') {
return { node, type: literalValue.itemType, isIncomplete };
}
}
}
const typeResult = getTypeFromObjectMember(
node.memberName,
baseType,
memberName,
usage,
diag,
/* memberAccessFlags */ undefined,
baseTypeResult.bindToType
);
if (typeResult) {
type = addConditionToType(typeResult.type, getTypeCondition(baseType));
}
if (typeResult?.isIncomplete) {
isIncomplete = true;
}
if (typeResult?.isAsymmetricDescriptor) {
isAsymmetricDescriptor = true;
}
}
break;
}
case TypeCategory.Module: {
const symbol = ModuleType.getField(baseType, memberName);
if (symbol && !symbol.isExternallyHidden()) {
if (usage.method === 'get') {
setSymbolAccessed(AnalyzerNodeInfo.getFileInfo(node), symbol, node.memberName);
}
type = getEffectiveTypeOfSymbolForUsage(
symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (isTypeVar(type)) {
type = validateTypeVarUsage(node, type, flags);
}
// If the type resolved to "unbound", treat it as "unknown" in
// the case of a module reference because if it's truly unbound,
// that error will be reported within the module and should not
// leak into other modules that import it.
if (isUnbound(type)) {
type = UnknownType.create();
}
if (symbol.isPrivateMember()) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportPrivateUsage,
DiagnosticRule.reportPrivateUsage,
Localizer.Diagnostic.privateUsedOutsideOfModule().format({
name: memberName,
}),
node.memberName
);
}
if (symbol.isPrivatePyTypedImport()) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportPrivateImportUsage,
DiagnosticRule.reportPrivateImportUsage,
Localizer.Diagnostic.privateImportFromPyTypedModule().format({
name: memberName,
module: baseType.moduleName,
}),
node.memberName
);
}
} else {
// Does the module export a top-level __getattr__ function?
if (usage.method === 'get') {
const getAttrSymbol = ModuleType.getField(baseType, '__getattr__');
if (getAttrSymbol) {
const isModuleGetAttrSupported =
fileInfo.executionEnvironment.pythonVersion >= PythonVersion.V3_7 ||
getAttrSymbol
.getDeclarations()
.some((decl) => decl.path.toLowerCase().endsWith('.pyi'));
if (isModuleGetAttrSupported) {
const getAttrTypeResult = getEffectiveTypeOfSymbolForUsage(getAttrSymbol);
if (isFunction(getAttrTypeResult.type)) {
type = getFunctionEffectiveReturnType(getAttrTypeResult.type);
if (getAttrTypeResult.isIncomplete) {
isIncomplete = true;
}
}
}
}
}
if (!type) {
if (!isIncomplete) {
addDiagnostic(
fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.moduleUnknownMember().format({ name: memberName }),
node.memberName
);
}
type = evaluatorOptions.evaluateUnknownImportsAsAny ? AnyType.create() : UnknownType.create();
}
}
break;
}
case TypeCategory.Union: {
type = mapSubtypes(baseType, (subtype) => {
if (isNoneInstance(subtype)) {
const typeResult = getTypeFromNoneBase();
if (typeResult) {
type = addConditionToType(typeResult.type, getTypeCondition(baseType));
if (typeResult.isIncomplete) {
isIncomplete = true;
}
return type;
} else {
if (!isIncomplete) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportOptionalMemberAccess,
DiagnosticRule.reportOptionalMemberAccess,
Localizer.Diagnostic.noneUnknownMember().format({ name: memberName }),
node.memberName
);
}
return undefined;
}
} else if (isUnbound(subtype)) {
// Don't do anything if it's unbound. The error will already
// be reported elsewhere.
return undefined;
} else {
const typeResult = getTypeFromMemberAccessWithBaseType(
node,
{
type: subtype,
node,
isIncomplete: baseTypeResult.isIncomplete,
},
usage,
EvaluatorFlags.None
);
if (typeResult.isIncomplete) {
isIncomplete = true;
}
return typeResult.type;
}
});
break;
}
case TypeCategory.Function:
case TypeCategory.OverloadedFunction: {
if (memberName === '__defaults__') {
// The "__defaults__" member is not currently defined in the "function"
// class, so we'll special-case it here.
type = AnyType.create();
} else if (memberName === '__self__') {
// The "__self__" member is not currently defined in the "function"
// class, so we'll special-case it here.
const functionType = isFunction(baseType) ? baseType : baseType.overloads[0];
if (
functionType.preBoundFlags !== undefined &&
(functionType.preBoundFlags & FunctionTypeFlags.StaticMethod) === 0
) {
type = functionType.boundToType;
}
} else {
if (!functionObj) {
type = AnyType.create();
} else {
type = getTypeFromMemberAccessWithBaseType(
node,
{ type: functionObj, node },
usage,
flags
).type;
}
}
break;
}
case TypeCategory.None: {
const typeResult = getTypeFromNoneBase();
if (typeResult) {
type = addConditionToType(typeResult.type, getTypeCondition(baseType));
if (typeResult.isIncomplete) {
isIncomplete = true;
}
}
break;
}
default:
diag.addMessage(Localizer.DiagnosticAddendum.typeUnsupported().format({ type: printType(baseType) }));
break;
}
if (!type) {
const isFunctionRule =
isFunction(baseType) ||
isOverloadedFunction(baseType) ||
(isClassInstance(baseType) && ClassType.isBuiltIn(baseType, 'function'));
if (!baseTypeResult.isIncomplete) {
let diagMessage = Localizer.Diagnostic.memberAccess();
if (usage.method === 'set') {
diagMessage = Localizer.Diagnostic.memberSet();
} else if (usage.method === 'del') {
diagMessage = Localizer.Diagnostic.memberDelete();
}
// If there is an expected type diagnostic addendum (used for assignments),
// use that rather than the local diagnostic addendum because it will be
// more informative.
if (usage.setExpectedTypeDiag) {
diag = usage.setExpectedTypeDiag;
}
const [ruleSet, rule] = isFunctionRule
? [fileInfo.diagnosticRuleSet.reportFunctionMemberAccess, DiagnosticRule.reportFunctionMemberAccess]
: [fileInfo.diagnosticRuleSet.reportGeneralTypeIssues, DiagnosticRule.reportGeneralTypeIssues];
addDiagnostic(
ruleSet,
rule,
diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(),
node.memberName
);
}
// If this is member access on a function, use "Any" so if the
// reportFunctionMemberAccess rule is disabled, we don't trigger
// additional reportUnknownMemberType diagnostics.
type = isFunctionRule ? AnyType.create() : UnknownType.create();
}
// Should we specialize the class?
if ((flags & EvaluatorFlags.DoNotSpecialize) === 0) {
if (isInstantiableClass(type) && !type.typeArguments) {
type = createSpecializedClassType(type, undefined, flags, node);
}
}
if (usage.method === 'get') {
let skipPartialUnknownCheck = isIncomplete;
// Don't report an error if the type is a partially-specialized
// class being passed as an argument. This comes up frequently in
// cases where a type is passed as an argument (e.g. "defaultdict(list)").
// It can also come up in cases like "isinstance(x, (list, dict))".
if (isInstantiableClass(type)) {
const argNode = ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.Argument);
if (argNode && argNode?.parent?.nodeType === ParseNodeType.Call) {
skipPartialUnknownCheck = true;
}
}
if (!skipPartialUnknownCheck) {
reportPossibleUnknownAssignment(
fileInfo.diagnosticRuleSet.reportUnknownMemberType,
DiagnosticRule.reportUnknownMemberType,
node.memberName,
type,
node,
/* ignoreEmptyContainers */ false
);
}
}
return { type, node, isIncomplete, isAsymmetricDescriptor };
}