function applyDescriptorAccessMethod()

in packages/pyright-internal/src/analyzer/typeEvaluator.ts [4900:5233]


    function applyDescriptorAccessMethod(
        type: Type,
        memberInfo: ClassMember | undefined,
        baseTypeClass: ClassType,
        bindToType: ClassType | TypeVarType | undefined,
        isAccessedThroughObject: boolean,
        flags: MemberAccessFlags,
        errorNode: ExpressionNode,
        memberName: string,
        usage: EvaluatorUsage,
        diag: DiagnosticAddendum | undefined
    ): DescriptorTypeResult | undefined {
        const treatConstructorAsClassMember = (flags & MemberAccessFlags.TreatConstructorAsClassMethod) !== 0;
        let isTypeValid = true;
        let isAsymmetricDescriptor = false;

        type = mapSubtypes(type, (subtype) => {
            const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);

            if (isClass(concreteSubtype)) {
                // If it's an object, use its class to lookup the descriptor. If it's a class,
                // use its metaclass instead.
                let lookupClass: ClassType | undefined = concreteSubtype;
                let isAccessedThroughMetaclass = false;
                if (TypeBase.isInstantiable(concreteSubtype)) {
                    if (
                        concreteSubtype.details.effectiveMetaclass &&
                        isInstantiableClass(concreteSubtype.details.effectiveMetaclass)
                    ) {
                        // When accessing a class member that is a class whose metaclass implements
                        // a descriptor protocol, only 'get' operations are allowed. If it's accessed
                        // through the object, all access methods are supported.
                        if (isAccessedThroughObject || usage.method === 'get') {
                            lookupClass = convertToInstance(concreteSubtype.details.effectiveMetaclass) as ClassType;
                            isAccessedThroughMetaclass = true;
                        } else {
                            lookupClass = undefined;
                        }
                    } else {
                        lookupClass = undefined;
                    }
                }

                if (lookupClass) {
                    let accessMethodName: string;

                    if (usage.method === 'get') {
                        accessMethodName = '__get__';
                    } else if (usage.method === 'set') {
                        accessMethodName = '__set__';
                    } else {
                        accessMethodName = '__delete__';
                    }

                    const accessMethod = lookUpClassMember(
                        lookupClass,
                        accessMethodName,
                        ClassMemberLookupFlags.SkipInstanceVariables
                    );

                    // Handle properties specially.
                    if (ClassType.isPropertyClass(lookupClass)) {
                        if (usage.method === 'set') {
                            if (!accessMethod) {
                                if (diag) {
                                    diag.addMessage(
                                        Localizer.DiagnosticAddendum.propertyMissingSetter().format({
                                            name: memberName,
                                        })
                                    );
                                }
                                isTypeValid = false;
                                return undefined;
                            }
                        } else if (usage.method === 'del') {
                            if (!accessMethod) {
                                if (diag) {
                                    diag.addMessage(
                                        Localizer.DiagnosticAddendum.propertyMissingDeleter().format({
                                            name: memberName,
                                        })
                                    );
                                }
                                isTypeValid = false;
                                return undefined;
                            }
                        }
                    }

                    if (accessMethod) {
                        let accessMethodType = getTypeOfMember(accessMethod);
                        const argList: FunctionArgument[] = [
                            {
                                // Provide "obj" argument.
                                argumentCategory: ArgumentCategory.Simple,
                                type: ClassType.isClassProperty(lookupClass)
                                    ? baseTypeClass
                                    : isAccessedThroughObject
                                    ? bindToType || ClassType.cloneAsInstance(baseTypeClass)
                                    : NoneType.createInstance(),
                            },
                        ];

                        if (usage.method === 'get') {
                            // Provide "objtype" argument.
                            argList.push({
                                argumentCategory: ArgumentCategory.Simple,
                                type: baseTypeClass,
                            });
                        } else if (usage.method === 'set') {
                            // Provide "value" argument.
                            argList.push({
                                argumentCategory: ArgumentCategory.Simple,
                                type: usage.setType || UnknownType.create(),
                            });
                        }

                        if (
                            ClassType.isPropertyClass(lookupClass) &&
                            memberInfo &&
                            isInstantiableClass(memberInfo!.classType)
                        ) {
                            // This specialization is required specifically for properties, which should be
                            // generic but are not defined that way. Because of this, we use type variables
                            // in the synthesized methods (e.g. __get__) for the property class that are
                            // defined in the class that declares the fget method.

                            // Infer return types before specializing. Otherwise a generic inferred
                            // return type won't be properly specialized.
                            inferReturnTypeIfNecessary(accessMethodType);

                            accessMethodType = partiallySpecializeType(accessMethodType, memberInfo.classType);

                            // If the property is being accessed from a protocol class (not an instance),
                            // flag this as an error because a property within a protocol is meant to be
                            // interpreted as a read-only attribute rather than a protocol, so accessing
                            // it directly from the class has an ambiguous meaning.
                            if (
                                (flags & MemberAccessFlags.AccessClassMembersOnly) !== 0 &&
                                ClassType.isProtocolClass(baseTypeClass)
                            ) {
                                if (diag) {
                                    diag.addMessage(Localizer.DiagnosticAddendum.propertyAccessFromProtocolClass());
                                }
                                isTypeValid = false;
                            }
                        }

                        if (
                            accessMethodType &&
                            (isFunction(accessMethodType) || isOverloadedFunction(accessMethodType))
                        ) {
                            const methodType = accessMethodType;

                            // Don't emit separate diagnostics for these method calls because
                            // they will be redundant.
                            const returnType = suppressDiagnostics(errorNode, () => {
                                // Bind the accessor to the base object type.
                                let bindToClass: ClassType | undefined;

                                // The "bind-to" class depends on whether the descriptor is defined
                                // on the metaclass or the class. We handle properties specially here
                                // because of the way we model the __get__ logic in the property class.
                                if (ClassType.isPropertyClass(concreteSubtype) && !isAccessedThroughMetaclass) {
                                    if (memberInfo && isInstantiableClass(memberInfo.classType)) {
                                        bindToClass = memberInfo.classType;
                                    }
                                } else {
                                    if (isInstantiableClass(accessMethod.classType)) {
                                        bindToClass = accessMethod.classType;
                                    }
                                }

                                const boundMethodType = bindFunctionToClassOrObject(
                                    lookupClass,
                                    methodType,
                                    bindToClass,
                                    errorNode,
                                    /* recursionCount */ undefined,
                                    /* treatConstructorAsClassMember */ undefined,
                                    isAccessedThroughMetaclass ? concreteSubtype : undefined
                                );

                                if (
                                    boundMethodType &&
                                    (isFunction(boundMethodType) || isOverloadedFunction(boundMethodType))
                                ) {
                                    const typeVarMap = new TypeVarMap(getTypeVarScopeId(boundMethodType));
                                    if (bindToClass) {
                                        typeVarMap.addSolveForScope(getTypeVarScopeId(bindToClass));
                                    }

                                    const callResult = validateCallArguments(
                                        errorNode,
                                        argList,
                                        boundMethodType,
                                        typeVarMap,
                                        /* skipUnknownArgCheck */ true
                                    );

                                    if (callResult.argumentErrors) {
                                        isTypeValid = false;
                                        return AnyType.create();
                                    }

                                    // For set or delete, always return Any.
                                    return usage.method === 'get'
                                        ? callResult.returnType || UnknownType.create()
                                        : AnyType.create();
                                }

                                return undefined;
                            });

                            // Determine if we're calling __set__ on an asymmetric descriptor or property.
                            if (usage.method === 'set' && isClass(accessMethod.classType)) {
                                if (isAsymmetricDescriptorClass(accessMethod.classType)) {
                                    isAsymmetricDescriptor = true;
                                }
                            }

                            if (returnType) {
                                return returnType;
                            }
                        }
                    }
                }
            } else if (isFunction(concreteSubtype) || isOverloadedFunction(concreteSubtype)) {
                // If this function is an instance member (e.g. a lambda that was
                // assigned to an instance variable), don't perform any binding.
                if (!isAccessedThroughObject || (memberInfo && !memberInfo.isInstanceMember)) {
                    return bindFunctionToClassOrObject(
                        isAccessedThroughObject ? ClassType.cloneAsInstance(baseTypeClass) : baseTypeClass,
                        concreteSubtype,
                        memberInfo && isInstantiableClass(memberInfo.classType) ? memberInfo.classType : undefined,
                        errorNode,
                        /* recursionCount */ undefined,
                        treatConstructorAsClassMember,
                        bindToType
                    );
                }
            }

            if (usage.method === 'set') {
                if (memberInfo?.symbol.isClassVar()) {
                    if (flags & MemberAccessFlags.DisallowClassVarWrites) {
                        if (diag) {
                            diag.addMessage(
                                Localizer.DiagnosticAddendum.memberSetClassVar().format({ name: memberName })
                            );
                        }
                        isTypeValid = false;
                        return undefined;
                    }
                }

                // Check for an attempt to overwrite a final member variable.
                const finalTypeDecl = memberInfo?.symbol
                    .getDeclarations()
                    .find((decl) => isFinalVariableDeclaration(decl));

                if (finalTypeDecl && !ParseTreeUtils.isNodeContainedWithin(errorNode, finalTypeDecl.node)) {
                    // If a Final instance variable is declared in the class body but is
                    // being assigned within an __init__ method, it's allowed.
                    const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode);
                    if (!enclosingFunctionNode || enclosingFunctionNode.name.value !== '__init__') {
                        if (diag) {
                            diag.addMessage(Localizer.Diagnostic.finalReassigned().format({ name: memberName }));
                        }
                        isTypeValid = false;
                        return undefined;
                    }
                }

                // Check for an attempt to overwrite an instance variable that is
                // read-only (e.g. in a named tuple).
                if (
                    memberInfo?.isInstanceMember &&
                    isClass(memberInfo.classType) &&
                    ClassType.isReadOnlyInstanceVariables(memberInfo.classType)
                ) {
                    if (diag) {
                        diag.addMessage(Localizer.DiagnosticAddendum.readOnlyAttribute().format({ name: memberName }));
                    }
                    isTypeValid = false;
                    return undefined;
                }

                let enforceTargetType = false;

                if (memberInfo && memberInfo.symbol.hasTypedDeclarations()) {
                    // If the member has a declared type, we will enforce it.
                    enforceTargetType = true;
                } else {
                    // If the member has no declared type, we will enforce it
                    // if this assignment isn't within the enclosing class. If
                    // it is within the enclosing class, the assignment is used
                    // to infer the type of the member.
                    if (memberInfo && !memberInfo.symbol.getDeclarations().some((decl) => decl.node === errorNode)) {
                        enforceTargetType = true;
                    }
                }

                if (enforceTargetType) {
                    let effectiveType = concreteSubtype;

                    // If the code is patching a method (defined on the class)
                    // with an object-level function, strip the "self" parameter
                    // off the original type. This is sometimes done for test
                    // purposes to override standard behaviors of specific methods.
                    if (isAccessedThroughObject) {
                        if (!memberInfo!.isInstanceMember && isFunction(concreteSubtype)) {
                            if (
                                FunctionType.isClassMethod(concreteSubtype) ||
                                FunctionType.isInstanceMethod(concreteSubtype)
                            ) {
                                effectiveType = FunctionType.clone(concreteSubtype, /* stripFirstParam */ true);
                            }
                        }
                    }

                    return effectiveType;
                }
            }

            return subtype;
        });

        if (!isTypeValid) {
            return undefined;
        }

        return { type, isAsymmetricDescriptor };
    }