mirror of
https://github.com/microsoft/pyright.git
synced 2024-11-13 09:34:44 +03:00
Fixed several bugs related to recursive type aliases. The hover text was sometimes incorrect, type narrowing for "isinstance" was broken in some cases, and the reportUnnecessaryIsInstance rule was reporting incorrect errors.
This commit is contained in:
parent
67d69ba0d1
commit
604a4dc70b
@ -114,6 +114,7 @@ import {
|
||||
makeTypeVarsConcrete,
|
||||
partiallySpecializeType,
|
||||
specializeType,
|
||||
transformPossibleRecursiveTypeAlias,
|
||||
transformTypeObjectToClass,
|
||||
} from './typeUtils';
|
||||
|
||||
@ -1428,7 +1429,7 @@ export class Checker extends ParseTreeWalker {
|
||||
return;
|
||||
}
|
||||
arg0Type = doForSubtypes(arg0Type, (subtype) => {
|
||||
return transformTypeObjectToClass(subtype);
|
||||
return transformPossibleRecursiveTypeAlias(transformTypeObjectToClass(subtype));
|
||||
});
|
||||
|
||||
if (derivesFromAnyOrUnknown(arg0Type)) {
|
||||
|
@ -12382,13 +12382,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
isInstanceCheck: boolean,
|
||||
isPositiveTest: boolean
|
||||
): Type {
|
||||
if (isTypeVar(type)) {
|
||||
// If it's a constrained TypeVar, treat it as a union for the
|
||||
// purposes of narrowing.
|
||||
type = getConcreteTypeFromTypeVar(type, /* convertConstraintsToUnion */ true);
|
||||
}
|
||||
|
||||
let effectiveType = doForSubtypes(type, (subtype) => {
|
||||
subtype = transformPossibleRecursiveTypeAlias(subtype);
|
||||
|
||||
if (isTypeVar(subtype)) {
|
||||
// If it's a constrained TypeVar, treat it as a union for the
|
||||
// purposes of narrowing.
|
||||
subtype = getConcreteTypeFromTypeVar(subtype, /* convertConstraintsToUnion */ true);
|
||||
}
|
||||
|
||||
return transformTypeObjectToClass(subtype);
|
||||
});
|
||||
|
||||
@ -12498,6 +12500,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
if (!isObject(containerType) || !ClassType.isBuiltIn(containerType.classType)) {
|
||||
return referenceType;
|
||||
}
|
||||
|
||||
const classType = containerType.classType;
|
||||
const builtInName = classType.details.aliasClass
|
||||
? classType.details.aliasClass.details.name
|
||||
@ -16383,6 +16386,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
// If it's a synthesized type var used to implement recursive type
|
||||
// aliases, return the type alias name.
|
||||
if (type.details.recursiveTypeAliasName) {
|
||||
if (expandTypeAlias && type.details.boundType) {
|
||||
return printType(type.details.boundType, expandTypeAlias, recursionCount + 1);
|
||||
}
|
||||
return type.details.recursiveTypeAliasName;
|
||||
}
|
||||
|
||||
|
@ -359,7 +359,7 @@ export function transformPossibleRecursiveTypeAlias(type: Type): Type;
|
||||
export function transformPossibleRecursiveTypeAlias(type: Type | undefined): Type | undefined;
|
||||
export function transformPossibleRecursiveTypeAlias(type: Type | undefined): Type | undefined {
|
||||
if (type) {
|
||||
if (type.category === TypeCategory.TypeVar && type.details.recursiveTypeAliasName && type.details.boundType) {
|
||||
if (isTypeVar(type) && type.details.recursiveTypeAliasName && type.details.boundType) {
|
||||
if (TypeBase.isInstance(type)) {
|
||||
return convertToInstance(type.details.boundType);
|
||||
}
|
||||
|
@ -1404,6 +1404,23 @@ export function isOverloadedFunction(type: Type): type is OverloadedFunctionType
|
||||
return type.category === TypeCategory.OverloadedFunction;
|
||||
}
|
||||
|
||||
export function getTypeAliasInfo(type: Type) {
|
||||
if (type.typeAliasInfo) {
|
||||
return type.typeAliasInfo;
|
||||
}
|
||||
|
||||
if (
|
||||
isTypeVar(type) &&
|
||||
type.details.recursiveTypeAliasName &&
|
||||
type.details.boundType &&
|
||||
type.details.boundType.typeAliasInfo
|
||||
) {
|
||||
return type.details.boundType.typeAliasInfo;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isTypeSame(type1: Type, type2: Type, recursionCount = 0): boolean {
|
||||
if (type1.category !== type2.category) {
|
||||
return false;
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
getOverloadedFunctionDocStrings,
|
||||
} from '../analyzer/typeDocStringUtils';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluator';
|
||||
import { isClass, isModule, isObject, Type, TypeCategory, UnknownType } from '../analyzer/types';
|
||||
import { getTypeAliasInfo, isClass, isModule, isObject, Type, TypeCategory, UnknownType } from '../analyzer/types';
|
||||
import { ClassMemberLookupFlags, isProperty, lookUpClassMember } from '../analyzer/typeUtils';
|
||||
import { throwIfCancellationRequested } from '../common/cancellationUtils';
|
||||
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
|
||||
@ -150,12 +150,15 @@ export class HoverProvider {
|
||||
// the type alias when printing the type information.
|
||||
const type = evaluator.getType(typeNode);
|
||||
let expandTypeAlias = false;
|
||||
if (type?.typeAliasInfo && node.value === type.typeAliasInfo.aliasName) {
|
||||
if (type.typeAliasInfo.aliasName === typeNode.value) {
|
||||
expandTypeAlias = true;
|
||||
}
|
||||
if (type) {
|
||||
const typeAliasInfo = getTypeAliasInfo(type);
|
||||
if (typeAliasInfo) {
|
||||
if (typeAliasInfo.aliasName === typeNode.value) {
|
||||
expandTypeAlias = true;
|
||||
}
|
||||
|
||||
label = 'type alias';
|
||||
label = 'type alias';
|
||||
}
|
||||
}
|
||||
|
||||
this._addResultsPart(
|
||||
|
@ -1098,6 +1098,12 @@ test('TypeAlias8', () => {
|
||||
validateResults(analysisResults, 4);
|
||||
});
|
||||
|
||||
test('TypeAlias9', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeAlias9.py']);
|
||||
|
||||
validateResults(analysisResults, 0, 0, 4);
|
||||
});
|
||||
|
||||
test('Dictionary1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dictionary1.py']);
|
||||
|
||||
|
36
packages/pyright-internal/src/tests/samples/typeAlias9.py
Normal file
36
packages/pyright-internal/src/tests/samples/typeAlias9.py
Normal file
@ -0,0 +1,36 @@
|
||||
# This sample tests the handling of complex recursive types.
|
||||
|
||||
# pyright: strict, reportUnusedVariable=false
|
||||
|
||||
from typing import Dict, List, Literal, Union
|
||||
|
||||
|
||||
JSONArray = List["JSONType"]
|
||||
JSONObject = Dict[str, "JSONType"]
|
||||
|
||||
JSONPrimitive = Union[str, float, int, bool, None]
|
||||
JSONStructured = Union[JSONArray, JSONObject]
|
||||
|
||||
JSONType = Union[JSONPrimitive, JSONStructured]
|
||||
|
||||
|
||||
# Using type alias checking for list:
|
||||
def f2(args: JSONStructured):
|
||||
if isinstance(args, List):
|
||||
t1: Literal[
|
||||
"List[str | float | int | bool | JSONArray | Dict[str, JSONType] | None]"
|
||||
] = reveal_type(args)
|
||||
else:
|
||||
t2: Literal["Dict[str, JSONType]"] = reveal_type(args)
|
||||
dargs: JSONObject = args
|
||||
|
||||
|
||||
# Using type alias checking for dict:
|
||||
def f3(args: JSONStructured):
|
||||
if isinstance(args, Dict):
|
||||
t1: Literal["Dict[str, JSONType]"] = reveal_type(args)
|
||||
else:
|
||||
t2: Literal[
|
||||
"List[str | float | int | bool | JSONArray | Dict[str, JSONType] | None]"
|
||||
] = reveal_type(args)
|
||||
largs: JSONArray = args
|
Loading…
Reference in New Issue
Block a user