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:
Eric Traut 2019-07-24 10:38:23 -07:00
parent 6370369b9e
commit 23306ddb55
4 changed files with 93 additions and 18 deletions

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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

View File

@ -292,7 +292,7 @@ test('Constant1', () => {
test('Tuples1', () => {
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['tuples1.py']);
validateResults(analysisResults, 4);
validateResults(analysisResults, 7);
});
test('NamedTuples1', () => {