function getTypeOfExpression()

in packages/pyright-internal/src/analyzer/typeEvaluator.ts [834:1232]


    function getTypeOfExpression(node: ExpressionNode, expectedType?: Type, flags = EvaluatorFlags.None): TypeResult {
        // Is this type already cached?
        const cachedType = readTypeCache(node, flags);
        if (cachedType) {
            return { type: cachedType, node };
        } else {
            // Is it cached in the speculative type cache?
            const speculativeCachedType = speculativeTypeTracker.getSpeculativeType(node, expectedType);
            if (speculativeCachedType) {
                return { type: speculativeCachedType, node };
            }
        }

        // This is a frequently-called routine, so it's a good place to call
        // the cancellation check. If the operation is canceled, an exception
        // will be thrown at this point.
        checkForCancellation();

        const expectedTypeAlt = transformPossibleRecursiveTypeAlias(expectedType);

        // If we haven't already fetched some core type definitions from the
        // typeshed stubs, do so here. It would be better to fetch this when it's
        // needed in canAssignType, but we don't have access to the parse tree
        // at that point.
        initializedBasicTypes(node);

        let typeResult: TypeResult | undefined;
        let reportExpectingTypeErrors = (flags & EvaluatorFlags.ExpectingType) !== 0;

        switch (node.nodeType) {
            case ParseNodeType.Name: {
                typeResult = getTypeFromName(node, flags);
                break;
            }

            case ParseNodeType.MemberAccess: {
                typeResult = getTypeFromMemberAccess(node, flags);

                // Cache the type information in the member name node as well.
                if (!isTypeAliasPlaceholder(typeResult.type)) {
                    writeTypeCache(node.memberName, typeResult.type, flags, !!typeResult.isIncomplete);
                }
                break;
            }

            case ParseNodeType.Index: {
                typeResult = getTypeFromIndex(node, flags);
                break;
            }

            case ParseNodeType.Call: {
                if ((flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0) {
                    // Evaluate the expression still so symbols are marked as accessed.
                    getTypeFromCall(node, expectedTypeAlt);

                    addDiagnostic(
                        AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        Localizer.Diagnostic.typeAnnotationCall(),
                        node
                    );
                    typeResult = { node, type: UnknownType.create() };
                } else {
                    typeResult = getTypeFromCall(node, expectedTypeAlt);
                }
                break;
            }

            case ParseNodeType.Tuple: {
                typeResult = getTypeFromTuple(node, expectedTypeAlt, flags);
                break;
            }

            case ParseNodeType.Constant: {
                typeResult = getTypeFromConstant(node, flags);
                break;
            }

            case ParseNodeType.StringList: {
                const expectingType =
                    (flags & EvaluatorFlags.EvaluateStringLiteralAsType) !== 0 && !isAnnotationLiteralValue(node);

                if (expectingType) {
                    if (node.typeAnnotation) {
                        typeResult = getTypeOfExpression(
                            node.typeAnnotation,
                            undefined,
                            flags |
                                EvaluatorFlags.AllowForwardReferences |
                                EvaluatorFlags.NotParsedByInterpreter |
                                EvaluatorFlags.ExpectingType
                        );
                    } else if (!node.typeAnnotation && node.strings.length === 1) {
                        // We didn't know at parse time that this string node was going
                        // to be evaluated as a forward-referenced type. We need
                        // to re-invoke the parser at this stage.
                        const expr = parseStringAsTypeAnnotation(node);
                        if (expr) {
                            typeResult = getTypeOfExpression(
                                expr,
                                undefined,
                                flags |
                                    EvaluatorFlags.AllowForwardReferences |
                                    EvaluatorFlags.NotParsedByInterpreter |
                                    EvaluatorFlags.ExpectingType
                            );
                        }
                    }

                    if (!typeResult) {
                        const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
                        addDiagnostic(
                            fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                            DiagnosticRule.reportGeneralTypeIssues,
                            Localizer.Diagnostic.expectedTypeNotString(),
                            node
                        );
                        typeResult = { node, type: UnknownType.create() };
                    }

                    // Don't report expecting type errors again. We will have already
                    // reported them when analyzing the contents of the string.
                    reportExpectingTypeErrors = false;
                } else {
                    // Evaluate the format string expressions in this context.
                    node.strings.forEach((str) => {
                        if (str.nodeType === ParseNodeType.FormatString) {
                            str.expressions.forEach((expr) => {
                                getTypeOfExpression(expr);
                            });
                        }
                    });

                    const isBytes = (node.strings[0].token.flags & StringTokenFlags.Bytes) !== 0;

                    // Don't create a literal type if it's an f-string.
                    if (node.strings.some((str) => str.nodeType === ParseNodeType.FormatString)) {
                        typeResult = {
                            node,
                            type: getBuiltInObject(node, isBytes ? 'bytes' : 'str'),
                        };
                    } else {
                        typeResult = {
                            node,
                            type: cloneBuiltinObjectWithLiteral(
                                node,
                                isBytes ? 'bytes' : 'str',
                                node.strings.map((s) => s.value).join('')
                            ),
                        };
                    }
                }
                break;
            }

            case ParseNodeType.Number: {
                if (node.isImaginary) {
                    typeResult = { node, type: getBuiltInObject(node, 'complex') };
                } else if (node.isInteger) {
                    typeResult = { node, type: cloneBuiltinObjectWithLiteral(node, 'int', node.value) };
                } else {
                    typeResult = { node, type: getBuiltInObject(node, 'float') };
                }
                break;
            }

            case ParseNodeType.Ellipsis: {
                if ((flags & EvaluatorFlags.ConvertEllipsisToAny) !== 0) {
                    typeResult = { type: AnyType.create(/* isEllipsis */ true), node };
                } else if ((flags & EvaluatorFlags.ConvertEllipsisToUnknown) !== 0) {
                    typeResult = { type: UnknownType.create(), node };
                } else {
                    const ellipsisType = getBuiltInObject(node, 'ellipsis') || AnyType.create();
                    typeResult = { type: ellipsisType, node };
                }
                break;
            }

            case ParseNodeType.UnaryOperation: {
                typeResult = getTypeFromUnaryOperation(node, expectedTypeAlt);
                break;
            }

            case ParseNodeType.BinaryOperation: {
                typeResult = getTypeFromBinaryOperation(node, expectedTypeAlt, flags);
                break;
            }

            case ParseNodeType.AugmentedAssignment: {
                typeResult = getTypeFromAugmentedAssignment(node, expectedTypeAlt);
                assignTypeToExpression(
                    node.destExpression,
                    typeResult.type,
                    !!typeResult.isIncomplete,
                    node.rightExpression
                );
                break;
            }

            case ParseNodeType.List:
            case ParseNodeType.Set: {
                typeResult = getTypeFromListOrSet(node, expectedTypeAlt);
                break;
            }

            case ParseNodeType.Slice: {
                typeResult = getTypeFromSlice(node);
                break;
            }

            case ParseNodeType.Await: {
                const effectiveExpectedType = expectedType
                    ? createAwaitableReturnType(node, expectedType, /* isGenerator */ false)
                    : undefined;

                const exprTypeResult = getTypeOfExpression(node.expression, effectiveExpectedType, flags);
                typeResult = {
                    type: getTypeFromAwaitable(exprTypeResult.type, node.expression),
                    node,
                };

                if (exprTypeResult.isIncomplete) {
                    typeResult.isIncomplete = true;
                }
                break;
            }

            case ParseNodeType.Ternary: {
                typeResult = getTypeFromTernary(node, flags, expectedTypeAlt);
                break;
            }

            case ParseNodeType.ListComprehension: {
                typeResult = getTypeFromListComprehension(node, expectedTypeAlt);
                break;
            }

            case ParseNodeType.Dictionary: {
                typeResult = getTypeFromDictionary(node, expectedTypeAlt);
                break;
            }

            case ParseNodeType.Lambda: {
                typeResult = getTypeFromLambda(node, expectedTypeAlt);
                break;
            }

            case ParseNodeType.ArrowCallable: {
                typeResult = getTypeFromArrowCallable(node, flags);
                break;
            }

            case ParseNodeType.Assignment: {
                typeResult = getTypeOfExpression(node.rightExpression);
                assignTypeToExpression(
                    node.leftExpression,
                    typeResult.type,
                    /* isTypeIncomplete */ false,
                    node.rightExpression,
                    /* ignoreEmptyContainers */ true
                );
                break;
            }

            case ParseNodeType.AssignmentExpression: {
                typeResult = getTypeOfExpression(node.rightExpression);
                assignTypeToExpression(
                    node.name,
                    typeResult.type,
                    /* isTypeIncomplete */ false,
                    node.rightExpression,
                    /* ignoreEmptyContainers */ true
                );
                break;
            }

            case ParseNodeType.Yield: {
                typeResult = getTypeFromYield(node);
                break;
            }

            case ParseNodeType.YieldFrom: {
                typeResult = getTypeFromYieldFrom(node);
                break;
            }

            case ParseNodeType.Unpack: {
                let iterExpectedType: Type | undefined;
                if (expectedTypeAlt) {
                    const iterableType = getBuiltInType(node, 'Iterable');
                    if (iterableType && isInstantiableClass(iterableType)) {
                        iterExpectedType = ClassType.cloneAsInstance(
                            ClassType.cloneForSpecialization(
                                iterableType,
                                [expectedTypeAlt],
                                /* isTypeArgumentExplicit */ true
                            )
                        );
                    }
                }

                const iterTypeResult = getTypeOfExpression(node.expression, iterExpectedType, flags);
                const iterType = iterTypeResult.type;
                if (
                    (flags & EvaluatorFlags.TypeVarTupleDisallowed) === 0 &&
                    isVariadicTypeVar(iterType) &&
                    !iterType.isVariadicUnpacked
                ) {
                    typeResult = { type: TypeVarType.cloneForUnpacked(iterType), node };
                } else {
                    const type = getTypeFromIterator(iterType, /* isAsync */ false, node) || UnknownType.create();
                    typeResult = { type, unpackedType: iterType, node, isIncomplete: iterTypeResult.isIncomplete };
                }
                break;
            }

            case ParseNodeType.TypeAnnotation: {
                typeResult = getTypeOfExpression(
                    node.typeAnnotation,
                    undefined,
                    EvaluatorFlags.EvaluateStringLiteralAsType |
                        EvaluatorFlags.ParamSpecDisallowed |
                        EvaluatorFlags.TypeVarTupleDisallowed |
                        EvaluatorFlags.ExpectingType |
                        EvaluatorFlags.ExpectingTypeAnnotation |
                        EvaluatorFlags.VariableTypeAnnotation
                );
                break;
            }

            case ParseNodeType.Error: {
                // Evaluate the child expression as best we can so the
                // type information is cached for the completion handler.
                suppressDiagnostics(node, () => {
                    if (node.child) {
                        getTypeOfExpression(node.child);
                    }
                });
                typeResult = { type: UnknownType.create(), node };
                break;
            }
        }

        if (!typeResult) {
            // We shouldn't get here. If we do, report an error.
            fail(`Unhandled expression type '${ParseTreeUtils.printExpression(node)}'`);
        }

        if (reportExpectingTypeErrors && !typeResult.isIncomplete) {
            if (flags & EvaluatorFlags.TypeVarTupleDisallowed) {
                if (
                    isTypeVar(typeResult.type) &&
                    typeResult.type.details.isVariadic &&
                    !typeResult.type.isVariadicInUnion
                ) {
                    addError(Localizer.Diagnostic.typeVarTupleContext(), node);
                    typeResult.type = UnknownType.create();
                }
            }

            if (!TypeBase.isInstantiable(typeResult.type)) {
                const isEmptyVariadic =
                    isClassInstance(typeResult.type) &&
                    ClassType.isTupleClass(typeResult.type) &&
                    typeResult.type.tupleTypeArguments?.length === 0;

                if (!isEmptyVariadic) {
                    addExpectedClassDiagnostic(typeResult.type, node);
                    typeResult.type = UnknownType.create();
                }
            }
        }

        if (flags & EvaluatorFlags.DisallowRecursiveTypeAliasPlaceholder) {
            if (isTypeAliasPlaceholder(typeResult.type)) {
                typeResult.type.details.illegalRecursionDetected = true;
            }
        }

        // Don't update the type cache with an unbound type that results from
        // a resolution cycle. The cache will be updated when the stack unwinds
        // and the type is fully evaluated.
        if (!isTypeAliasPlaceholder(typeResult.type)) {
            writeTypeCache(
                node,
                typeResult.type,
                flags,
                !!typeResult.isIncomplete,
                expectedType,
                /* allowSpeculativeCaching */ true
            );

            if (expectedType && !isAnyOrUnknown(expectedType)) {
                expectedTypeCache.set(node.id, expectedType);
            }
        }

        return typeResult;
    }