Added type checking for unary operations (not, -, +, ~).

This commit is contained in:
Eric Traut 2019-03-31 18:28:47 -07:00
Родитель 705cc767de
Коммит de035a2f1b
9 изменённых файлов: 289 добавлений и 219 удалений

Просмотреть файл

@ -207,9 +207,7 @@ export class ExpressionEvaluator {
} else if (node instanceof EllipsisNode) {
typeResult = { type: AnyType.create(), node };
} else if (node instanceof UnaryExpressionNode) {
// TODO - need to implement
this._getTypeFromExpression(node.expression, flags);
typeResult = { type: UnknownType.create(), node };
typeResult = this._getTypeFromUnaryExpression(node, flags);
} else if (node instanceof BinaryExpressionNode) {
typeResult = this._getTypeFromBinaryExpression(node, flags);
} else if (node instanceof ListNode) {
@ -237,7 +235,7 @@ export class ExpressionEvaluator {
elseType = this._getTypeFromExpression(node.elseExpression, flags);
});
let type = TypeUtils.combineTypes(ifType!.type, elseType!.type);
let type = TypeUtils.combineTypes([ifType!.type, elseType!.type]);
typeResult = { type, node };
} else if (node instanceof ListComprehensionNode) {
// TODO - need to implement
@ -389,7 +387,7 @@ export class ExpressionEvaluator {
});
if (returnTypes.length > 0) {
type = TypeUtils.combineTypesArray(returnTypes);
type = TypeUtils.combineTypes(returnTypes);
}
} else if (baseType instanceof PropertyType) {
// TODO - need to come up with new strategy for properties
@ -736,7 +734,7 @@ export class ExpressionEvaluator {
});
if (returnTypes.length > 0) {
type = TypeUtils.combineTypesArray(returnTypes);
type = TypeUtils.combineTypes(returnTypes);
}
} else if (callType.isAny()) {
type = UnknownType.create();
@ -874,7 +872,7 @@ export class ExpressionEvaluator {
}
if (returnTypes.length > 0) {
returnType = TypeUtils.combineTypesArray(returnTypes);
returnType = TypeUtils.combineTypes(returnTypes);
}
}
@ -1180,41 +1178,110 @@ export class ExpressionEvaluator {
classFields.set('__class__', new Symbol(classType, DefaultTypeSourceId));
const instanceFields = classType.getInstanceFields();
let tupleType = new TupleType(ScopeUtils.getBuiltInType(this._scope, 'tuple') as ClassType);
let constructorType = new FunctionType(FunctionTypeFlags.ClassMethod);
constructorType.setDeclaredReturnType(new ObjectType(classType));
constructorType.addParameter({
category: ParameterCategory.Simple,
name: 'cls',
type: classType
});
let builtInTupleType = ScopeUtils.getBuiltInType(this._scope, 'tuple');
if (builtInTupleType instanceof ClassType) {
let tupleType = new TupleType(builtInTupleType);
let constructorType = new FunctionType(FunctionTypeFlags.ClassMethod);
constructorType.setDeclaredReturnType(new ObjectType(classType));
constructorType.addParameter({
category: ParameterCategory.Simple,
name: 'cls',
type: classType
});
let initType = new FunctionType(FunctionTypeFlags.InstanceMethod);
const selfParameter: FunctionParameter = {
category: ParameterCategory.Simple,
name: 'self',
type: new ObjectType(classType)
};
initType.setDeclaredReturnType(NoneType.create());
initType.addParameter(selfParameter);
let initType = new FunctionType(FunctionTypeFlags.InstanceMethod);
const selfParameter: FunctionParameter = {
category: ParameterCategory.Simple,
name: 'self',
type: new ObjectType(classType)
};
initType.setDeclaredReturnType(NoneType.create());
initType.addParameter(selfParameter);
let addGenericGetAttribute = false;
let addGenericGetAttribute = false;
if (node.arguments.length < 2) {
this._addError('Expected named tuple entry list as second parameter',
node.leftExpression);
addGenericGetAttribute = true;
} else {
const entriesArg = node.arguments[1];
if (entriesArg.argumentCategory !== ArgumentCategory.Simple) {
if (node.arguments.length < 2) {
this._addError('Expected named tuple entry list as second parameter',
node.leftExpression);
addGenericGetAttribute = true;
} else {
if (!includesTypes && entriesArg.valueExpression instanceof StringNode) {
let entries = entriesArg.valueExpression.getValue().split(' ');
entries.forEach(entryName => {
entryName = entryName.trim();
if (entryName) {
let entryType = UnknownType.create();
const entriesArg = node.arguments[1];
if (entriesArg.argumentCategory !== ArgumentCategory.Simple) {
addGenericGetAttribute = true;
} else {
if (!includesTypes && entriesArg.valueExpression instanceof StringNode) {
let entries = entriesArg.valueExpression.getValue().split(' ');
entries.forEach(entryName => {
entryName = entryName.trim();
if (entryName) {
let entryType = UnknownType.create();
tupleType.addEntryType(entryType);
const paramInfo: FunctionParameter = {
category: ParameterCategory.Simple,
name: entryName,
type: entryType
};
constructorType.addParameter(paramInfo);
initType.addParameter(paramInfo);
instanceFields.set(entryName, new Symbol(entryType, DefaultTypeSourceId));
}
});
} else if (entriesArg.valueExpression instanceof ListNode) {
const entryList = entriesArg.valueExpression;
let entryMap: { [name: string]: string } = {};
entryList.entries.forEach((entry, index) => {
let entryType: Type | undefined;
let entryNameNode: ExpressionNode | undefined;
let entryName = '';
if (includesTypes) {
// Handle the variant that includes name/type tuples.
if (entry instanceof TupleExpressionNode && entry.expressions.length === 2) {
entryNameNode = entry.expressions[0];
let entryTypeInfo = this._getTypeFromExpression(entry.expressions[1],
EvaluatorFlags.ConvertClassToObject);
if (entryTypeInfo) {
entryType = entryTypeInfo.type;
}
} else {
this._addError(
'Expected two-entry tuple specifying entry name and type', entry);
}
} else {
entryNameNode = entry;
entryType = UnknownType.create();
}
if (entryNameNode instanceof StringNode) {
entryName = entryNameNode.getValue();
if (!entryName) {
this._addError(
'Names within a named tuple cannot be empty', entryNameNode);
}
} else {
this._addError(
'Expected string literal for entry name', entryNameNode || entry);
}
if (!entryName) {
entryName = `_${ index.toString() }`;
}
if (entryMap[entryName]) {
this._addError(
'Names within a named tuple must be unique', entryNameNode || entry);
}
// Record names in a map to detect duplicates.
entryMap[entryName] = entryName;
if (!entryType) {
entryType = UnknownType.create();
}
tupleType.addEntryType(entryType);
const paramInfo: FunctionParameter = {
category: ParameterCategory.Simple,
@ -1226,111 +1293,45 @@ export class ExpressionEvaluator {
initType.addParameter(paramInfo);
instanceFields.set(entryName, new Symbol(entryType, DefaultTypeSourceId));
}
});
} else if (entriesArg.valueExpression instanceof ListNode) {
const entryList = entriesArg.valueExpression;
let entryMap: { [name: string]: string } = {};
entryList.entries.forEach((entry, index) => {
let entryType: Type | undefined;
let entryNameNode: ExpressionNode | undefined;
let entryName = '';
if (includesTypes) {
// Handle the variant that includes name/type tuples.
if (entry instanceof TupleExpressionNode && entry.expressions.length === 2) {
entryNameNode = entry.expressions[0];
let entryTypeInfo = this._getTypeFromExpression(entry.expressions[1],
EvaluatorFlags.ConvertClassToObject);
if (entryTypeInfo) {
entryType = entryTypeInfo.type;
}
} else {
this._addError(
'Expected two-entry tuple specifying entry name and type', entry);
}
} else {
entryNameNode = entry;
entryType = UnknownType.create();
}
if (entryNameNode instanceof StringNode) {
entryName = entryNameNode.getValue();
if (!entryName) {
this._addError(
'Names within a named tuple cannot be empty', entryNameNode);
}
} else {
this._addError(
'Expected string literal for entry name', entryNameNode || entry);
}
if (!entryName) {
entryName = `_${ index.toString() }`;
}
if (entryMap[entryName]) {
this._addError(
'Names within a named tuple must be unique', entryNameNode || entry);
}
// Record names in a map to detect duplicates.
entryMap[entryName] = entryName;
if (!entryType) {
entryType = UnknownType.create();
}
tupleType.addEntryType(entryType);
const paramInfo: FunctionParameter = {
category: ParameterCategory.Simple,
name: entryName,
type: entryType
};
constructorType.addParameter(paramInfo);
initType.addParameter(paramInfo);
instanceFields.set(entryName, new Symbol(entryType, DefaultTypeSourceId));
});
} else {
// A dynamic expression was used, so we can't evaluate
// the named tuple statically.
addGenericGetAttribute = true;
});
} else {
// A dynamic expression was used, so we can't evaluate
// the named tuple statically.
addGenericGetAttribute = true;
}
}
}
}
if (addGenericGetAttribute) {
TypeUtils.addDefaultFunctionParameters(constructorType);
TypeUtils.addDefaultFunctionParameters(initType);
}
if (addGenericGetAttribute) {
TypeUtils.addDefaultFunctionParameters(constructorType);
TypeUtils.addDefaultFunctionParameters(initType);
}
classFields.set('__new__', new Symbol(constructorType, DefaultTypeSourceId));
classFields.set('__init__', new Symbol(initType, DefaultTypeSourceId));
classFields.set('__new__', new Symbol(constructorType, DefaultTypeSourceId));
classFields.set('__init__', new Symbol(initType, DefaultTypeSourceId));
let keysItemType = new FunctionType(FunctionTypeFlags.None);
keysItemType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'list',
[ScopeUtils.getBuiltInObject(this._scope, 'str')]));
classFields.set('keys', new Symbol(keysItemType, DefaultTypeSourceId));
classFields.set('items', new Symbol(keysItemType, DefaultTypeSourceId));
let keysItemType = new FunctionType(FunctionTypeFlags.None);
keysItemType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'list',
[ScopeUtils.getBuiltInObject(this._scope, 'str')]));
classFields.set('keys', new Symbol(keysItemType, DefaultTypeSourceId));
classFields.set('items', new Symbol(keysItemType, DefaultTypeSourceId));
let lenType = new FunctionType(FunctionTypeFlags.InstanceMethod);
lenType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'int'));
lenType.addParameter(selfParameter);
classFields.set('__len__', new Symbol(lenType, DefaultTypeSourceId));
let lenType = new FunctionType(FunctionTypeFlags.InstanceMethod);
lenType.setDeclaredReturnType(ScopeUtils.getBuiltInObject(this._scope, 'int'));
lenType.addParameter(selfParameter);
classFields.set('__len__', new Symbol(lenType, DefaultTypeSourceId));
if (addGenericGetAttribute) {
let getAttribType = new FunctionType(FunctionTypeFlags.InstanceMethod);
getAttribType.setDeclaredReturnType(AnyType.create());
getAttribType.addParameter(selfParameter);
getAttribType.addParameter({
category: ParameterCategory.Simple,
name: 'name',
type: ScopeUtils.getBuiltInObject(this._scope, 'str')
});
classFields.set('__getattribute__', new Symbol(getAttribType, DefaultTypeSourceId));
if (addGenericGetAttribute) {
let getAttribType = new FunctionType(FunctionTypeFlags.InstanceMethod);
getAttribType.setDeclaredReturnType(AnyType.create());
getAttribType.addParameter(selfParameter);
getAttribType.addParameter({
category: ParameterCategory.Simple,
name: 'name',
type: ScopeUtils.getBuiltInObject(this._scope, 'str')
});
classFields.set('__getattribute__', new Symbol(getAttribType, DefaultTypeSourceId));
}
}
return classType;
@ -1356,9 +1357,56 @@ export class ExpressionEvaluator {
return { type, node };
}
private _getTypeFromUnaryExpression(node: UnaryExpressionNode, flags: EvaluatorFlags): TypeResult | undefined {
let exprType = this._getTypeFromExpression(node.expression, flags).type;
let type: Type;
if (exprType.isAny()) {
type = exprType;
} else if (exprType instanceof ObjectType) {
if (node.operator === OperatorType.Not) {
// The "not" operator always returns a boolean.
type = ScopeUtils.getBuiltInObject(this._scope, 'bool');
} else if (node.operator === OperatorType.BitwiseInvert) {
const intObjType = ScopeUtils.getBuiltInObject(this._scope, 'int');
if (intObjType.isSame(exprType)) {
type = intObjType;
} else {
// TODO - need to handle generic case.
type = UnknownType.create();
}
} else if (node.operator === OperatorType.Add || node.operator === OperatorType.Subtract) {
const intType = ScopeUtils.getBuiltInObject(this._scope, 'int');
const floatType = ScopeUtils.getBuiltInObject(this._scope, 'float');
const complexType = ScopeUtils.getBuiltInObject(this._scope, 'complex');
if (intType.isSame(exprType)) {
type = intType;
} else if (floatType.isSame(exprType)) {
type = floatType;
} else if (complexType.isSame(exprType)) {
type = complexType;
} else {
// TODO - need to handle generic case.
type = UnknownType.create();
}
} else {
// We should never get here.
this._addError('Unexpected unary operator', node);
type = UnknownType.create();
}
} else {
// TODO - need to handle additional types.
type = UnknownType.create();
}
return { type, node };
}
private _getTypeFromBinaryExpression(node: BinaryExpressionNode, flags: EvaluatorFlags): TypeResult | undefined {
let leftType = this._getTypeFromExpression(node.leftExpression, flags);
let rightType = this._getTypeFromExpression(node.rightExpression, flags);
let leftType = this._getTypeFromExpression(node.leftExpression, flags).type;
let rightType = this._getTypeFromExpression(node.rightExpression, flags).type;
// Is this an AND operator? If so, we can assume that the
// rightExpression won't be evaluated at runtime unless the
@ -1411,9 +1459,9 @@ export class ExpressionEvaluator {
let type: Type;
if (arithmeticOperatorMap[node.operator]) {
if (leftType.type.isAny() || rightType.type.isAny()) {
if (leftType.isAny() || rightType.isAny()) {
type = UnknownType.create();
} else if (leftType.type instanceof ObjectType && rightType.type instanceof ObjectType) {
} else if (leftType instanceof ObjectType && rightType instanceof ObjectType) {
const builtInClassTypes = this._getBuiltInClassTypes(['int', 'float', 'complex']);
const getTypeMatch = (classType: ClassType): boolean[] => {
let foundMatch = false;
@ -1424,8 +1472,8 @@ export class ExpressionEvaluator {
return foundMatch;
});
};
const leftClassMatches = getTypeMatch(leftType.type.getClassType());
const rightClassMatches = getTypeMatch(rightType.type.getClassType());
const leftClassMatches = getTypeMatch(leftType.getClassType());
const rightClassMatches = getTypeMatch(rightType.getClassType());
if (leftClassMatches[0] && rightClassMatches[0]) {
// If they're both int types, the result is an int.
@ -1449,15 +1497,17 @@ export class ExpressionEvaluator {
type = UnknownType.create();
}
} else if (bitwiseOperatorMap[node.operator]) {
if (leftType.type.isAny() || rightType.type.isAny()) {
if (leftType.isAny() || rightType.isAny()) {
type = UnknownType.create();
} else if (leftType.type instanceof ObjectType && rightType.type instanceof ObjectType) {
const intType = ScopeUtils.getBuiltInType(this._scope, 'int') as ClassType;
const leftIsInt = intType && leftType.type.getClassType().isSameGenericClass(intType);
const rightIsInt = intType && rightType.type.getClassType().isSameGenericClass(intType);
} else if (leftType instanceof ObjectType && rightType instanceof ObjectType) {
const intType = ScopeUtils.getBuiltInType(this._scope, 'int');
const leftIsInt = intType instanceof ClassType &&
leftType.getClassType().isSameGenericClass(intType);
const rightIsInt = intType instanceof ClassType &&
rightType.getClassType().isSameGenericClass(intType);
if (leftIsInt && rightIsInt) {
type = new ObjectType(intType);
type = new ObjectType(intType as ClassType);
} else {
// In all other cases, we need to look at the magic methods
// on the two types.
@ -1469,21 +1519,19 @@ export class ExpressionEvaluator {
type = UnknownType.create();
}
} else if (comparisonOperatorMap[node.operator]) {
const boolType = ScopeUtils.getBuiltInType(this._scope, 'bool') as ClassType;
type = boolType ? new ObjectType(boolType) : UnknownType.create();
type = ScopeUtils.getBuiltInObject(this._scope, 'bool');
} else if (booleanOperatorMap[node.operator]) {
if (node.operator === OperatorType.And) {
// If the operator is an AND or OR, we need to combine the two types.
type = TypeUtils.combineTypesArray([
TypeUtils.removeTruthinessFromType(leftType.type), rightType.type]);
type = TypeUtils.combineTypes([
TypeUtils.removeTruthinessFromType(leftType), rightType]);
} else if (node.operator === OperatorType.Or) {
type = TypeUtils.combineTypesArray([
TypeUtils.removeFalsinessFromType(leftType.type), rightType.type]);
type = TypeUtils.combineTypes([
TypeUtils.removeFalsinessFromType(leftType), rightType]);
} else {
// The other boolean operators always return a bool value.
// TODO - validate inputs for "is", "is not", "in" and "not in" operators.
const boolType = ScopeUtils.getBuiltInType(this._scope, 'bool') as ClassType;
type = boolType ? new ObjectType(boolType) : UnknownType.create();
type = ScopeUtils.getBuiltInObject(this._scope, 'bool');
}
} else {
// We should never get here.
@ -1496,7 +1544,8 @@ export class ExpressionEvaluator {
private _getBuiltInClassTypes(names: string[]): (ClassType | undefined)[] {
return names.map(name => {
return ScopeUtils.getBuiltInType(this._scope, name) as ClassType;
let classType = ScopeUtils.getBuiltInType(this._scope, name);
return classType instanceof ClassType ? classType : undefined;
});
}
@ -1606,7 +1655,7 @@ export class ExpressionEvaluator {
return UnknownType.create();
}
return TypeUtils.combineTypes(typeArgs[0].type, NoneType.create());
return TypeUtils.combineTypes([typeArgs[0].type, NoneType.create()]);
}
private _createClassVarType(typeArgs: TypeResult[], flags: EvaluatorFlags): Type {
@ -1646,7 +1695,7 @@ export class ExpressionEvaluator {
}
if (types.length > 0) {
return TypeUtils.combineTypesArray(types);
return TypeUtils.combineTypes(types);
}
return NoneType.create();

Просмотреть файл

@ -70,7 +70,7 @@ export class InferredType {
if (!newCombinedType) {
newCombinedType = source.type;
} else {
newCombinedType = TypeUtils.combineTypes(newCombinedType, source.type);
newCombinedType = TypeUtils.combineTypes([newCombinedType, source.type]);
}
}

Просмотреть файл

@ -217,14 +217,14 @@ export class Scope {
// is properly merged later.
const targetSymbol = targetScope._symbolTable.get(name);
if (targetSymbol) {
newType = TypeUtils.combineTypes(newType, targetSymbol.currentType);
newType = TypeUtils.combineTypes([newType, targetSymbol.currentType]);
} else {
markTypeConditional = true;
}
} else {
let existingBinding = targetScope.lookUpSymbolRecursive(name);
if (existingBinding) {
newType = TypeUtils.combineTypes(newType, existingBinding.symbol.currentType);
newType = TypeUtils.combineTypes([newType, existingBinding.symbol.currentType]);
}
}
}
@ -286,7 +286,7 @@ export class Scope {
if (targetSymbol) {
this.setSymbolCurrentType(name,
TypeUtils.combineTypes(targetSymbol.currentType, sourceSymbol.currentType),
TypeUtils.combineTypes([targetSymbol.currentType, sourceSymbol.currentType]),
sourceSymbol.inferredType.getPrimarySourceId());
if (sourceSymbol.declarations) {

Просмотреть файл

@ -224,7 +224,8 @@ export class TypeAnalyzer extends ParseTreeWalker {
// TODO - tighten this up, perhaps using a config flag
if (param.defaultValue instanceof ConstantNode) {
if (param.defaultValue.token.keywordType === KeywordType.None) {
annotatedType = TypeUtils.combineTypes(annotatedType, NoneType.create());
annotatedType = TypeUtils.combineTypes(
[annotatedType, NoneType.create()]);
}
}
@ -1271,7 +1272,8 @@ export class TypeAnalyzer extends ParseTreeWalker {
// name, but there's also now an instance variable introduced. Combine the
// type of the class variable with that of the new instance variable.
if (memberInfo.symbol && !memberInfo.isInstanceMember && isInstanceMember) {
typeOfExpr = TypeUtils.combineTypes(typeOfExpr, TypeUtils.getEffectiveTypeOfMember(memberInfo));
typeOfExpr = TypeUtils.combineTypes(
[typeOfExpr, TypeUtils.getEffectiveTypeOfMember(memberInfo)]);
}
addNewMemberToLocalClass = true;
}

Просмотреть файл

@ -121,14 +121,7 @@ export class TruthyTypeConstraint extends TypeConstraint {
types = types.filter(t => TypeUtils.canBeFalsy(t));
}
if (types.length === 0) {
// Use a "Never" type (which is a special form
// of None) to indicate that the condition will
// always evaluate to false.
return NeverType.create();
} else {
return TypeUtils.combineTypesArray(types);
}
return TypeUtils.combineTypes(types);
}
// Return the original type.
@ -159,14 +152,7 @@ export class IsNoneTypeConstraint extends TypeConstraint {
return (t instanceof NoneType) === this.isPositiveTest();
});
if (remainingTypes.length === 0) {
// Use a "Never" type (which is a special form
// of None) to indicate that the condition will
// always evaluate to false.
return NeverType.create();
}
return TypeUtils.combineTypesArray(remainingTypes);
return TypeUtils.combineTypes(remainingTypes);
} else if (type instanceof NoneType) {
if (!this.isPositiveTest()) {
// Use a "Never" type (which is a special form
@ -237,14 +223,7 @@ export class IsInstanceTypeConstraint extends TypeConstraint {
};
const finalizeFilteredTypeList = (types: Type[]): Type => {
if (types.length === 0) {
// Use a "Never" type (which is a special form
// of None) to indicate that the condition will
// always evaluate to false.
return NeverType.create();
}
return TypeUtils.combineTypesArray(types);
return TypeUtils.combineTypes(types);
};
if (type instanceof ObjectType) {

Просмотреть файл

@ -24,38 +24,21 @@ export interface ClassMember {
}
export class TypeUtils {
// Combines two types into a single type. If the types are
// Combines multiple types into a single type. If the types are
// the same, only one is returned. If they differ, they
// are combined into a UnionType.
static combineTypes(type1: Type, type2: Type): Type {
if (type1.isSame(type2)) {
return type1;
// are combined into a UnionType. NeverTypes are filtered out.
// If no types remain in the end, a NeverType is returned.
static combineTypes(types: Type[]): Type {
// Filter out any "Never" types.
types = types.filter(type => type.category !== TypeCategory.Never);
if (types.length === 0) {
return NeverType.create();
}
let unionType = new UnionType();
if (type1 instanceof UnionType) {
unionType.addTypes(type1.getTypes());
} else {
unionType.addTypes([type1]);
}
if (type2 instanceof UnionType) {
unionType.addTypes(type2.getTypes());
} else {
unionType.addTypes([type2]);
}
return unionType;
}
static combineTypesArray(types: Type[]): Type {
assert(types.length > 0);
let resultingType = types[0];
types.forEach((t, index) => {
if (index > 0) {
resultingType = this.combineTypes(resultingType, t);
resultingType = this._combineTwoTypes(resultingType, t);
}
});
@ -596,7 +579,7 @@ export class TypeUtils {
recursionLevel + 1));
});
return TypeUtils.combineTypesArray(subtypes);
return TypeUtils.combineTypes(subtypes);
}
if (type instanceof ObjectType) {
@ -690,7 +673,7 @@ export class TypeUtils {
this.specializeType(constraint, undefined, recursionLevel + 1)
);
return TypeUtils.combineTypesArray(concreteTypes);
return TypeUtils.combineTypes(concreteTypes);
}
private static _specializeFunctionType(functionType: FunctionType,
@ -954,7 +937,7 @@ export class TypeUtils {
return AnyType.create();
}
return TypeUtils.combineTypesArray(subtypes);
return TypeUtils.combineTypes(subtypes);
}
static cloneTypeVarMap(typeVarMap: TypeVarMap): TypeVarMap {
@ -1018,7 +1001,7 @@ export class TypeUtils {
return NeverType.create();
}
return this.combineTypesArray(remainingTypes);
return this.combineTypes(remainingTypes);
}
// Filters a type such that that it is guaranteed not to
@ -1059,6 +1042,28 @@ export class TypeUtils {
return NeverType.create();
}
return this.combineTypesArray(remainingTypes);
return this.combineTypes(remainingTypes);
}
private static _combineTwoTypes(type1: Type, type2: Type): Type {
if (type1.isSame(type2)) {
return type1;
}
let unionType = new UnionType();
if (type1 instanceof UnionType) {
unionType.addTypes(type1.getTypes());
} else {
unionType.addTypes([type1]);
}
if (type2 instanceof UnionType) {
unionType.addTypes(type2.getTypes());
} else {
unionType.addTypes([type2]);
}
return unionType;
}
}

Просмотреть файл

@ -966,6 +966,7 @@ export class UnionType extends Type {
addType(type1: Type) {
assert(type1.category !== TypeCategory.Union);
assert(type1.category !== TypeCategory.Never);
this._types.push(type1);
}
@ -974,6 +975,7 @@ export class UnionType extends Type {
// Add any types that are unique to the union.
for (let newType of types) {
assert(newType.category !== TypeCategory.Union);
assert(newType.category !== TypeCategory.Never);
if (!this._types.find(t => t.isSame(newType))) {
this._types.push(newType);
}

Просмотреть файл

@ -0,0 +1,26 @@
# This sample tests various unary expressions.
def returnsFloat1() -> float:
a = 1
b = not a
# This should generate an error because bool
# cannot be assigned to a float.
return b
def returnsInt1() -> int:
a = 1
b = -a
return b
def returnsInt2() -> int:
a = 1
b = +a
return b
def returnsInt3() -> int:
a = 4
b = ~a
return b

Просмотреть файл

@ -138,3 +138,10 @@ test('Expressions2', () => {
assert.equal(analysisResults.length, 1);
assert.equal(analysisResults[0].errors.length, 1);
});
test('Expressions3', () => {
let analysisResults = TestUtils.typeAnalyzeSampleFiles(['expressions3.py']);
assert.equal(analysisResults.length, 1);
assert.equal(analysisResults[0].errors.length, 1);
});