Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Thumbs.db
serena/
.beads/
AGENTS.md
/plan

# Generated benchmark output
packages/pyright-internal/src/tests/benchmarks/.generated/
Expand Down
109 changes: 44 additions & 65 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Misspelling in commit message: "current interence context" -> "current inference context"

Original file line number Diff line number Diff line change
Expand Up @@ -1117,10 +1117,7 @@ export function createTypeEvaluator(

if (isAny(anySpecialForm)) {
TypeBase.setSpecialForm(anySpecialForm, anyClass);

if (isTypeFormSupported(node)) {
TypeBase.setTypeForm(anySpecialForm, convertToInstance(anySpecialForm));
}
TypeBase.setTypeForm(anySpecialForm, convertToInstance(anySpecialForm));
}
}
}
Expand Down Expand Up @@ -1314,7 +1311,7 @@ export function createTypeEvaluator(
expectingInstantiable = false;
}

typeResult = getTypeOfStringList(node, flags);
typeResult = getTypeOfStringList(node, flags, inferenceContext);
break;
}

Expand Down Expand Up @@ -1656,7 +1653,11 @@ export function createTypeEvaluator(
return typeResult;
}

function getTypeOfStringList(node: StringListNode, flags: EvalFlags): TypeResult {
function getTypeOfStringList(
node: StringListNode,
flags: EvalFlags,
inferenceContext?: InferenceContext
): TypeResult {
let typeResult: TypeResult | undefined;

if ((flags & EvalFlags.StrLiteralAsType) !== 0 && (flags & EvalFlags.TypeFormArg) === 0) {
Expand Down Expand Up @@ -1743,11 +1744,15 @@ export function createTypeEvaluator(
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot generated:
The wantsTypeForm guard is well-designed — it prevents expensive string-as-TypeForm interpretation for plain string literals. However, [unverified] — it's unclear whether inferenceContext propagates deeply enough through container literal evaluation. For example, in d: dict[str, TypeForm] = {"key": "int"}, does the TypeForm expected type reach the string "int" via inference context? A targeted test case for container scenarios (list[TypeForm], dict[str, TypeForm]) would confirm or refute this.

[verified]

}

if (
node.d.strings.length !== 1 ||
node.d.strings[0].nodeType !== ParseNodeType.String ||
!isTypeFormSupported(node)
) {
// Only attempt to interpret the string as a TypeForm forward reference when
// there's a signal that a TypeForm value is wanted in this context. Doing it
// unconditionally can trigger expensive (and recursion-prone) type lookups
// for plain string literals in non-type contexts.
const wantsTypeForm =
(flags & EvalFlags.TypeFormArg) !== 0 ||
(inferenceContext !== undefined && expectedTypeWantsTypeForm(inferenceContext.expectedType));

if (node.d.strings.length !== 1 || node.d.strings[0].nodeType !== ParseNodeType.String || !wantsTypeForm) {
return typeResult;
}

Expand Down Expand Up @@ -5014,10 +5019,6 @@ export function createTypeEvaluator(
}

function addTypeFormForSymbol(node: ExpressionNode, type: Type, flags: EvalFlags, includesVarDecl: boolean): Type {
if (!isTypeFormSupported(node)) {
return type;
}

const isValid = isSymbolValidTypeExpression(type, includesVarDecl);

// If the type already has type information associated with it, don't replace.
Expand Down Expand Up @@ -7659,9 +7660,7 @@ export function createTypeEvaluator(
typeArgs: aliasTypeArgs,
});

if (isTypeFormSupported(node)) {
type = TypeBase.cloneWithTypeForm(type, reportedError ? undefined : convertToInstance(type));
}
type = TypeBase.cloneWithTypeForm(type, reportedError ? undefined : convertToInstance(type));

if (baseType.props?.typeAliasInfo) {
return { type, node };
Expand Down Expand Up @@ -8832,7 +8831,7 @@ export function createTypeEvaluator(
}

const typeFormResult = getTypeOfArgExpectingType(convertNodeToArg(node.d.args[0]), {
typeFormArg: isTypeFormSupported(node),
typeFormArg: true,
noNonTypeSpecialForms: true,
typeExpression: true,
});
Expand Down Expand Up @@ -13852,9 +13851,7 @@ export function createTypeEvaluator(
? prefetched.noneTypeClass
: convertToInstance(prefetched.noneTypeClass);

if (isTypeFormSupported(node)) {
type = TypeBase.cloneWithTypeForm(type, convertToInstance(type));
}
type = TypeBase.cloneWithTypeForm(type, convertToInstance(type));
}
} else if (
node.d.constType === KeywordType.True ||
Expand Down Expand Up @@ -15738,7 +15735,7 @@ export function createTypeEvaluator(
FunctionType.addParamSpecVariadics(functionType, convertToInstance(paramSpec));
}

if (isTypeFormSupported(errorNode) && isValidTypeForm) {
if (isValidTypeForm) {
functionType = TypeBase.cloneWithTypeForm(functionType, convertToInstance(functionType));
}

Expand Down Expand Up @@ -15954,7 +15951,7 @@ export function createTypeEvaluator(
result = TypeBase.cloneAsSpecialForm(result, ClassType.cloneAsInstance(prefetched.unionTypeClass));
}

if (isTypeFormSupported(node) && isValidTypeForm) {
if (isValidTypeForm) {
result = TypeBase.cloneWithTypeForm(result, convertToInstance(result));
}

Expand Down Expand Up @@ -16025,9 +16022,7 @@ export function createTypeEvaluator(
});
let resultType = ClassType.specialize(classType, convertedTypeArgs);

if (isTypeFormSupported(errorNode)) {
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));
}
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

return resultType;
}
Expand Down Expand Up @@ -16059,9 +16054,7 @@ export function createTypeEvaluator(

let resultType = ClassType.specialize(classType, convertedTypeArgs);

if (isTypeFormSupported(errorNode)) {
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));
}
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

return resultType;
}
Expand Down Expand Up @@ -16664,7 +16657,7 @@ export function createTypeEvaluator(
if (unionType.props?.typeForm) {
unionType = TypeBase.cloneWithTypeForm(unionType, undefined);
}
} else if (isTypeFormSupported(errorNode)) {
} else {
const typeFormType = combineTypes(types.map((t) => t.props!.typeForm!));
unionType = TypeBase.cloneWithTypeForm(unionType, typeFormType);
}
Expand Down Expand Up @@ -17025,9 +17018,7 @@ export function createTypeEvaluator(
specialType.shared.baseClasses.push(prefetched?.strClass ?? AnyType.create());
computeMroLinearization(specialType);

if (isTypeFormSupported(node)) {
specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
}
specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
}

// Handle 'Never' and 'NoReturn' specially.
Expand All @@ -17037,9 +17028,7 @@ export function createTypeEvaluator(
specialType
);

if (isTypeFormSupported(node)) {
specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
}
specialType = TypeBase.cloneWithTypeForm(specialType, convertToInstance(specialType));
}

writeTypeCache(node, { type: specialType }, EvalFlags.None);
Expand Down Expand Up @@ -21244,9 +21233,7 @@ export function createTypeEvaluator(

let resultType = aliasedName === 'Never' ? NeverType.createNever() : NeverType.createNoReturn();
resultType = TypeBase.cloneAsSpecialForm(resultType, classType);
if (isTypeFormSupported(errorNode)) {
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));
}
resultType = TypeBase.cloneWithTypeForm(resultType, convertToInstance(resultType));

return { type: resultType };
}
Expand All @@ -21268,9 +21255,7 @@ export function createTypeEvaluator(
typeType = explodeGenericClass(typeType);
}

if (isTypeFormSupported(errorNode)) {
typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));
}
typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));

return { type: typeType };
}
Expand Down Expand Up @@ -21430,9 +21415,7 @@ export function createTypeEvaluator(
typeType = explodeGenericClass(typeType);
}

if (isTypeFormSupported(errorNode)) {
typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));
}
typeType = TypeBase.cloneWithTypeForm(typeType, convertToInstance(typeType));

return { type: typeType };
}
Expand All @@ -21449,12 +21432,7 @@ export function createTypeEvaluator(
/* isSpecialForm */ false
);

if (isTypeFormSupported(errorNode)) {
specializedClass = TypeBase.cloneWithTypeForm(
specializedClass,
convertToInstance(specializedClass)
);
}
specializedClass = TypeBase.cloneWithTypeForm(specializedClass, convertToInstance(specializedClass));

return { type: specializedClass };
}
Expand Down Expand Up @@ -21715,12 +21693,10 @@ export function createTypeEvaluator(

let specializedClass = ClassType.specialize(classType, typeArgTypes, typeArgs !== undefined);

if (isTypeFormSupported(errorNode)) {
specializedClass = TypeBase.cloneWithTypeForm(
specializedClass,
isValidTypeForm ? convertToInstance(specializedClass) : undefined
);
}
specializedClass = TypeBase.cloneWithTypeForm(
specializedClass,
isValidTypeForm ? convertToInstance(specializedClass) : undefined
);

return { type: specializedClass };
}
Expand Down Expand Up @@ -25598,6 +25574,16 @@ export function createTypeEvaluator(
return isAssignable;
}

function expectedTypeWantsTypeForm(expectedType: Type): boolean {
let result = false;
doForEachSubtype(expectedType, (subtype) => {
if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'TypeForm')) {
result = true;
}
});
return result;
}

// If the expected type is an explicit TypeForm type, see if the source
// type has an implicit TypeForm type that can be assigned to it. If so,
// convert to an explicit TypeForm type.
Expand Down Expand Up @@ -28701,13 +28687,6 @@ export function createTypeEvaluator(
return { sourceType: simpleSrcType, destType: simpleDestType };
}

function isTypeFormSupported(node: ParseNode) {
Comment thread
rchiodo marked this conversation as resolved.
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);

// For now, enable only if enableExperimentalFeatures is true.
return fileInfo.diagnosticRuleSet.enableExperimentalFeatures;
}

function printType(type: Type, options?: PrintTypeOptions): string {
let flags = evaluatorOptions.printTypeFlags;

Expand Down