mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Fixed bug #178 - the type analyzer wasn't properly handling unpack operators as assignment targets. Improved error messages for this case. Added unit tests to prevent future regressions.
This commit is contained in:
parent
6370369b9e
commit
23306ddb55
@ -19,9 +19,8 @@ import { NameBindings, NameBindingType } from '../parser/nameBindings';
|
||||
import { AssignmentNode, ClassNode, DelNode, ExpressionNode,
|
||||
ForNode, FunctionNode, GlobalNode, ImportAsNode, ImportFromAsNode,
|
||||
ImportFromNode, LambdaNode, ListNode, ModuleNameNode, ModuleNode, NameNode,
|
||||
NonlocalNode, ParseNode, TupleExpressionNode,
|
||||
TypeAnnotationExpressionNode,
|
||||
WithNode } from '../parser/parseNodes';
|
||||
NonlocalNode, ParseNode, TupleExpressionNode, TypeAnnotationExpressionNode,
|
||||
UnpackExpressionNode, WithNode } from '../parser/parseNodes';
|
||||
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
|
||||
import { ParseTreeWalker } from './parseTreeWalker';
|
||||
|
||||
@ -255,6 +254,8 @@ export class PostParseWalker extends ParseTreeWalker {
|
||||
});
|
||||
} else if (node instanceof TypeAnnotationExpressionNode) {
|
||||
this._addPossibleTupleNamedTarget(node.valueExpression);
|
||||
} else if (node instanceof UnpackExpressionNode) {
|
||||
this._addPossibleTupleNamedTarget(node.expression);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,9 @@ import { SymbolUtils } from './symbolUtils';
|
||||
import { TypeConstraintBuilder } from './typeConstraint';
|
||||
import { TypeConstraintUtils } from './typeConstraintUtils';
|
||||
import { AnyType, ClassType, ClassTypeFlags, FunctionParameter, FunctionType,
|
||||
FunctionTypeFlags, ModuleType, NoneType, ObjectType, OverloadedFunctionType,
|
||||
PropertyType, Type, TypeCategory, TypeVarType, UnboundType, UnionType,
|
||||
FunctionTypeFlags, ModuleType, NeverType, NoneType, ObjectType,
|
||||
OverloadedFunctionType, PropertyType, Type, TypeCategory, TypeVarType, UnboundType,
|
||||
UnionType,
|
||||
UnknownType } from './types';
|
||||
import { ClassMemberLookupFlags, TypeUtils } from './typeUtils';
|
||||
|
||||
@ -2743,23 +2744,50 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
if (tupleType && tupleType.getTypeArguments()) {
|
||||
const entryTypes = tupleType.getTypeArguments()!;
|
||||
let entryCount = entryTypes.length;
|
||||
const allowsMoreEntries = entryCount > 0 &&
|
||||
|
||||
const sourceEndsInEllipsis = entryCount > 0 &&
|
||||
TypeUtils.isEllipsisType(entryTypes[entryCount - 1]);
|
||||
if (allowsMoreEntries) {
|
||||
if (sourceEndsInEllipsis) {
|
||||
entryCount--;
|
||||
}
|
||||
|
||||
if (target.expressions.length === entryCount ||
|
||||
(allowsMoreEntries && target.expressions.length >= entryCount)) {
|
||||
for (let index = 0; index < target.expressions.length; index++) {
|
||||
const entryType = index < entryCount ? entryTypes[index] : UnknownType.create();
|
||||
targetTypes[index].push(entryType);
|
||||
const targetEndsWithUnpackOperator = target.expressions.length > 0 &&
|
||||
target.expressions[target.expressions.length - 1] instanceof UnpackExpressionNode;
|
||||
|
||||
if (targetEndsWithUnpackOperator) {
|
||||
if (entryCount >= target.expressions.length) {
|
||||
for (let index = 0; index < target.expressions.length - 1; index++) {
|
||||
const entryType = index < entryCount ? entryTypes[index] : UnknownType.create();
|
||||
targetTypes[index].push(entryType);
|
||||
}
|
||||
|
||||
let remainingTypes: Type[] = [];
|
||||
for (let index = target.expressions.length - 1; index < entryCount; index++) {
|
||||
const entryType = entryTypes[index];
|
||||
remainingTypes.push(entryType);
|
||||
}
|
||||
|
||||
targetTypes[target.expressions.length - 1].push(TypeUtils.combineTypes(remainingTypes));
|
||||
} else {
|
||||
this._addError(
|
||||
`Tuple size mismatch: expected at least ${ target.expressions.length } entries` +
|
||||
` but got ${ entryCount }`,
|
||||
target);
|
||||
}
|
||||
} else {
|
||||
this._addError(
|
||||
`Tuple size mismatch: expected ${ target.expressions.length }` +
|
||||
` but got ${ entryCount }`,
|
||||
target);
|
||||
if (target.expressions.length === entryCount ||
|
||||
(sourceEndsInEllipsis && target.expressions.length >= entryCount)) {
|
||||
|
||||
for (let index = 0; index < target.expressions.length; index++) {
|
||||
const entryType = index < entryCount ? entryTypes[index] : UnknownType.create();
|
||||
targetTypes[index].push(entryType);
|
||||
}
|
||||
} else {
|
||||
this._addError(
|
||||
`Tuple size mismatch: expected ${ target.expressions.length }` +
|
||||
` but got ${ entryCount }`,
|
||||
target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The assigned expression isn't a tuple, so it had better
|
||||
@ -2798,7 +2826,16 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
path: this._fileInfo.filePath,
|
||||
range: convertOffsetsToRange(name.start, name.end, this._fileInfo.lines)
|
||||
};
|
||||
srcType = UnknownType.create();
|
||||
|
||||
if (!srcType.isAny()) {
|
||||
// Make a list type from the source.
|
||||
const listType = ScopeUtils.getBuiltInType(this._currentScope, 'List');
|
||||
if (listType instanceof ClassType) {
|
||||
srcType = new ObjectType(listType.cloneForSpecialization([srcType]));
|
||||
} else {
|
||||
srcType = UnknownType.create();
|
||||
}
|
||||
}
|
||||
this._assignTypeToNameNode(target.expression, srcType, declaration, srcExpr);
|
||||
}
|
||||
} else if (target instanceof ListNode) {
|
||||
|
@ -66,3 +66,40 @@ func9(func10())
|
||||
func9((2, 3, 4))
|
||||
func9((2,))
|
||||
|
||||
# Tests for tuple assignments with unpack expressions.
|
||||
def func10() -> int:
|
||||
a = (3, 4, 5)
|
||||
|
||||
c, *d = a
|
||||
if c:
|
||||
# This should generate an error because
|
||||
# d should be a list type, not compatible
|
||||
# with the declared return type.
|
||||
return d
|
||||
|
||||
if c:
|
||||
return d[0]
|
||||
|
||||
# This should generate an error because
|
||||
# there are not enough elements to populate
|
||||
# the unpacked variable h.
|
||||
e, f, g, *h = a
|
||||
|
||||
return e
|
||||
|
||||
# Tests for tuple assignments with unpack expressions.
|
||||
def func11() -> float:
|
||||
b = ('hello', 3, 6.7)
|
||||
|
||||
c, *d = b
|
||||
if c:
|
||||
# This should generate an error because
|
||||
# d should be a list type, not compatible
|
||||
# with the declared return type.
|
||||
return d
|
||||
|
||||
if c:
|
||||
return d[0]
|
||||
|
||||
return 3
|
||||
|
||||
|
@ -292,7 +292,7 @@ test('Constant1', () => {
|
||||
test('Tuples1', () => {
|
||||
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['tuples1.py']);
|
||||
|
||||
validateResults(analysisResults, 4);
|
||||
validateResults(analysisResults, 7);
|
||||
});
|
||||
|
||||
test('NamedTuples1', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user