diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dae18a9dd534a..0c560aa4970f2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -51,6 +51,7 @@ namespace ts { const languageVersion = compilerOptions.target || ScriptTarget.ES3; const modulekind = getEmitModuleKind(compilerOptions); const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System; + const strictNullChecks = compilerOptions.strictNullChecks; const emitResolver = createResolver(); @@ -68,10 +69,7 @@ namespace ts { isUnknownSymbol: symbol => symbol === unknownSymbol, getDiagnostics, getGlobalDiagnostics, - - // The language service will always care about the narrowed type of a symbol, because that is - // the type the language says the symbol should have. - getTypeOfSymbolAtLocation: getNarrowedTypeOfSymbol, + getTypeOfSymbolAtLocation, getSymbolsOfParameterPropertyDeclaration, getDeclaredTypeOfSymbol, getPropertiesOfType, @@ -109,14 +107,16 @@ namespace ts { const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); + const nullableWideningFlags = strictNullChecks ? 0 : TypeFlags.ContainsUndefinedOrNull; const anyType = createIntrinsicType(TypeFlags.Any, "any"); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); const booleanType = createIntrinsicType(TypeFlags.Boolean, "boolean"); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); - const nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined | nullableWideningFlags, "undefined"); + const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null"); + const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined"); const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -229,6 +229,7 @@ namespace ts { const subtypeRelation: Map = {}; const assignableRelation: Map = {}; + const comparableRelation: Map = {}; const identityRelation: Map = {}; // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. @@ -2703,6 +2704,11 @@ namespace ts { type = createArrayType(elementType); } } + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + if (strictNullChecks && declaration.initializer && !(getNullableKind(checkExpressionCached(declaration.initializer)) & TypeFlags.Undefined)) { + type = removeNullableKind(type, TypeFlags.Undefined); + } return type; } @@ -2773,7 +2779,8 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - return getTypeFromTypeNode(declaration.type); + const type = getTypeFromTypeNode(declaration.type); + return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type; } if (declaration.kind === SyntaxKind.Parameter) { @@ -2788,7 +2795,7 @@ namespace ts { // Use contextual parameter type if one is available const type = getContextuallyTypedParameterType(declaration); if (type) { - return type; + return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type; } } @@ -3498,6 +3505,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.StringLiteralType: return true; case SyntaxKind.ArrayType: @@ -4507,10 +4516,12 @@ namespace ts { // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type // of an object literal or the anyFunctionType. This is because there are operations in the type checker // that care about the presence of such types at arbitrary depth in a containing type. - function getPropagatingFlagsOfTypes(types: Type[]): TypeFlags { + function getPropagatingFlagsOfTypes(types: Type[], excludeKinds: TypeFlags): TypeFlags { let result: TypeFlags = 0; for (const type of types) { - result |= type.flags; + if (!(type.flags & excludeKinds)) { + result |= type.flags; + } } return result & TypeFlags.PropagatingFlags; } @@ -4519,7 +4530,8 @@ namespace ts { const id = getTypeListId(typeArguments); let type = target.instantiations[id]; if (!type) { - const flags = TypeFlags.Reference | (typeArguments ? getPropagatingFlagsOfTypes(typeArguments) : 0); + const propagatedFlags = typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; + const flags = TypeFlags.Reference | propagatedFlags; type = target.instantiations[id] = createObjectType(flags, target.symbol); type.target = target; type.typeArguments = typeArguments; @@ -4779,7 +4791,8 @@ namespace ts { } function createNewTupleType(elementTypes: Type[]) { - const type = createObjectType(TypeFlags.Tuple | getPropagatingFlagsOfTypes(elementTypes)); + const propagatedFlags = getPropagatingFlagsOfTypes(elementTypes, /*excludeKinds*/ 0); + const type = createObjectType(TypeFlags.Tuple | propagatedFlags); type.elementTypes = elementTypes; return type; } @@ -4792,10 +4805,21 @@ namespace ts { return links.resolvedType; } - function addTypeToSet(typeSet: Type[], type: Type, typeSetKind: TypeFlags) { + interface TypeSet extends Array { + containsAny?: boolean; + containsUndefined?: boolean; + containsNull?: boolean; + } + + function addTypeToSet(typeSet: TypeSet, type: Type, typeSetKind: TypeFlags) { if (type.flags & typeSetKind) { addTypesToSet(typeSet, (type).types, typeSetKind); } + else if (type.flags & (TypeFlags.Any | TypeFlags.Undefined | TypeFlags.Null)) { + if (type.flags & TypeFlags.Any) typeSet.containsAny = true; + if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true; + if (type.flags & TypeFlags.Null) typeSet.containsNull = true; + } else if (!contains(typeSet, type)) { typeSet.push(type); } @@ -4803,7 +4827,7 @@ namespace ts { // Add the given types to the given type set. Order is preserved, duplicates are removed, // and nested types of the given kind are flattened into the set. - function addTypesToSet(typeSet: Type[], types: Type[], typeSetKind: TypeFlags) { + function addTypesToSet(typeSet: TypeSet, types: Type[], typeSetKind: TypeFlags) { for (const type of types) { addTypeToSet(typeSet, type, typeSetKind); } @@ -4828,25 +4852,6 @@ namespace ts { } } - function containsTypeAny(types: Type[]): boolean { - for (const type of types) { - if (isTypeAny(type)) { - return true; - } - } - return false; - } - - function removeAllButLast(types: Type[], typeToRemove: Type) { - let i = types.length; - while (i > 0 && types.length > 1) { - i--; - if (types[i] === typeToRemove) { - types.splice(i, 1); - } - } - } - // We reduce the constituent type set to only include types that aren't subtypes of other types, unless // the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on // object identity. Subtype reduction is possible only when union types are known not to circularly @@ -4858,25 +4863,29 @@ namespace ts { if (types.length === 0) { return emptyUnionType; } - const typeSet: Type[] = []; + const typeSet = [] as TypeSet; addTypesToSet(typeSet, types, TypeFlags.Union); - if (containsTypeAny(typeSet)) { + if (typeSet.containsAny) { return anyType; } - if (noSubtypeReduction) { - removeAllButLast(typeSet, undefinedType); - removeAllButLast(typeSet, nullType); + if (strictNullChecks) { + if (typeSet.containsNull) typeSet.push(nullType); + if (typeSet.containsUndefined) typeSet.push(undefinedType); } - else { + if (!noSubtypeReduction) { removeSubtypes(typeSet); } - if (typeSet.length === 1) { + if (typeSet.length === 0) { + return typeSet.containsNull ? nullType : undefinedType; + } + else if (typeSet.length === 1) { return typeSet[0]; } const id = getTypeListId(typeSet); let type = unionTypes[id]; if (!type) { - type = unionTypes[id] = createObjectType(TypeFlags.Union | getPropagatingFlagsOfTypes(typeSet)); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); + type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); type.types = typeSet; } return type; @@ -4899,18 +4908,23 @@ namespace ts { if (types.length === 0) { return emptyObjectType; } - const typeSet: Type[] = []; + const typeSet = [] as TypeSet; addTypesToSet(typeSet, types, TypeFlags.Intersection); - if (containsTypeAny(typeSet)) { + if (typeSet.containsAny) { return anyType; } + if (strictNullChecks) { + if (typeSet.containsNull) typeSet.push(nullType); + if (typeSet.containsUndefined) typeSet.push(undefinedType); + } if (typeSet.length === 1) { return typeSet[0]; } const id = getTypeListId(typeSet); let type = intersectionTypes[id]; if (!type) { - type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | getPropagatingFlagsOfTypes(typeSet)); + const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); + type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | propagatedFlags); type.types = typeSet; } return type; @@ -5006,6 +5020,10 @@ namespace ts { return esSymbolType; case SyntaxKind.VoidKeyword: return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword: + return nullType; case SyntaxKind.ThisType: return getTypeFromThisTypeNode(node); case SyntaxKind.StringLiteralType: @@ -5379,6 +5397,10 @@ namespace ts { return checkTypeAssignableTo(source, target, /*errorNode*/ undefined); } + function isTypeComparableTo(source: Type, target: Type): boolean { + return checkTypeComparableTo(source, target, /*errorNode*/ undefined); + } + function checkTypeSubtypeOf(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain); } @@ -5387,6 +5409,10 @@ namespace ts { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } + function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { @@ -5423,8 +5449,8 @@ namespace ts { const sourceParams = source.parameters; const targetParams = target.parameters; for (let i = 0; i < checkCount; i++) { - const s = i < sourceMax ? getTypeOfSymbol(sourceParams[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfSymbol(targetParams[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); const related = compareTypes(s, t, /*reportErrors*/ false) || compareTypes(t, s, reportErrors); if (!related) { if (reportErrors) { @@ -5544,7 +5570,7 @@ namespace ts { * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'assignableRelation', or 'subTypeRelation'. + * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. * Used as both to determine which checks are performed and as a cache of previously computed results. * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. @@ -5609,8 +5635,12 @@ namespace ts { } if (isTypeAny(target)) return Ternary.True; - if (source === undefinedType) return Ternary.True; - if (source === nullType && target !== undefinedType) return Ternary.True; + if (source.flags & TypeFlags.Undefined) { + if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True; + } + if (source.flags & TypeFlags.Null) { + if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True; + } if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True; if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) { if (result = enumRelatedTo(source, target)) { @@ -5618,7 +5648,7 @@ namespace ts { } } if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True; - if (relation === assignableRelation) { + if (relation === assignableRelation || relation === comparableRelation) { if (isTypeAny(source)) return Ternary.True; if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True; } @@ -5646,8 +5676,15 @@ namespace ts { // Note that the "each" checks must precede the "some" checks to produce the correct results if (source.flags & TypeFlags.Union) { - if (result = eachTypeRelatedToType(source, target, reportErrors)) { - return result; + if (relation === comparableRelation) { + if (result = someTypeRelatedToType(source, target, reportErrors)) { + return result; + } + } + else { + if (result = eachTypeRelatedToType(source, target, reportErrors)) { + return result; + } } } else if (target.flags & TypeFlags.Intersection) { @@ -5754,7 +5791,8 @@ namespace ts { function isKnownProperty(type: Type, name: string): boolean { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) || + if ((relation === assignableRelation || relation === comparableRelation) && + (type === globalObjectType || resolved.properties.length === 0) || resolved.stringIndexInfo || resolved.numberIndexInfo || getPropertyOfType(type, name)) { return true; } @@ -5804,7 +5842,19 @@ namespace ts { function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { const targetTypes = target.types; - for (let i = 0, len = targetTypes.length; i < len; i++) { + let len = targetTypes.length; + // The null and undefined types are guaranteed to be at the end of the constituent type list. In order + // to produce the best possible errors we first check the nullable types, such that the last type we + // check and report errors from is a non-nullable type if one is present. + while (len >= 2 && targetTypes[len - 1].flags & TypeFlags.Nullable) { + const related = isRelatedTo(source, targetTypes[len - 1], /*reportErrors*/ false); + if (related) { + return related; + } + len--; + } + // Now check the non-nullable types and report errors on the last one. + for (let i = 0; i < len; i++) { const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); if (related) { return related; @@ -5828,7 +5878,19 @@ namespace ts { function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { const sourceTypes = source.types; - for (let i = 0, len = sourceTypes.length; i < len; i++) { + let len = sourceTypes.length; + // The null and undefined types are guaranteed to be at the end of the constituent type list. In order + // to produce the best possible errors we first check the nullable types, such that the last type we + // check and report errors from is a non-nullable type if one is present. + while (len >= 2 && sourceTypes[len - 1].flags & TypeFlags.Nullable) { + const related = isRelatedTo(sourceTypes[len - 1], target, /*reportErrors*/ false); + if (related) { + return related; + } + len--; + } + // Now check the non-nullable types and report errors on the last one. + for (let i = 0; i < len; i++) { const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1); if (related) { return related; @@ -6368,8 +6430,8 @@ namespace ts { let result = Ternary.True; const targetLen = target.parameters.length; for (let i = 0; i < targetLen; i++) { - const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); - const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); + const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfParameter(source.parameters[i]); + const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfParameter(target.parameters[i]); const related = compareTypes(s, t); if (!related) { return Ternary.False; @@ -6387,14 +6449,30 @@ namespace ts { } function isSupertypeOfEach(candidate: Type, types: Type[]): boolean { - for (const type of types) { - if (candidate !== type && !isTypeSubtypeOf(type, candidate)) return false; + for (const t of types) { + if (candidate !== t && !isTypeSubtypeOf(t, candidate)) return false; } return true; } + function getCombinedFlagsOfTypes(types: Type[]) { + let flags: TypeFlags = 0; + for (const t of types) { + flags |= t.flags; + } + return flags; + } + function getCommonSupertype(types: Type[]): Type { - return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); + if (!strictNullChecks) { + return forEach(types, t => isSupertypeOfEach(t, types) ? t : undefined); + } + const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); + if (!primaryTypes.length) { + return getUnionType(types); + } + const supertype = forEach(primaryTypes, t => isSupertypeOfEach(t, primaryTypes) ? t : undefined); + return supertype && addNullableKind(supertype, getCombinedFlagsOfTypes(types) & TypeFlags.Nullable); } function reportNoCommonSupertypeError(types: Type[], errorLocation: Node, errorMessageChainHead: DiagnosticMessageChain): void { @@ -6446,7 +6524,7 @@ namespace ts { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray return type.flags & TypeFlags.Reference && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType) || - !(type.flags & (TypeFlags.Undefined | TypeFlags.Null)) && isTypeAssignableTo(type, anyReadonlyArrayType); + !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isTupleLikeType(type: Type): boolean { @@ -6465,6 +6543,63 @@ namespace ts { return !!(type.flags & TypeFlags.Tuple); } + function getNullableKind(type: Type): TypeFlags { + let flags = type.flags; + if (flags & TypeFlags.Union) { + for (const t of (type as UnionType).types) { + flags |= t.flags; + } + } + return flags & TypeFlags.Nullable; + } + + function getNullableTypeOfKind(kind: TypeFlags) { + return kind & TypeFlags.Null ? kind & TypeFlags.Undefined ? + getUnionType([nullType, undefinedType]) : nullType : undefinedType; + } + + function addNullableKind(type: Type, kind: TypeFlags): Type { + if ((getNullableKind(type) & kind) !== kind) { + const types = [type]; + if (kind & TypeFlags.Undefined) { + types.push(undefinedType); + } + if (kind & TypeFlags.Null) { + types.push(nullType); + } + type = getUnionType(types); + } + return type; + } + + function removeNullableKind(type: Type, kind: TypeFlags) { + if (type.flags & TypeFlags.Union && getNullableKind(type) & kind) { + let firstType: Type; + let types: Type[]; + for (const t of (type as UnionType).types) { + if (!(t.flags & kind)) { + if (!firstType) { + firstType = t; + } + else { + if (!types) { + types = [firstType]; + } + types.push(t); + } + } + } + if (firstType) { + type = types ? getUnionType(types) : firstType; + } + } + return type; + } + + function getNonNullableType(type: Type): Type { + return strictNullChecks ? removeNullableKind(type, TypeFlags.Nullable) : type; + } + /** * Return true if type was inferred from an object literal or written as an object type literal * with no call or construct signatures. @@ -6518,16 +6653,20 @@ namespace ts { numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); } + function getWidenedConstituentType(type: Type): Type { + return type.flags & TypeFlags.Nullable ? type : getWidenedType(type); + } + function getWidenedType(type: Type): Type { if (type.flags & TypeFlags.RequiresWidening) { - if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { + if (type.flags & TypeFlags.Nullable) { return anyType; } if (type.flags & TypeFlags.ObjectLiteral) { return getWidenedTypeOfObjectLiteral(type); } if (type.flags & TypeFlags.Union) { - return getUnionType(map((type).types, getWidenedType), /*noSubtypeReduction*/ true); + return getUnionType(map((type).types, getWidenedConstituentType), /*noSubtypeReduction*/ true); } if (isArrayType(type)) { return createArrayType(getWidenedType((type).typeArguments[0])); @@ -6645,8 +6784,8 @@ namespace ts { count = sourceMax < targetMax ? sourceMax : targetMax; } for (let i = 0; i < count; i++) { - const s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfParameter(source.parameters[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfParameter(target.parameters[i]) : getRestTypeOfSignature(target); callback(s, t); } } @@ -6958,10 +7097,22 @@ namespace ts { // EXPRESSION TYPE CHECKING + function createTransientIdentifier(symbol: Symbol, location: Node): Identifier { + const result = createNode(SyntaxKind.Identifier); + result.text = symbol.name; + result.resolvedSymbol = symbol; + result.parent = location; + result.id = -1; + return result; + } + function getResolvedSymbol(node: Identifier): Symbol { + if (node.id === -1) { + return (node).resolvedSymbol; + } const links = getNodeLinks(node); if (!links.resolvedSymbol) { - links.resolvedSymbol = (!nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node)) || unknownSymbol; + links.resolvedSymbol = !nodeIsMissing(node) && resolveName(node, node.text, SymbolFlags.Value | SymbolFlags.ExportValue, Diagnostics.Cannot_find_name_0, node) || unknownSymbol; } return links.resolvedSymbol; } @@ -6985,58 +7136,74 @@ namespace ts { Debug.fail("should not get here"); } + // Return the assignment key for a "dotted name" (i.e. a sequence of identifiers + // separated by dots). The key consists of the id of the symbol referenced by the + // leftmost identifier followed by zero or more property names separated by dots. + // The result is undefined if the reference isn't a dotted name. + function getAssignmentKey(node: Node): string { + if (node.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol(node); + return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined; + } + if (node.kind === SyntaxKind.ThisKeyword) { + return "0"; + } + if (node.kind === SyntaxKind.PropertyAccessExpression) { + const key = getAssignmentKey((node).expression); + return key && key + "." + (node).name.text; + } + return undefined; + } + function hasInitializer(node: VariableLikeDeclaration): boolean { return !!(node.initializer || isBindingPattern(node.parent) && hasInitializer(node.parent.parent)); } - // Check if a given variable is assigned within a given syntax node - function isVariableAssignedWithin(symbol: Symbol, node: Node): boolean { - const links = getNodeLinks(node); - if (links.assignmentChecks) { - const cachedResult = links.assignmentChecks[symbol.id]; - if (cachedResult !== undefined) { - return cachedResult; - } - } - else { - links.assignmentChecks = {}; - } - return links.assignmentChecks[symbol.id] = isAssignedIn(node); + // For a given node compute a map of which dotted names are assigned within + // the node. + function getAssignmentMap(node: Node): Map { + const assignmentMap: Map = {}; + visit(node); + return assignmentMap; - function isAssignedInBinaryExpression(node: BinaryExpression) { - if (node.operatorToken.kind >= SyntaxKind.FirstAssignment && node.operatorToken.kind <= SyntaxKind.LastAssignment) { - const n = skipParenthesizedNodes(node.left); - if (n.kind === SyntaxKind.Identifier && getResolvedSymbol(n) === symbol) { - return true; + function visitReference(node: Identifier | PropertyAccessExpression) { + if (isAssignmentTarget(node) || isCompoundAssignmentTarget(node)) { + const key = getAssignmentKey(node); + if (key) { + assignmentMap[key] = true; } } - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); } - function isAssignedInVariableDeclaration(node: VariableLikeDeclaration) { - if (!isBindingPattern(node.name) && getSymbolOfNode(node) === symbol && hasInitializer(node)) { - return true; + function visitVariableDeclaration(node: VariableLikeDeclaration) { + if (!isBindingPattern(node.name) && hasInitializer(node)) { + assignmentMap[getSymbolId(getSymbolOfNode(node))] = true; } - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); } - function isAssignedIn(node: Node): boolean { + function visit(node: Node) { switch (node.kind) { - case SyntaxKind.BinaryExpression: - return isAssignedInBinaryExpression(node); + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + visitReference(node); + break; case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: - return isAssignedInVariableDeclaration(node); + visitVariableDeclaration(node); + break; + case SyntaxKind.BinaryExpression: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.DeleteExpression: @@ -7059,6 +7226,7 @@ namespace ts { case SyntaxKind.ReturnStatement: case SyntaxKind.WithStatement: case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: case SyntaxKind.LabeledStatement: @@ -7071,98 +7239,210 @@ namespace ts { case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxExpression: - return forEachChild(node, isAssignedIn); + forEachChild(node, visit); + break; + } + } + } + + function isReferenceAssignedWithin(reference: Node, node: Node): boolean { + if (reference.kind !== SyntaxKind.ThisKeyword) { + const key = getAssignmentKey(reference); + if (key) { + const links = getNodeLinks(node); + return (links.assignmentMap || (links.assignmentMap = getAssignmentMap(node)))[key]; + } + } + return false; + } + + function isAnyPartOfReferenceAssignedWithin(reference: Node, node: Node) { + while (true) { + if (isReferenceAssignedWithin(reference, node)) { + return true; + } + if (reference.kind !== SyntaxKind.PropertyAccessExpression) { + return false; + } + reference = (reference).expression; + } + } + + function isNullOrUndefinedLiteral(node: Expression) { + return node.kind === SyntaxKind.NullKeyword || + node.kind === SyntaxKind.Identifier && getResolvedSymbol(node) === undefinedSymbol; + } + + function getLeftmostIdentifierOrThis(node: Node): Node { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + return node; + case SyntaxKind.PropertyAccessExpression: + return getLeftmostIdentifierOrThis((node).expression); + } + return undefined; + } + + function isMatchingReference(source: Node, target: Node): boolean { + if (source.kind === target.kind) { + switch (source.kind) { + case SyntaxKind.Identifier: + return getResolvedSymbol(source) === getResolvedSymbol(target); + case SyntaxKind.ThisKeyword: + return true; + case SyntaxKind.PropertyAccessExpression: + return (source).name.text === (target).name.text && + isMatchingReference((source).expression, (target).expression); } - return false; } + return false; } // Get the narrowed type of a given symbol at a given location - function getNarrowedTypeOfSymbol(symbol: Symbol, node: Node) { - let type = getTypeOfSymbol(symbol); - // Only narrow when symbol is variable of type any or an object, union, or type parameter type - if (node && symbol.flags & SymbolFlags.Variable) { - if (isTypeAny(type) || type.flags & (TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter)) { - const declaration = getDeclarationOfKind(symbol, SyntaxKind.VariableDeclaration); - const top = declaration && getDeclarationContainer(declaration); - const originalType = type; - const nodeStack: {node: Node, child: Node}[] = []; - loop: while (node.parent) { - const child = node; - node = node.parent; - switch (node.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.BinaryExpression: - nodeStack.push({node, child}); - break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleDeclaration: - // Stop at the first containing file or module declaration - break loop; - } - if (node === top) { - break; + function getNarrowedTypeOfReference(type: Type, reference: Node) { + if (!(type.flags & (TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter))) { + return type; + } + const leftmostNode = getLeftmostIdentifierOrThis(reference); + if (!leftmostNode) { + return type; + } + let top: Node; + if (leftmostNode.kind === SyntaxKind.Identifier) { + const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostNode)); + if (!leftmostSymbol) { + return type; + } + const declaration = leftmostSymbol.valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { + return type; + } + top = getDeclarationContainer(declaration); + } + const originalType = type; + const nodeStack: { node: Node, child: Node }[] = []; + let node: Node = reference; + loop: while (node.parent) { + const child = node; + node = node.parent; + switch (node.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.BinaryExpression: + nodeStack.push({node, child}); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + break loop; + default: + if (node === top || isFunctionLikeKind(node.kind)) { + break loop; } - } + break; + } + } - let nodes: {node: Node, child: Node}; - while (nodes = nodeStack.pop()) { - const {node, child} = nodes; - switch (node.kind) { - case SyntaxKind.IfStatement: - // In a branch of an if statement, narrow based on controlling expression - if (child !== (node).expression) { - type = narrowType(type, (node).expression, /*assumeTrue*/ child === (node).thenStatement); - } - break; - case SyntaxKind.ConditionalExpression: - // In a branch of a conditional expression, narrow based on controlling condition - if (child !== (node).condition) { - type = narrowType(type, (node).condition, /*assumeTrue*/ child === (node).whenTrue); - } - break; - case SyntaxKind.BinaryExpression: - // In the right operand of an && or ||, narrow based on left operand - if (child === (node).right) { - if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - type = narrowType(type, (node).left, /*assumeTrue*/ true); - } - else if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { - type = narrowType(type, (node).left, /*assumeTrue*/ false); - } - } - break; - default: - Debug.fail("Unreachable!"); + let nodes: { node: Node, child: Node }; + while (nodes = nodeStack.pop()) { + const {node, child} = nodes; + switch (node.kind) { + case SyntaxKind.IfStatement: + // In a branch of an if statement, narrow based on controlling expression + if (child !== (node).expression) { + type = narrowType(type, (node).expression, /*assumeTrue*/ child === (node).thenStatement); } - - // Use original type if construct contains assignments to variable - if (type !== originalType && isVariableAssignedWithin(symbol, node)) { - type = originalType; + break; + case SyntaxKind.ConditionalExpression: + // In a branch of a conditional expression, narrow based on controlling condition + if (child !== (node).condition) { + type = narrowType(type, (node).condition, /*assumeTrue*/ child === (node).whenTrue); } - } + break; + case SyntaxKind.BinaryExpression: + // In the right operand of an && or ||, narrow based on left operand + if (child === (node).right) { + if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + type = narrowType(type, (node).left, /*assumeTrue*/ true); + } + else if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { + type = narrowType(type, (node).left, /*assumeTrue*/ false); + } + } + break; + default: + Debug.fail("Unreachable!"); + } - // Preserve old top-level behavior - if the branch is really an empty set, revert to prior type - if (type === emptyUnionType) { - type = originalType; - } + // Use original type if construct contains assignments to variable + if (type !== originalType && isAnyPartOfReferenceAssignedWithin(reference, node)) { + type = originalType; } } + // Preserve old top-level behavior - if the branch is really an empty set, revert to prior type + if (type === emptyUnionType) { + type = originalType; + } + return type; - function narrowTypeByEquality(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - // Check that we have 'typeof ' on the left and string literal on the right - if (expr.left.kind !== SyntaxKind.TypeOfExpression || expr.right.kind !== SyntaxKind.StringLiteral) { + function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { + return strictNullChecks && assumeTrue && isMatchingReference(expr, reference) ? getNonNullableType(type) : type; + } + + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + if (isNullOrUndefinedLiteral(expr.right)) { + return narrowTypeByNullCheck(type, expr, assumeTrue); + } + if (expr.left.kind === SyntaxKind.TypeOfExpression && expr.right.kind === SyntaxKind.StringLiteral) { + return narrowTypeByTypeof(type, expr, assumeTrue); + } + break; + case SyntaxKind.AmpersandAmpersandToken: + return narrowTypeByAnd(type, expr, assumeTrue); + case SyntaxKind.BarBarToken: + return narrowTypeByOr(type, expr, assumeTrue); + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr, assumeTrue); + } + return type; + } + + function narrowTypeByNullCheck(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '===', or '!==' operator with 'null' or 'undefined' on the right + const operator = expr.operatorToken.kind; + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } + if (!strictNullChecks || !isMatchingReference(expr.left, reference)) { return type; } + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const exprNullableKind = doubleEquals ? TypeFlags.Nullable : + expr.right.kind === SyntaxKind.NullKeyword ? TypeFlags.Null : TypeFlags.Undefined; + if (assumeTrue) { + const nullableKind = getNullableKind(type) & exprNullableKind; + return nullableKind ? getNullableTypeOfKind(nullableKind) : type; + } + return removeNullableKind(type, exprNullableKind); + } + + function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left + // and string literal on the right const left = expr.left; const right = expr.right; - if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(left.expression) !== symbol) { + if (!isMatchingReference(left.expression, reference)) { return type; } - if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { + if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsToken || + expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const typeInfo = primitiveTypeInfo[right.text]; @@ -7226,7 +7506,7 @@ namespace ts { function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { // Check that type is not any, assumed result is true, and we have variable symbol on the left - if (isTypeAny(type) || expr.left.kind !== SyntaxKind.Identifier || getResolvedSymbol(expr.left) !== symbol) { + if (isTypeAny(type) || !isMatchingReference(expr.left, reference)) { return type; } @@ -7284,7 +7564,8 @@ namespace ts { } } - if (isTypeAssignableTo(narrowedTypeCandidate, originalType)) { + const targetType = originalType.flags & TypeFlags.TypeParameter ? getApparentType(originalType) : originalType; + if (isTypeAssignableTo(narrowedTypeCandidate, targetType)) { // Narrow to the target type if it's assignable to the current type return narrowedTypeCandidate; } @@ -7297,68 +7578,43 @@ namespace ts { return type; } const signature = getResolvedSignature(callExpression); - const predicate = signature.typePredicate; if (!predicate) { return type; } - if (isIdentifierTypePredicate(predicate)) { - if (callExpression.arguments[predicate.parameterIndex] && - getSymbolAtTypePredicatePosition(callExpression.arguments[predicate.parameterIndex]) === symbol) { + const predicateArgument = callExpression.arguments[predicate.parameterIndex]; + if (predicateArgument && isMatchingReference(predicateArgument, reference)) { return getNarrowedType(type, predicate.type, assumeTrue); } } else { const invokedExpression = skipParenthesizedNodes(callExpression.expression); - return narrowTypeByThisTypePredicate(type, predicate, invokedExpression, assumeTrue); - } - return type; - } - - function narrowTypeByThisTypePredicate(type: Type, predicate: ThisTypePredicate, invokedExpression: Expression, assumeTrue: boolean): Type { - if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) { - const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression; - const possibleIdentifier = skipParenthesizedNodes(accessExpression.expression); - if (possibleIdentifier.kind === SyntaxKind.Identifier && getSymbolAtTypePredicatePosition(possibleIdentifier) === symbol) { - return getNarrowedType(type, predicate.type, assumeTrue); + if (invokedExpression.kind === SyntaxKind.ElementAccessExpression || invokedExpression.kind === SyntaxKind.PropertyAccessExpression) { + const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression; + const possibleReference= skipParenthesizedNodes(accessExpression.expression); + if (isMatchingReference(possibleReference, reference)) { + return getNarrowedType(type, predicate.type, assumeTrue); + } } } return type; } - function getSymbolAtTypePredicatePosition(expr: Expression): Symbol { - expr = skipParenthesizedNodes(expr); - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return getSymbolOfEntityNameOrPropertyAccessExpression(expr as (Identifier | PropertyAccessExpression)); - } - } - // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.PropertyAccessExpression: + return narrowTypeByTruthiness(type, expr, assumeTrue); case SyntaxKind.CallExpression: return narrowTypeByTypePredicate(type, expr, assumeTrue); case SyntaxKind.ParenthesizedExpression: return narrowType(type, (expr).expression, assumeTrue); case SyntaxKind.BinaryExpression: - const operator = (expr).operatorToken.kind; - if (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - return narrowTypeByEquality(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAnd(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.BarBarToken) { - return narrowTypeByOr(type, expr, assumeTrue); - } - else if (operator === SyntaxKind.InstanceOfKeyword) { - return narrowTypeByInstanceof(type, expr, assumeTrue); - } - break; + return narrowTypeByBinaryExpression(type, expr, assumeTrue); case SyntaxKind.PrefixUnaryExpression: if ((expr).operator === SyntaxKind.ExclamationToken) { return narrowType(type, (expr).operand, !assumeTrue); @@ -7369,6 +7625,30 @@ namespace ts { } } + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + // The language service will always care about the narrowed type of a symbol, because that is + // the type the language says the symbol should have. + const type = getTypeOfSymbol(symbol); + if (location.kind === SyntaxKind.Identifier) { + if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { + location = location.parent; + } + // If location is an identifier or property access that references the given + // symbol, use the location as the reference with respect to which we narrow. + if (isExpression(location)) { + checkExpression(location); + if (getNodeLinks(location).resolvedSymbol === symbol) { + return getNarrowedTypeOfReference(type, location); + } + } + } + // The location isn't a reference to the given symbol, meaning we're being asked + // a hypothetical question of what type the symbol would have if there was a reference + // to it at the given location. To answer that question we manufacture a transient + // identifier at the location and narrow with respect to that identifier. + return getNarrowedTypeOfReference(type, createTransientIdentifier(symbol, location)); + } + function skipParenthesizedNodes(expression: Expression): Expression { while (expression.kind === SyntaxKind.ParenthesizedExpression) { expression = (expression as ParenthesizedExpression).expression; @@ -7376,6 +7656,98 @@ namespace ts { return expression; } + function findFirstAssignment(symbol: Symbol, container: Node): Node { + return visit(isFunctionLike(container) ? (container).body : container); + + function visit(node: Node): Node { + switch (node.kind) { + case SyntaxKind.Identifier: + const assignment = getAssignmentRoot(node); + return assignment && getResolvedSymbol(node) === symbol ? assignment : undefined; + case SyntaxKind.BinaryExpression: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.PostfixUnaryExpression: + case SyntaxKind.YieldExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: + case SyntaxKind.VariableStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabeledStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.CatchClause: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxExpression: + case SyntaxKind.Block: + case SyntaxKind.SourceFile: + return forEachChild(node, visit); + } + return undefined; + } + } + + function checkVariableAssignedBefore(symbol: Symbol, reference: Node) { + if (!(symbol.flags & SymbolFlags.Variable)) { + return; + } + const declaration = symbol.valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration || (declaration).initializer) { + return; + } + const parentParentKind = declaration.parent.parent.kind; + if (parentParentKind === SyntaxKind.ForOfStatement || parentParentKind === SyntaxKind.ForInStatement) { + return; + } + const declarationContainer = getContainingFunction(declaration) || getSourceFileOfNode(declaration); + const referenceContainer = getContainingFunction(reference) || getSourceFileOfNode(reference); + if (declarationContainer !== referenceContainer) { + return; + } + const links = getSymbolLinks(symbol); + if (!links.firstAssignmentChecked) { + links.firstAssignmentChecked = true; + links.firstAssignment = findFirstAssignment(symbol, declarationContainer); + } + if (links.firstAssignment && links.firstAssignment.end <= reference.pos) { + return; + } + error(reference, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); @@ -7427,7 +7799,11 @@ namespace ts { checkCollisionWithCapturedThisVariable(node, node); checkNestedBlockScopedBinding(node, symbol); - return getNarrowedTypeOfSymbol(localOrExportSymbol, node); + const type = getTypeOfSymbol(localOrExportSymbol); + if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined)) { + checkVariableAssignedBefore(symbol, node); + } + return getNarrowedTypeOfReference(type, node); } function isInsideFunction(node: Node, threshold: Node): boolean { @@ -7645,7 +8021,8 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); - return container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; + const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; + return getNarrowedTypeOfReference(type, node); } if (isInJavaScriptFile(node)) { @@ -8338,19 +8715,40 @@ namespace ts { return mapper && mapper.context; } + // Return the root assignment node of an assignment target + function getAssignmentRoot(node: Node): Node { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + while (true) { + if (node.parent.kind === SyntaxKind.PropertyAssignment) { + node = node.parent.parent; + } + else if (node.parent.kind === SyntaxKind.ArrayLiteralExpression) { + node = node.parent; + } + else { + break; + } + } + const parent = node.parent; + return parent.kind === SyntaxKind.BinaryExpression && + (parent).operatorToken.kind === SyntaxKind.EqualsToken && + (parent).left === node ? parent : undefined; + } + // A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property // assignment in an object literal that is an assignment target, or if it is parented by an array literal that is // an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'. function isAssignmentTarget(node: Node): boolean { + return !!getAssignmentRoot(node); + } + + function isCompoundAssignmentTarget(node: Node) { const parent = node.parent; - if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node) { - return true; - } - if (parent.kind === SyntaxKind.PropertyAssignment) { - return isAssignmentTarget(parent.parent); - } - if (parent.kind === SyntaxKind.ArrayLiteralExpression) { - return isAssignmentTarget(parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { + const operator = (parent).operatorToken.kind; + return operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment; } return false; } @@ -8436,7 +8834,7 @@ namespace ts { } } } - return createArrayType(elementTypes.length ? getUnionType(elementTypes) : undefinedType); + return createArrayType(elementTypes.length ? getUnionType(elementTypes) : emptyArrayElementType); } function isNumericName(name: DeclarationName): boolean { @@ -9160,6 +9558,15 @@ namespace ts { return true; } + function checkNonNullExpression(node: Expression | QualifiedName) { + const type = checkExpression(node); + if (strictNullChecks && getNullableKind(type)) { + error(node, Diagnostics.Object_is_possibly_null_or_undefined); + return getNonNullableType(type); + } + return type; + } + function checkPropertyAccessExpression(node: PropertyAccessExpression) { return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name); } @@ -9169,7 +9576,7 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - const type = checkExpression(left); + const type = checkNonNullExpression(left); if (isTypeAny(type)) { return type; } @@ -9192,7 +9599,10 @@ namespace ts { if (prop.parent && prop.parent.flags & SymbolFlags.Class) { checkClassPropertyAccess(node, left, apparentType, prop); } - return getTypeOfSymbol(prop); + + const propType = getTypeOfSymbol(prop); + return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & SymbolFlags.Property ? + getNarrowedTypeOfReference(propType, node) : propType; } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { @@ -9277,7 +9687,7 @@ namespace ts { } // Obtain base constraint such that we can bail out if the constraint is an unknown type - const objectType = getApparentType(checkExpression(node.expression)); + const objectType = getApparentType(checkNonNullExpression(node.expression)); const indexType = node.argumentExpression ? checkExpression(node.argumentExpression) : unknownType; if (objectType === unknownType) { @@ -10292,7 +10702,7 @@ namespace ts { return resolveUntypedCall(node); } - const funcType = checkExpression(node.expression); + const funcType = checkNonNullExpression(node.expression); const apparentType = getApparentType(funcType); if (apparentType === unknownType) { @@ -10345,7 +10755,7 @@ namespace ts { } } - let expressionType = checkExpression(node.expression); + let expressionType = checkNonNullExpression(node.expression); // If expressionType's apparent type(section 3.8.1) is an object type with one or // more construct signatures, the expression is processed in the same manner as a @@ -10602,21 +11012,32 @@ namespace ts { const targetType = getTypeFromTypeNode(node.type); if (produceDiagnostics && targetType !== unknownType) { const widenedType = getWidenedType(exprType); - - // Permit 'number[] | "foo"' to be asserted to 'string'. - const bothAreStringLike = maybeTypeOfKind(targetType, TypeFlags.StringLike) && - maybeTypeOfKind(widenedType, TypeFlags.StringLike); - if (!bothAreStringLike && !(isTypeAssignableTo(targetType, widenedType))) { - checkTypeAssignableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); } } return targetType; } + function checkNonNullAssertion(node: NonNullExpression) { + return getNonNullableType(checkExpression(node.expression)); + } + + function getTypeOfParameter(symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && (declaration).initializer) { + return addNullableKind(type, TypeFlags.Undefined); + } + } + return type; + } + function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? - pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; + pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) : + pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; } function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { @@ -10742,7 +11163,8 @@ namespace ts { } } else { - types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper, isAsync); + const hasImplicitReturn = !!(func.flags & NodeFlags.HasImplicitReturn); + types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper, isAsync, hasImplicitReturn); if (types.length === 0) { if (isAsync) { // For an async function, the return type will not be void, but rather a Promise for void. @@ -10822,9 +11244,9 @@ namespace ts { return aggregatedTypes; } - function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper, isAsync?: boolean): Type[] { + function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper: TypeMapper, isAsync: boolean, hasImplicitReturn: boolean): Type[] { const aggregatedTypes: Type[] = []; - + let hasOmittedExpressions = false; forEachReturnStatement(body, returnStatement => { const expr = returnStatement.expression; if (expr) { @@ -10836,13 +11258,19 @@ namespace ts { // the native Promise type by the caller. type = checkAwaitedType(type, body.parent, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); } - if (!contains(aggregatedTypes, type)) { aggregatedTypes.push(type); } } + else { + hasOmittedExpressions = true; + } }); - + if (strictNullChecks && aggregatedTypes.length && (hasOmittedExpressions || hasImplicitReturn)) { + if (!contains(aggregatedTypes, undefinedType)) { + aggregatedTypes.push(undefinedType); + } + } return aggregatedTypes; } @@ -10879,6 +11307,9 @@ namespace ts { // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } + else if (returnType && strictNullChecks && !isTypeAssignableTo(undefinedType, returnType)) { + error(func.type, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } else if (compilerOptions.noImplicitReturns) { if (!returnType) { // If return type annotation is omitted check if function has any explicit return statements. @@ -11126,7 +11557,8 @@ namespace ts { return booleanType; case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: - const ok = checkArithmeticOperandType(node.operand, operandType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); + const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression(node.operand, @@ -11140,7 +11572,8 @@ namespace ts { function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { const operandType = checkExpression(node.operand); - const ok = checkArithmeticOperandType(node.operand, operandType, Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); + const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression(node.operand, @@ -11391,8 +11824,11 @@ namespace ts { // as having the primitive type Number. If one operand is the null or undefined value, // it is treated as having the type of the other operand. // The result is always of the Number primitive type. - if (leftType.flags & (TypeFlags.Undefined | TypeFlags.Null)) leftType = rightType; - if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType; + if (leftType.flags & TypeFlags.Nullable) leftType = rightType; + if (rightType.flags & TypeFlags.Nullable) rightType = leftType; + + leftType = getNonNullableType(leftType); + rightType = getNonNullableType(rightType); let suggestedOperator: SyntaxKind; // if a user tries to apply a bitwise operator to 2 boolean operands @@ -11419,8 +11855,11 @@ namespace ts { // or at least one of the operands to be of type Any or the String primitive type. // If one operand is the null or undefined value, it is treated as having the type of the other operand. - if (leftType.flags & (TypeFlags.Undefined | TypeFlags.Null)) leftType = rightType; - if (rightType.flags & (TypeFlags.Undefined | TypeFlags.Null)) rightType = leftType; + if (leftType.flags & TypeFlags.Nullable) leftType = rightType; + if (rightType.flags & TypeFlags.Nullable) rightType = leftType; + + leftType = getNonNullableType(leftType); + rightType = getNonNullableType(rightType); let resultType: Type; if (isTypeOfKind(leftType, TypeFlags.NumberLike) && isTypeOfKind(rightType, TypeFlags.NumberLike)) { @@ -11466,11 +11905,7 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - // Permit 'number[] | "foo"' to be asserted to 'string'. - if (maybeTypeOfKind(leftType, TypeFlags.StringLike) && maybeTypeOfKind(rightType, TypeFlags.StringLike)) { - return booleanType; - } - if (!isTypeAssignableTo(leftType, rightType) && !isTypeAssignableTo(rightType, leftType)) { + if (!isTypeComparableTo(leftType, rightType) && !isTypeComparableTo(rightType, leftType)) { reportOperatorError(); } return booleanType; @@ -11479,9 +11914,9 @@ namespace ts { case SyntaxKind.InKeyword: return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: - return rightType; + return addNullableKind(rightType, getNullableKind(leftType)); case SyntaxKind.BarBarToken: - return getUnionType([leftType, rightType]); + return getUnionType([getNonNullableType(leftType), rightType]); case SyntaxKind.EqualsToken: checkAssignmentOperator(rightType); return getRegularTypeOfObjectLiteral(rightType); @@ -11779,6 +12214,8 @@ namespace ts { case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return checkAssertion(node); + case SyntaxKind.NonNullExpression: + return checkNonNullAssertion(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: @@ -13648,7 +14085,7 @@ namespace ts { } } - const rightType = checkExpression(node.expression); + const rightType = checkNonNullExpression(node.expression); // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved // in this case error about missing name is already reported - do not report extra one if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.ObjectType | TypeFlags.TypeParameter)) { @@ -13668,7 +14105,7 @@ namespace ts { } function checkRightHandSideOfForOf(rhsExpression: Expression): Type { - const expressionType = getTypeOfExpression(rhsExpression); + const expressionType = checkNonNullExpression(rhsExpression); return checkIteratedTypeOrElementType(expressionType, rhsExpression, /*allowStringInput*/ true); } @@ -13938,8 +14375,8 @@ namespace ts { if (func) { const signature = getSignatureFromDeclaration(func); const returnType = getReturnTypeOfSignature(signature); - if (node.expression) { - const exprType = checkExpressionCached(node.expression); + if (strictNullChecks || node.expression) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (func.asteriskToken) { // A generator does not need its return expressions checked against its return type. @@ -13950,26 +14387,28 @@ namespace ts { } if (func.kind === SyntaxKind.SetAccessor) { - error(node.expression, Diagnostics.Setters_cannot_return_a_value); + if (node.expression) { + error(node.expression, Diagnostics.Setters_cannot_return_a_value); + } } else if (func.kind === SyntaxKind.Constructor) { - if (!checkTypeAssignableTo(exprType, returnType, node.expression)) { + if (node.expression && !checkTypeAssignableTo(exprType, returnType, node.expression)) { error(node.expression, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } else if (func.type || isGetAccessorWithAnnotatedSetAccessor(func)) { if (isAsyncFunctionLike(func)) { const promisedType = getPromisedType(returnType); - const awaitedType = checkAwaitedType(exprType, node.expression, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); + const awaitedType = checkAwaitedType(exprType, node.expression || node, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); if (promisedType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. - checkTypeAssignableTo(awaitedType, promisedType, node.expression); + checkTypeAssignableTo(awaitedType, promisedType, node.expression || node); } } else { - checkTypeAssignableTo(exprType, returnType, node.expression); + checkTypeAssignableTo(exprType, returnType, node.expression || node); } } } @@ -14000,7 +14439,6 @@ namespace ts { let hasDuplicateDefaultClause = false; const expressionType = checkExpression(node.expression); - const expressionTypeIsStringLike = maybeTypeOfKind(expressionType, TypeFlags.StringLike); forEach(node.caseBlock.clauses, clause => { // Grammar check for duplicate default clauses, skip if we already report duplicate default clause if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { @@ -14019,17 +14457,12 @@ namespace ts { if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { const caseClause = clause; // TypeScript 1.0 spec (April 2014):5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is assignable to or from the type of the 'switch' expression. + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. const caseType = checkExpression(caseClause.expression); - - const expressionTypeIsAssignableToCaseType = - // Permit 'number[] | "foo"' to be asserted to 'string'. - (expressionTypeIsStringLike && maybeTypeOfKind(caseType, TypeFlags.StringLike)) || - isTypeAssignableTo(expressionType, caseType); - - if (!expressionTypeIsAssignableToCaseType) { - // 'expressionType is not assignable to caseType', try the reversed check and report errors if it fails - checkTypeAssignableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); + if (!isTypeComparableTo(expressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, expressionType, caseClause.expression, /*headMessage*/ undefined); } } forEach(clause.statements, checkSourceElement); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 2f10a8103c492..c2d82112d106a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -331,6 +331,11 @@ namespace ts { name: "noImplicitUseStrict", type: "boolean", description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output + }, + { + name: "strictNullChecks", + type: "boolean", + description: Diagnostics.Enable_strict_null_checks } ]; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 93286db7e4675..76306e290ca22 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -242,8 +242,14 @@ namespace ts { const count = array.length; if (count > 0) { let pos = 0; - let result = arguments.length <= 2 ? array[pos] : initial; - pos++; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial; + } while (pos < count) { result = f(result, array[pos]); pos++; @@ -260,8 +266,14 @@ namespace ts { if (array) { let pos = array.length - 1; if (pos >= 0) { - let result = arguments.length <= 2 ? array[pos] : initial; - pos--; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos--; + } + else { + result = initial; + } while (pos >= 0) { result = f(result, array[pos]); pos--; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index c758d65888893..0a8b6615a23ec 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -367,6 +367,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.ThisType: case SyntaxKind.StringLiteralType: return writeTextOfNode(currentText, type); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b8dd62efa8e54..d7c2fac27102f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1103,6 +1103,10 @@ "category": "Error", "code": 2365 }, + "Function lacks ending return statement and return type does not include 'undefined'.": { + "category": "Error", + "code": 2366 + }, "Type parameter name cannot be '{0}'": { "category": "Error", "code": 2368 @@ -1423,6 +1427,10 @@ "category": "Error", "code": 2453 }, + "Variable '{0}' is used before being assigned.": { + "category": "Error", + "code": 2454 + }, "Type argument candidate '{1}' is not a valid type argument because it is not a supertype of candidate '{0}'.": { "category": "Error", "code": 2455 @@ -1719,6 +1727,10 @@ "category": "Error", "code": 2530 }, + "Object is possibly 'null' or 'undefined'.": { + "category": "Error", + "code": 2531 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 @@ -2604,6 +2616,11 @@ "category": "Message", "code": 6112 }, + "Enable strict null checks.": { + "category": "Message", + "code": 6113 + }, + "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8e474546ec9f7..0cb28a72a6758 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1533,6 +1533,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxExpression: case SyntaxKind.NewExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.PostfixUnaryExpression: case SyntaxKind.PrefixUnaryExpression: @@ -2077,8 +2078,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) { - expr = (expr).expression; + while (expr.kind === SyntaxKind.TypeAssertionExpression || + expr.kind === SyntaxKind.AsExpression || + expr.kind === SyntaxKind.NonNullExpression) { + expr = (expr).expression; } // isLeftHandSideExpression is almost the correct criterion for when it is not necessary @@ -2343,8 +2346,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } function skipParentheses(node: Expression): Expression { - while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { - node = (node).expression; + while (node.kind === SyntaxKind.ParenthesizedExpression || + node.kind === SyntaxKind.TypeAssertionExpression || + node.kind === SyntaxKind.AsExpression || + node.kind === SyntaxKind.NonNullExpression) { + node = (node).expression; } return node; } @@ -2518,13 +2524,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge // not the user. If we didn't want them, the emitter would not have put them // there. if (!nodeIsSynthesized(node) && node.parent.kind !== SyntaxKind.ArrowFunction) { - if (node.expression.kind === SyntaxKind.TypeAssertionExpression || node.expression.kind === SyntaxKind.AsExpression) { - let operand = (node.expression).expression; + if (node.expression.kind === SyntaxKind.TypeAssertionExpression || + node.expression.kind === SyntaxKind.AsExpression || + node.expression.kind === SyntaxKind.NonNullExpression) { + let operand = (node.expression).expression; // Make sure we consider all nested cast expressions, e.g.: // (-A).x; - while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) { - operand = (operand).expression; + while (operand.kind === SyntaxKind.TypeAssertionExpression || + operand.kind === SyntaxKind.AsExpression || + operand.kind === SyntaxKind.NonNullExpression) { + operand = (operand).expression; } // We have an expression of the form: (SubExpr) @@ -7928,9 +7938,9 @@ const _super = (function (geti, seti) { case SyntaxKind.TaggedTemplateExpression: return emitTaggedTemplateExpression(node); case SyntaxKind.TypeAssertionExpression: - return emit((node).expression); case SyntaxKind.AsExpression: - return emit((node).expression); + case SyntaxKind.NonNullExpression: + return emit((node).expression); case SyntaxKind.ParenthesizedExpression: return emitParenExpression(node); case SyntaxKind.FunctionDeclaration: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4f8d246911058..b7c011631ee18 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -177,6 +177,8 @@ namespace ts { case SyntaxKind.AsExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).type); + case SyntaxKind.NonNullExpression: + return visitNode(cbNode, (node).expression); case SyntaxKind.ConditionalExpression: return visitNode(cbNode, (node).condition) || visitNode(cbNode, (node).questionToken) || @@ -2361,12 +2363,14 @@ namespace ts { case SyntaxKind.NumberKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: + case SyntaxKind.UndefinedKeyword: // If these are followed by a dot, then parse these out as a dotted type reference instead. const node = tryParse(parseKeywordAndNoDot); return node || parseTypeReference(); case SyntaxKind.StringLiteral: return parseStringLiteralTypeNode(); case SyntaxKind.VoidKeyword: + case SyntaxKind.NullKeyword: return parseTokenNode(); case SyntaxKind.ThisKeyword: { const thisKeyword = parseThisTypeNode(); @@ -2398,6 +2402,8 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.OpenBraceToken: @@ -3724,6 +3730,14 @@ namespace ts { continue; } + if (token === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); + nonNullExpression.expression = expression; + expression = finishNode(nonNullExpression); + continue; + } + // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) { const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 64747fc638c16..8979814a7a2a6 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -114,6 +114,7 @@ namespace ts { "try": SyntaxKind.TryKeyword, "type": SyntaxKind.TypeKeyword, "typeof": SyntaxKind.TypeOfKeyword, + "undefined": SyntaxKind.UndefinedKeyword, "var": SyntaxKind.VarKeyword, "void": SyntaxKind.VoidKeyword, "while": SyntaxKind.WhileKeyword, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c5d822ad01377..546d1631945cc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -171,6 +171,7 @@ namespace ts { StringKeyword, SymbolKeyword, TypeKeyword, + UndefinedKeyword, FromKeyword, GlobalKeyword, OfKeyword, // LastKeyword and LastToken @@ -240,6 +241,7 @@ namespace ts { OmittedExpression, ExpressionWithTypeArguments, AsExpression, + NonNullExpression, // Misc TemplateSpan, @@ -475,6 +477,11 @@ namespace ts { originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later } + // Transient identifier node (marked by id === -1) + export interface TransientIdentifier extends Identifier { + resolvedSymbol: Symbol; + } + // @kind(SyntaxKind.QualifiedName) export interface QualifiedName extends Node { // Must have same layout as PropertyAccess @@ -968,6 +975,8 @@ namespace ts { name: Identifier; } + export type IdentifierOrPropertyAccess = Identifier | PropertyAccessExpression; + // @kind(SyntaxKind.ElementAccessExpression) export interface ElementAccessExpression extends MemberExpression { expression: LeftHandSideExpression; @@ -1012,6 +1021,11 @@ namespace ts { export type AssertionExpression = TypeAssertion | AsExpression; + // @kind(SyntaxKind.NonNullExpression) + export interface NonNullExpression extends LeftHandSideExpression { + expression: Expression; + } + /// A JSX expression of the form ... // @kind(SyntaxKind.JsxElement) export interface JsxElement extends PrimaryExpression { @@ -2029,7 +2043,9 @@ namespace ts { exportsChecked?: boolean; // True if exports of external module have been checked isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration bindingElement?: BindingElement; // Binding element associated with property symbol - exportsSomeValue?: boolean; // true if module exports some value (not just types) + exportsSomeValue?: boolean; // True if module exports some value (not just types) + firstAssignmentChecked?: boolean; // True if first assignment node has been computed + firstAssignment?: Node; // First assignment node (undefined if no assignments) } /* @internal */ @@ -2072,7 +2088,7 @@ namespace ts { isVisible?: boolean; // Is this node visible generatedName?: string; // Generated name for module, enum, or import declaration generatedNames?: Map; // Generated names table for source file - assignmentChecks?: Map; // Cache of assignment checks + assignmentMap?: Map; // Cached map of references assigned within this node hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context importOnRightSide?: Symbol; // for import declarations - import that appear on the right side jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with @@ -2106,7 +2122,7 @@ namespace ts { /* @internal */ FreshObjectLiteral = 0x00100000, // Fresh object literal type /* @internal */ - ContainsUndefinedOrNull = 0x00200000, // Type is or contains Undefined or Null type + ContainsUndefinedOrNull = 0x00200000, // Type is or contains undefined or null type /* @internal */ ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type /* @internal */ @@ -2115,6 +2131,8 @@ namespace ts { ThisType = 0x02000000, // This type ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties + /* @internal */ + Nullable = Undefined | Null, /* @internal */ Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, /* @internal */ @@ -2439,6 +2457,7 @@ namespace ts { allowSyntheticDefaultImports?: boolean; allowJs?: boolean; noImplicitUseStrict?: boolean; + strictNullChecks?: boolean; lib?: string[]; /* @internal */ stripInternal?: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6b120b745fef2..2966af3cb76f3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -511,6 +511,7 @@ namespace ts { case SyntaxKind.StringKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: + case SyntaxKind.UndefinedKeyword: return true; case SyntaxKind.VoidKeyword: return node.parent.kind !== SyntaxKind.VoidExpression; @@ -968,6 +969,7 @@ namespace ts { case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.AsExpression: case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ClassExpression: @@ -2406,6 +2408,7 @@ namespace ts { case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: + case SyntaxKind.NonNullExpression: case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.TaggedTemplateExpression: diff --git a/tests/baselines/reference/arrayLiteralWidened.types b/tests/baselines/reference/arrayLiteralWidened.types index 9599db2dff53b..83db7046eed4a 100644 --- a/tests/baselines/reference/arrayLiteralWidened.types +++ b/tests/baselines/reference/arrayLiteralWidened.types @@ -19,7 +19,7 @@ var a = [undefined, undefined]; var b = [[], [null, null]]; // any[][] >b : any[][] ->[[], [null, null]] : null[][] +>[[], [null, null]] : undefined[][] >[] : undefined[] >[null, null] : null[] >null : null diff --git a/tests/baselines/reference/castingTuple.errors.txt b/tests/baselines/reference/castingTuple.errors.txt index 62a36c9c15615..128ee5b5bb497 100644 --- a/tests/baselines/reference/castingTuple.errors.txt +++ b/tests/baselines/reference/castingTuple.errors.txt @@ -1,7 +1,3 @@ -tests/cases/conformance/types/tuple/castingTuple.ts(13,23): error TS2352: Neither type '[number, string]' nor type '[number, string, boolean]' is assignable to the other. - Property '2' is missing in type '[number, string]'. -tests/cases/conformance/types/tuple/castingTuple.ts(16,21): error TS2352: Neither type '[C, D]' nor type '[C, D, A]' is assignable to the other. - Property '2' is missing in type '[C, D]'. tests/cases/conformance/types/tuple/castingTuple.ts(28,10): error TS2352: Neither type '[number, string]' nor type '[number, number]' is assignable to the other. Types of property '1' are incompatible. Type 'string' is not assignable to type 'number'. @@ -10,15 +6,10 @@ tests/cases/conformance/types/tuple/castingTuple.ts(29,10): error TS2352: Neithe Type 'C' is not assignable to type 'A'. Property 'a' is missing in type 'C'. tests/cases/conformance/types/tuple/castingTuple.ts(30,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'array1' must be of type '{}[]', but here has type 'number[]'. -tests/cases/conformance/types/tuple/castingTuple.ts(30,14): error TS2352: Neither type '[number, string]' nor type 'number[]' is assignable to the other. - Types of property 'pop' are incompatible. - Type '() => number | string' is not assignable to type '() => number'. - Type 'number | string' is not assignable to type 'number'. - Type 'string' is not assignable to type 'number'. tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot find name 't4'. -==== tests/cases/conformance/types/tuple/castingTuple.ts (7 errors) ==== +==== tests/cases/conformance/types/tuple/castingTuple.ts (4 errors) ==== interface I { } class A { a = 10; } class C implements I { c }; @@ -32,15 +23,9 @@ tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot var numStrTuple: [number, string] = [5, "foo"]; var emptyObjTuple = <[{}, {}]>numStrTuple; var numStrBoolTuple = <[number, string, boolean]>numStrTuple; - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[number, string]' nor type '[number, string, boolean]' is assignable to the other. -!!! error TS2352: Property '2' is missing in type '[number, string]'. var classCDTuple: [C, D] = [new C(), new D()]; var interfaceIITuple = <[I, I]>classCDTuple; var classCDATuple = <[C, D, A]>classCDTuple; - ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[C, D]' nor type '[C, D, A]' is assignable to the other. -!!! error TS2352: Property '2' is missing in type '[C, D]'. var eleFromCDA1 = classCDATuple[2]; // A var eleFromCDA2 = classCDATuple[5]; // C | D | A var t10: [E1, E2] = [E1.one, E2.one]; @@ -66,12 +51,6 @@ tests/cases/conformance/types/tuple/castingTuple.ts(31,1): error TS2304: Cannot var array1 = numStrTuple; ~~~~~~ !!! error TS2403: Subsequent variable declarations must have the same type. Variable 'array1' must be of type '{}[]', but here has type 'number[]'. - ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2352: Neither type '[number, string]' nor type 'number[]' is assignable to the other. -!!! error TS2352: Types of property 'pop' are incompatible. -!!! error TS2352: Type '() => number | string' is not assignable to type '() => number'. -!!! error TS2352: Type 'number | string' is not assignable to type 'number'. -!!! error TS2352: Type 'string' is not assignable to type 'number'. t4[2] = 10; ~~ !!! error TS2304: Cannot find name 't4'. diff --git a/tests/baselines/reference/logicalAndOperatorWithEveryType.types b/tests/baselines/reference/logicalAndOperatorWithEveryType.types index bd913e94da58c..b2628eed9219f 100644 --- a/tests/baselines/reference/logicalAndOperatorWithEveryType.types +++ b/tests/baselines/reference/logicalAndOperatorWithEveryType.types @@ -623,7 +623,7 @@ var rj8 = a8 && undefined; var rj9 = null && undefined; >rj9 : any ->null && undefined : undefined +>null && undefined : null >null : null >undefined : undefined diff --git a/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt b/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt index 07262962b081a..c7d66e17f9d5e 100644 --- a/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt +++ b/tests/baselines/reference/logicalNotOperatorInvalidOperations.errors.txt @@ -1,19 +1,13 @@ -tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(5,17): error TS1005: ',' expected. -tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(5,18): error TS1109: Expression expected. tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(8,16): error TS2365: Operator '+' cannot be applied to types 'boolean' and 'number'. tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts(11,16): error TS1109: Expression expected. -==== tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts (4 errors) ==== +==== tests/cases/conformance/expressions/unaryOperators/logicalNotOperator/logicalNotOperatorInvalidOperations.ts (2 errors) ==== // Unary operator ! var b: number; // operand before ! var BOOLEAN1 = b!; //expect error - ~ -!!! error TS1005: ',' expected. - ~ -!!! error TS1109: Expression expected. // miss parentheses var BOOLEAN2 = !b + b; diff --git a/tests/baselines/reference/logicalNotOperatorInvalidOperations.js b/tests/baselines/reference/logicalNotOperatorInvalidOperations.js index 7b16f2ef36f50..0cb941b0e41d5 100644 --- a/tests/baselines/reference/logicalNotOperatorInvalidOperations.js +++ b/tests/baselines/reference/logicalNotOperatorInvalidOperations.js @@ -15,8 +15,7 @@ var BOOLEAN3 =!; // Unary operator ! var b; // operand before ! -var BOOLEAN1 = b; -!; //expect error +var BOOLEAN1 = b; //expect error // miss parentheses var BOOLEAN2 = !b + b; // miss an operand diff --git a/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt b/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt index 69dd6c2f6a955..92afe67df0c62 100644 --- a/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt +++ b/tests/baselines/reference/stringLiteralTypesWithVariousOperators02.errors.txt @@ -7,9 +7,12 @@ tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperato tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(13,11): error TS2356: An arithmetic operand must be of type 'any', 'number' or an enum type. tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(14,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(15,9): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(16,9): error TS2365: Operator '<' cannot be applied to types '"ABC"' and '"XYZ"'. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(17,9): error TS2365: Operator '===' cannot be applied to types '"ABC"' and '"XYZ"'. +tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts(18,9): error TS2365: Operator '!=' cannot be applied to types '"ABC"' and '"XYZ"'. -==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts (9 errors) ==== +==== tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperators02.ts (12 errors) ==== let abc: "ABC" = "ABC"; let xyz: "XYZ" = "XYZ"; @@ -44,5 +47,11 @@ tests/cases/conformance/types/stringLiteral/stringLiteralTypesWithVariousOperato ~~~~~~~~~~~~~~~~ !!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. let j = abc < xyz; + ~~~~~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types '"ABC"' and '"XYZ"'. let k = abc === xyz; - let l = abc != xyz; \ No newline at end of file + ~~~~~~~~~~~ +!!! error TS2365: Operator '===' cannot be applied to types '"ABC"' and '"XYZ"'. + let l = abc != xyz; + ~~~~~~~~~~ +!!! error TS2365: Operator '!=' cannot be applied to types '"ABC"' and '"XYZ"'. \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThis.types b/tests/baselines/reference/typeGuardFunctionOfFormThis.types index 6b21fea34576a..cd0381a94e9da 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThis.types +++ b/tests/baselines/reference/typeGuardFunctionOfFormThis.types @@ -174,9 +174,9 @@ if (holder2.a.isLeader()) { >isLeader : () => this is LeadGuard holder2.a; ->holder2.a : RoyalGuard +>holder2.a : LeadGuard >holder2 : { a: RoyalGuard; } ->a : RoyalGuard +>a : LeadGuard } else { holder2.a; diff --git a/tests/baselines/reference/typeGuardInClass.errors.txt b/tests/baselines/reference/typeGuardInClass.errors.txt new file mode 100644 index 0000000000000..aa86067576f5c --- /dev/null +++ b/tests/baselines/reference/typeGuardInClass.errors.txt @@ -0,0 +1,30 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts(6,17): error TS2322: Type 'string | number' is not assignable to type 'string'. + Type 'number' is not assignable to type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts(13,17): error TS2322: Type 'string | number' is not assignable to type 'number'. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts (2 errors) ==== + let x: string | number; + + if (typeof x === "string") { + let n = class { + constructor() { + let y: string = x; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + } + } + else { + let m = class { + constructor() { + let y: number = x; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'number'. +!!! error TS2322: Type 'string' is not assignable to type 'number'. + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardInClass.symbols b/tests/baselines/reference/typeGuardInClass.symbols deleted file mode 100644 index cc0e745e4de9e..0000000000000 --- a/tests/baselines/reference/typeGuardInClass.symbols +++ /dev/null @@ -1,29 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts === -let x: string | number; ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - -if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - - let n = class { ->n : Symbol(n, Decl(typeGuardInClass.ts, 3, 7)) - - constructor() { - let y: string = x; ->y : Symbol(y, Decl(typeGuardInClass.ts, 5, 15)) ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - } - } -} -else { - let m = class { ->m : Symbol(m, Decl(typeGuardInClass.ts, 10, 7)) - - constructor() { - let y: number = x; ->y : Symbol(y, Decl(typeGuardInClass.ts, 12, 15)) ->x : Symbol(x, Decl(typeGuardInClass.ts, 0, 3)) - } - } -} - diff --git a/tests/baselines/reference/typeGuardInClass.types b/tests/baselines/reference/typeGuardInClass.types deleted file mode 100644 index 93fe9f28c5e9e..0000000000000 --- a/tests/baselines/reference/typeGuardInClass.types +++ /dev/null @@ -1,34 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardInClass.ts === -let x: string | number; ->x : string | number - -if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : string | number ->"string" : string - - let n = class { ->n : typeof (Anonymous class) ->class { constructor() { let y: string = x; } } : typeof (Anonymous class) - - constructor() { - let y: string = x; ->y : string ->x : string - } - } -} -else { - let m = class { ->m : typeof (Anonymous class) ->class { constructor() { let y: number = x; } } : typeof (Anonymous class) - - constructor() { - let y: number = x; ->y : number ->x : number - } - } -} - diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt new file mode 100644 index 0000000000000..c76c6e819df11 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(13,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'string', but here has type 'number'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(20,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'boolean', but here has type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(27,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'number', but here has type 'boolean'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts(34,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'C', but here has type 'string'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts (4 errors) ==== + class C { private p: string }; + + var strOrNum: string | number; + var strOrBool: string | boolean; + var numOrBool: number | boolean + var strOrC: string | C; + + // typeof x == s has not effect on typeguard + if (typeof strOrNum == "string") { + var r1 = strOrNum; // string | number + } + else { + var r1 = strOrNum; // string | number + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'string', but here has type 'number'. + } + + if (typeof strOrBool == "boolean") { + var r2 = strOrBool; // string | boolean + } + else { + var r2 = strOrBool; // string | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'boolean', but here has type 'string'. + } + + if (typeof numOrBool == "number") { + var r3 = numOrBool; // number | boolean + } + else { + var r3 = numOrBool; // number | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'number', but here has type 'boolean'. + } + + if (typeof strOrC == "Object") { + var r4 = strOrC; // string | C + } + else { + var r4 = strOrC; // string | C + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'C', but here has type 'string'. + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols deleted file mode 100644 index bf79fb6a1d69e..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.symbols +++ /dev/null @@ -1,70 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts === -class C { private p: string }; ->C : Symbol(C, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 0)) ->p : Symbol(C.p, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 9)) - -var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) - -var strOrBool: string | boolean; ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) - -var numOrBool: number | boolean ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) - -var strOrC: string | C; ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) ->C : Symbol(C, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 0, 0)) - -// typeof x == s has not effect on typeguard -if (typeof strOrNum == "string") { ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) - - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) -} -else { - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 2, 3)) -} - -if (typeof strOrBool == "boolean") { ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) - - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) -} -else { - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 3, 3)) -} - -if (typeof numOrBool == "number") { ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) - - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) -} -else { - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 4, 3)) -} - -if (typeof strOrC == "Object") { ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) - - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) -} -else { - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts, 5, 3)) -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types b/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types deleted file mode 100644 index 4bfc8fe6bf1c4..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfEqualEqualHasNoEffect.types +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfEqualEqualHasNoEffect.ts === -class C { private p: string }; ->C : C ->p : string - -var strOrNum: string | number; ->strOrNum : string | number - -var strOrBool: string | boolean; ->strOrBool : string | boolean - -var numOrBool: number | boolean ->numOrBool : number | boolean - -var strOrC: string | C; ->strOrC : string | C ->C : C - -// typeof x == s has not effect on typeguard -if (typeof strOrNum == "string") { ->typeof strOrNum == "string" : boolean ->typeof strOrNum : string ->strOrNum : string | number ->"string" : string - - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} -else { - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} - -if (typeof strOrBool == "boolean") { ->typeof strOrBool == "boolean" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean ->"boolean" : string - - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} -else { - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} - -if (typeof numOrBool == "number") { ->typeof numOrBool == "number" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean ->"number" : string - - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} -else { - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} - -if (typeof strOrC == "Object") { ->typeof strOrC == "Object" : boolean ->typeof strOrC : string ->strOrC : string | C ->"Object" : string - - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} -else { - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt new file mode 100644 index 0000000000000..3b29f3ecba841 --- /dev/null +++ b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.errors.txt @@ -0,0 +1,50 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(13,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'number', but here has type 'string'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(20,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'string', but here has type 'boolean'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(27,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'boolean', but here has type 'number'. +tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts(34,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'string', but here has type 'C'. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts (4 errors) ==== + class C { private p: string }; + + var strOrNum: string | number; + var strOrBool: string | boolean; + var numOrBool: number | boolean + var strOrC: string | C; + + // typeof x != s has not effect on typeguard + if (typeof strOrNum != "string") { + var r1 = strOrNum; // string | number + } + else { + var r1 = strOrNum; // string | number + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r1' must be of type 'number', but here has type 'string'. + } + + if (typeof strOrBool != "boolean") { + var r2 = strOrBool; // string | boolean + } + else { + var r2 = strOrBool; // string | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r2' must be of type 'string', but here has type 'boolean'. + } + + if (typeof numOrBool != "number") { + var r3 = numOrBool; // number | boolean + } + else { + var r3 = numOrBool; // number | boolean + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r3' must be of type 'boolean', but here has type 'number'. + } + + if (typeof strOrC != "Object") { + var r4 = strOrC; // string | C + } + else { + var r4 = strOrC; // string | C + ~~ +!!! error TS2403: Subsequent variable declarations must have the same type. Variable 'r4' must be of type 'string', but here has type 'C'. + } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols deleted file mode 100644 index 7988b7196ef3d..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.symbols +++ /dev/null @@ -1,70 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts === -class C { private p: string }; ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) ->p : Symbol(C.p, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 9)) - -var strOrNum: string | number; ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - -var strOrBool: string | boolean; ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - -var numOrBool: number | boolean ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - -var strOrC: string | C; ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) ->C : Symbol(C, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 0, 0)) - -// typeof x != s has not effect on typeguard -if (typeof strOrNum != "string") { ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) - - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} -else { - var r1 = strOrNum; // string | number ->r1 : Symbol(r1, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 9, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 12, 7)) ->strOrNum : Symbol(strOrNum, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 2, 3)) -} - -if (typeof strOrBool != "boolean") { ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) - - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} -else { - var r2 = strOrBool; // string | boolean ->r2 : Symbol(r2, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 16, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 19, 7)) ->strOrBool : Symbol(strOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 3, 3)) -} - -if (typeof numOrBool != "number") { ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) - - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} -else { - var r3 = numOrBool; // number | boolean ->r3 : Symbol(r3, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 23, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 26, 7)) ->numOrBool : Symbol(numOrBool, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 4, 3)) -} - -if (typeof strOrC != "Object") { ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) - - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} -else { - var r4 = strOrC; // string | C ->r4 : Symbol(r4, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 30, 7), Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 33, 7)) ->strOrC : Symbol(strOrC, Decl(typeGuardOfFormTypeOfNotEqualHasNoEffect.ts, 5, 3)) -} diff --git a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types b/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types deleted file mode 100644 index 6eabeb25ede19..0000000000000 --- a/tests/baselines/reference/typeGuardOfFormTypeOfNotEqualHasNoEffect.types +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardOfFormTypeOfNotEqualHasNoEffect.ts === -class C { private p: string }; ->C : C ->p : string - -var strOrNum: string | number; ->strOrNum : string | number - -var strOrBool: string | boolean; ->strOrBool : string | boolean - -var numOrBool: number | boolean ->numOrBool : number | boolean - -var strOrC: string | C; ->strOrC : string | C ->C : C - -// typeof x != s has not effect on typeguard -if (typeof strOrNum != "string") { ->typeof strOrNum != "string" : boolean ->typeof strOrNum : string ->strOrNum : string | number ->"string" : string - - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} -else { - var r1 = strOrNum; // string | number ->r1 : string | number ->strOrNum : string | number -} - -if (typeof strOrBool != "boolean") { ->typeof strOrBool != "boolean" : boolean ->typeof strOrBool : string ->strOrBool : string | boolean ->"boolean" : string - - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} -else { - var r2 = strOrBool; // string | boolean ->r2 : string | boolean ->strOrBool : string | boolean -} - -if (typeof numOrBool != "number") { ->typeof numOrBool != "number" : boolean ->typeof numOrBool : string ->numOrBool : number | boolean ->"number" : string - - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} -else { - var r3 = numOrBool; // number | boolean ->r3 : number | boolean ->numOrBool : number | boolean -} - -if (typeof strOrC != "Object") { ->typeof strOrC != "Object" : boolean ->typeof strOrC : string ->strOrC : string | C ->"Object" : string - - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} -else { - var r4 = strOrC; // string | C ->r4 : string | C ->strOrC : string | C -} diff --git a/tests/baselines/reference/typeGuardsDefeat.errors.txt b/tests/baselines/reference/typeGuardsDefeat.errors.txt new file mode 100644 index 0000000000000..d4006711d45c2 --- /dev/null +++ b/tests/baselines/reference/typeGuardsDefeat.errors.txt @@ -0,0 +1,52 @@ +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(21,20): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(21,24): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(32,23): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. +tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts(32,27): error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + + +==== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts (4 errors) ==== + // Also note that it is possible to defeat a type guard by calling a function that changes the + // type of the guarded variable. + function foo(x: number | string) { + function f() { + x = 10; + } + if (typeof x === "string") { + f(); + return x.length; // string + } + else { + return x++; // number + } + } + function foo2(x: number | string) { + if (typeof x === "string") { + return x.length; // string + } + else { + var f = function () { + return x * x; + ~ +!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + ~ +!!! error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + }; + } + x = "hello"; + f(); + } + function foo3(x: number | string) { + if (typeof x === "string") { + return x.length; // string + } + else { + var f = () => x * x; + ~ +!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + ~ +!!! error TS2363: The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. + } + x = "hello"; + f(); + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsDefeat.symbols b/tests/baselines/reference/typeGuardsDefeat.symbols deleted file mode 100644 index 388b69b578904..0000000000000 --- a/tests/baselines/reference/typeGuardsDefeat.symbols +++ /dev/null @@ -1,82 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts === -// Also note that it is possible to defeat a type guard by calling a function that changes the -// type of the guarded variable. -function foo(x: number | string) { ->foo : Symbol(foo, Decl(typeGuardsDefeat.ts, 0, 0)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - - function f() { ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 2, 34)) - - x = 10; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - } - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 2, 34)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - return x++; // number ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 2, 13)) - } -} -function foo2(x: number | string) { ->foo2 : Symbol(foo2, Decl(typeGuardsDefeat.ts, 13, 1)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - var f = function () { ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 19, 11)) - - return x * x; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - }; - } - x = "hello"; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 14, 14)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 19, 11)) -} -function foo3(x: number | string) { ->foo3 : Symbol(foo3, Decl(typeGuardsDefeat.ts, 25, 1)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - if (typeof x === "string") { ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - return x.length; // string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) - } - else { - var f = () => x * x; ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 31, 11)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - } - x = "hello"; ->x : Symbol(x, Decl(typeGuardsDefeat.ts, 26, 14)) - - f(); ->f : Symbol(f, Decl(typeGuardsDefeat.ts, 31, 11)) -} - diff --git a/tests/baselines/reference/typeGuardsDefeat.types b/tests/baselines/reference/typeGuardsDefeat.types deleted file mode 100644 index cc655d3ce0f36..0000000000000 --- a/tests/baselines/reference/typeGuardsDefeat.types +++ /dev/null @@ -1,105 +0,0 @@ -=== tests/cases/conformance/expressions/typeGuards/typeGuardsDefeat.ts === -// Also note that it is possible to defeat a type guard by calling a function that changes the -// type of the guarded variable. -function foo(x: number | string) { ->foo : (x: number | string) => number ->x : number | string - - function f() { ->f : () => void - - x = 10; ->x = 10 : number ->x : number | string ->10 : number - } - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - f(); ->f() : void ->f : () => void - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - return x++; // number ->x++ : number ->x : number - } -} -function foo2(x: number | string) { ->foo2 : (x: number | string) => number ->x : number | string - - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - var f = function () { ->f : () => number ->function () { return x * x; } : () => number - - return x * x; ->x * x : number ->x : number ->x : number - - }; - } - x = "hello"; ->x = "hello" : string ->x : number | string ->"hello" : string - - f(); ->f() : number ->f : () => number -} -function foo3(x: number | string) { ->foo3 : (x: number | string) => number ->x : number | string - - if (typeof x === "string") { ->typeof x === "string" : boolean ->typeof x : string ->x : number | string ->"string" : string - - return x.length; // string ->x.length : number ->x : string ->length : number - } - else { - var f = () => x * x; ->f : () => number ->() => x * x : () => number ->x * x : number ->x : number ->x : number - } - x = "hello"; ->x = "hello" : string ->x : number | string ->"hello" : string - - f(); ->f() : number ->f : () => number -} - diff --git a/tests/baselines/reference/typeGuardsInExternalModule.types b/tests/baselines/reference/typeGuardsInExternalModule.types index e20063719f5b6..940e7db831adf 100644 --- a/tests/baselines/reference/typeGuardsInExternalModule.types +++ b/tests/baselines/reference/typeGuardsInExternalModule.types @@ -44,13 +44,13 @@ if (typeof var2 === "string") { // export makes the var property and not variable strOrNum = var2; // string | number ->strOrNum = var2 : string | number +>strOrNum = var2 : string >strOrNum : string | number ->var2 : string | number +>var2 : string } else { strOrNum = var2; // number | string ->strOrNum = var2 : string | number +>strOrNum = var2 : number >strOrNum : string | number ->var2 : string | number +>var2 : number } diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols index 34810f303db3d..bf21641624e3e 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.symbols @@ -27,9 +27,9 @@ function foo(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 2, 13)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) } (); } @@ -60,9 +60,9 @@ function foo2(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) } (x); // x here is narrowed to number | boolean >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 12, 14)) @@ -91,9 +91,9 @@ function foo3(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 22, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) })(); } @@ -123,9 +123,9 @@ function foo4(x: number | string | boolean) { >toString : Symbol(Object.toString, Decl(lib.d.ts, --, --)) : x.toString(); // number ->x.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>x.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14)) ->toString : Symbol(Number.toString, Decl(lib.d.ts, --, --)) +>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) })(x); // x here is narrowed to number | boolean >x : Symbol(x, Decl(typeGuardsInFunctionAndModuleBlock.ts, 32, 14)) diff --git a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types index f7d56ed17d1a6..67d1816cfc373 100644 --- a/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types +++ b/tests/baselines/reference/typeGuardsInFunctionAndModuleBlock.types @@ -21,14 +21,14 @@ function foo(x: number | string | boolean) { >f : () => string var b = x; // number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -40,7 +40,7 @@ function foo(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string } (); @@ -66,14 +66,14 @@ function foo2(x: number | string | boolean) { >a : number | boolean var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -85,7 +85,7 @@ function foo2(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string } (x); // x here is narrowed to number | boolean @@ -111,14 +111,14 @@ function foo3(x: number | string | boolean) { >() => { var b = x; // new scope - number | boolean return typeof x === "boolean" ? x.toString() // boolean : x.toString(); // number } : () => string var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -130,7 +130,7 @@ function foo3(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string })(); @@ -156,14 +156,14 @@ function foo4(x: number | string | boolean) { >a : number | boolean var b = x; // new scope - number | boolean ->b : number | boolean ->x : number | boolean +>b : number | string | boolean +>x : number | string | boolean return typeof x === "boolean" >typeof x === "boolean" ? x.toString() // boolean : x.toString() : string >typeof x === "boolean" : boolean >typeof x : string ->x : number | boolean +>x : number | string | boolean >"boolean" : string ? x.toString() // boolean @@ -175,7 +175,7 @@ function foo4(x: number | string | boolean) { : x.toString(); // number >x.toString() : string >x.toString : (radix?: number) => string ->x : number +>x : number | string >toString : (radix?: number) => string })(x); // x here is narrowed to number | boolean @@ -200,8 +200,8 @@ function foo5(x: number | string | boolean) { >foo : () => void var z = x; // string ->z : string ->x : string +>z : number | string | boolean +>x : number | string | boolean } } } diff --git a/tests/baselines/reference/typeGuardsInModule.types b/tests/baselines/reference/typeGuardsInModule.types index 6b6c552f0cced..7d3753037ad38 100644 --- a/tests/baselines/reference/typeGuardsInModule.types +++ b/tests/baselines/reference/typeGuardsInModule.types @@ -64,15 +64,15 @@ module m1 { >"string" : string strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : string >strOrNum : string | number ->var3 : string | number +>var3 : string } else { strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : number >strOrNum : string | number ->var3 : string | number +>var3 : number } } // local module @@ -116,14 +116,14 @@ module m2 { // exported variable from outer the module strOrNum = typeof var3 === "string" && var3; // string | number ->strOrNum = typeof var3 === "string" && var3 : string | number +>strOrNum = typeof var3 === "string" && var3 : string >strOrNum : string | number ->typeof var3 === "string" && var3 : string | number +>typeof var3 === "string" && var3 : string >typeof var3 === "string" : boolean >typeof var3 : string >var3 : string | number >"string" : string ->var3 : string | number +>var3 : string // variables in module declaration var var4: string | number; @@ -160,15 +160,15 @@ module m2 { >"string" : string strOrNum = var5; // string | number ->strOrNum = var5 : string | number +>strOrNum = var5 : string >strOrNum : string | number ->var5 : string | number +>var5 : string } else { strOrNum = var5; // string | number ->strOrNum = var5 : string | number +>strOrNum = var5 : number >strOrNum : string | number ->var5 : string | number +>var5 : number } } } @@ -225,15 +225,15 @@ module m3.m4 { >"string" : string strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : string >strOrNum : string | number ->var3 : string | number +>var3 : string } else { strOrNum = var3; // string | number ->strOrNum = var3 : string | number +>strOrNum = var3 : number >strOrNum : string | number ->var3 : string | number +>var3 : number } } diff --git a/tests/baselines/reference/typeGuardsInProperties.types b/tests/baselines/reference/typeGuardsInProperties.types index ca4d8b9452786..ef4cfbb5b051f 100644 --- a/tests/baselines/reference/typeGuardsInProperties.types +++ b/tests/baselines/reference/typeGuardsInProperties.types @@ -29,32 +29,32 @@ class C1 { >method : () => void strOrNum = typeof this.pp1 === "string" && this.pp1; // string | number ->strOrNum = typeof this.pp1 === "string" && this.pp1 : string | number +>strOrNum = typeof this.pp1 === "string" && this.pp1 : string >strOrNum : string | number ->typeof this.pp1 === "string" && this.pp1 : string | number +>typeof this.pp1 === "string" && this.pp1 : string >typeof this.pp1 === "string" : boolean >typeof this.pp1 : string >this.pp1 : string | number >this : this >pp1 : string | number >"string" : string ->this.pp1 : string | number +>this.pp1 : string >this : this ->pp1 : string | number +>pp1 : string strOrNum = typeof this.pp2 === "string" && this.pp2; // string | number ->strOrNum = typeof this.pp2 === "string" && this.pp2 : string | number +>strOrNum = typeof this.pp2 === "string" && this.pp2 : string >strOrNum : string | number ->typeof this.pp2 === "string" && this.pp2 : string | number +>typeof this.pp2 === "string" && this.pp2 : string >typeof this.pp2 === "string" : boolean >typeof this.pp2 : string >this.pp2 : string | number >this : this >pp2 : string | number >"string" : string ->this.pp2 : string | number +>this.pp2 : string >this : this ->pp2 : string | number +>pp2 : string strOrNum = typeof this.pp3 === "string" && this.pp3; // string | number >strOrNum = typeof this.pp3 === "string" && this.pp3 : string | number @@ -76,18 +76,18 @@ var c1: C1; >C1 : C1 strOrNum = typeof c1.pp2 === "string" && c1.pp2; // string | number ->strOrNum = typeof c1.pp2 === "string" && c1.pp2 : string | number +>strOrNum = typeof c1.pp2 === "string" && c1.pp2 : string >strOrNum : string | number ->typeof c1.pp2 === "string" && c1.pp2 : string | number +>typeof c1.pp2 === "string" && c1.pp2 : string >typeof c1.pp2 === "string" : boolean >typeof c1.pp2 : string >c1.pp2 : string | number >c1 : C1 >pp2 : string | number >"string" : string ->c1.pp2 : string | number +>c1.pp2 : string >c1 : C1 ->pp2 : string | number +>pp2 : string strOrNum = typeof c1.pp3 === "string" && c1.pp3; // string | number >strOrNum = typeof c1.pp3 === "string" && c1.pp3 : string | number @@ -111,16 +111,16 @@ var obj1: { }; strOrNum = typeof obj1.x === "string" && obj1.x; // string | number ->strOrNum = typeof obj1.x === "string" && obj1.x : string | number +>strOrNum = typeof obj1.x === "string" && obj1.x : string >strOrNum : string | number ->typeof obj1.x === "string" && obj1.x : string | number +>typeof obj1.x === "string" && obj1.x : string >typeof obj1.x === "string" : boolean >typeof obj1.x : string >obj1.x : string | number >obj1 : { x: string | number; } >x : string | number >"string" : string ->obj1.x : string | number +>obj1.x : string >obj1 : { x: string | number; } ->x : string | number +>x : string diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt b/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt deleted file mode 100644 index 91d6d6998bc49..0000000000000 --- a/tests/baselines/reference/typeGuardsOnClassProperty.errors.txt +++ /dev/null @@ -1,37 +0,0 @@ -tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(14,70): error TS2339: Property 'join' does not exist on type 'string | string[]'. -tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts(26,44): error TS2339: Property 'toLowerCase' does not exist on type 'number | string'. - - -==== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts (2 errors) ==== - // Note that type guards affect types of variables and parameters only and - // have no effect on members of objects such as properties. - - // Note that the class's property must be copied to a local variable for - // the type guard to have an effect - class D { - data: string | string[]; - getData() { - var data = this.data; - return typeof data === "string" ? data : data.join(" "); - } - - getData1() { - return typeof this.data === "string" ? this.data : this.data.join(" "); - ~~~~ -!!! error TS2339: Property 'join' does not exist on type 'string | string[]'. - } - } - - var o: { - prop1: number|string; - prop2: boolean|string; - } = { - prop1: "string" , - prop2: true - } - - if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} - ~~~~~~~~~~~ -!!! error TS2339: Property 'toLowerCase' does not exist on type 'number | string'. - var prop1 = o.prop1; - if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.symbols b/tests/baselines/reference/typeGuardsOnClassProperty.symbols new file mode 100644 index 0000000000000..5017c442b2f38 --- /dev/null +++ b/tests/baselines/reference/typeGuardsOnClassProperty.symbols @@ -0,0 +1,86 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts === +// Note that type guards affect types of variables and parameters only and +// have no effect on members of objects such as properties. + +// Note that the class's property must be copied to a local variable for +// the type guard to have an effect +class D { +>D : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) + + data: string | string[]; +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) + + getData() { +>getData : Symbol(D.getData, Decl(typeGuardsOnClassProperty.ts, 6, 28)) + + var data = this.data; +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) + + return typeof data === "string" ? data : data.join(" "); +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>data.join : Symbol(Array.join, Decl(lib.d.ts, --, --)) +>data : Symbol(data, Decl(typeGuardsOnClassProperty.ts, 8, 11)) +>join : Symbol(Array.join, Decl(lib.d.ts, --, --)) + } + + getData1() { +>getData1 : Symbol(D.getData1, Decl(typeGuardsOnClassProperty.ts, 10, 5)) + + return typeof this.data === "string" ? this.data : this.data.join(" "); +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this.data.join : Symbol(Array.join, Decl(lib.d.ts, --, --)) +>this.data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>this : Symbol(D, Decl(typeGuardsOnClassProperty.ts, 0, 0)) +>data : Symbol(D.data, Decl(typeGuardsOnClassProperty.ts, 5, 9)) +>join : Symbol(Array.join, Decl(lib.d.ts, --, --)) + } +} + +var o: { +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) + + prop1: number|string; +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) + + prop2: boolean|string; +>prop2 : Symbol(prop2, Decl(typeGuardsOnClassProperty.ts, 18, 25)) + +} = { + prop1: "string" , +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 20, 5)) + + prop2: true +>prop2 : Symbol(prop2, Decl(typeGuardsOnClassProperty.ts, 21, 25)) + } + +if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o.prop1.toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.d.ts, --, --)) + +var prop1 = o.prop1; +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>o.prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) +>o : Symbol(o, Decl(typeGuardsOnClassProperty.ts, 17, 3)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 17, 8)) + +if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>prop1.toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, --, --)) +>prop1 : Symbol(prop1, Decl(typeGuardsOnClassProperty.ts, 26, 3)) +>toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, --, --)) + diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.types b/tests/baselines/reference/typeGuardsOnClassProperty.types new file mode 100644 index 0000000000000..6d524ccf6744c --- /dev/null +++ b/tests/baselines/reference/typeGuardsOnClassProperty.types @@ -0,0 +1,112 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsOnClassProperty.ts === +// Note that type guards affect types of variables and parameters only and +// have no effect on members of objects such as properties. + +// Note that the class's property must be copied to a local variable for +// the type guard to have an effect +class D { +>D : D + + data: string | string[]; +>data : string | string[] + + getData() { +>getData : () => string + + var data = this.data; +>data : string | string[] +>this.data : string | string[] +>this : this +>data : string | string[] + + return typeof data === "string" ? data : data.join(" "); +>typeof data === "string" ? data : data.join(" ") : string +>typeof data === "string" : boolean +>typeof data : string +>data : string | string[] +>"string" : string +>data : string +>data.join(" ") : string +>data.join : (separator?: string) => string +>data : string[] +>join : (separator?: string) => string +>" " : string + } + + getData1() { +>getData1 : () => string + + return typeof this.data === "string" ? this.data : this.data.join(" "); +>typeof this.data === "string" ? this.data : this.data.join(" ") : string +>typeof this.data === "string" : boolean +>typeof this.data : string +>this.data : string | string[] +>this : this +>data : string | string[] +>"string" : string +>this.data : string +>this : this +>data : string +>this.data.join(" ") : string +>this.data.join : (separator?: string) => string +>this.data : string[] +>this : this +>data : string[] +>join : (separator?: string) => string +>" " : string + } +} + +var o: { +>o : { prop1: number | string; prop2: boolean | string; } + + prop1: number|string; +>prop1 : number | string + + prop2: boolean|string; +>prop2 : boolean | string + +} = { +>{ prop1: "string" , prop2: true } : { prop1: string; prop2: boolean; } + + prop1: "string" , +>prop1 : string +>"string" : string + + prop2: true +>prop2 : boolean +>true : boolean + } + +if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} +>typeof o.prop1 === "string" && o.prop1.toLowerCase() : string +>typeof o.prop1 === "string" : boolean +>typeof o.prop1 : string +>o.prop1 : number | string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : number | string +>"string" : string +>o.prop1.toLowerCase() : string +>o.prop1.toLowerCase : () => string +>o.prop1 : string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : string +>toLowerCase : () => string + +var prop1 = o.prop1; +>prop1 : number | string +>o.prop1 : number | string +>o : { prop1: number | string; prop2: boolean | string; } +>prop1 : number | string + +if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } +>typeof prop1 === "string" && prop1.toLocaleLowerCase() : string +>typeof prop1 === "string" : boolean +>typeof prop1 : string +>prop1 : number | string +>"string" : string +>prop1.toLocaleLowerCase() : string +>prop1.toLocaleLowerCase : () => string +>prop1 : string +>toLocaleLowerCase : () => string + diff --git a/tests/baselines/reference/typeParameterConstraints1.errors.txt b/tests/baselines/reference/typeParameterConstraints1.errors.txt index e837e19317deb..97bc816575138 100644 --- a/tests/baselines/reference/typeParameterConstraints1.errors.txt +++ b/tests/baselines/reference/typeParameterConstraints1.errors.txt @@ -1,11 +1,9 @@ tests/cases/compiler/typeParameterConstraints1.ts(6,25): error TS2304: Cannot find name 'hm'. tests/cases/compiler/typeParameterConstraints1.ts(9,25): error TS1110: Type expected. tests/cases/compiler/typeParameterConstraints1.ts(10,26): error TS1110: Type expected. -tests/cases/compiler/typeParameterConstraints1.ts(11,26): error TS1110: Type expected. -tests/cases/compiler/typeParameterConstraints1.ts(12,26): error TS2304: Cannot find name 'undefined'. -==== tests/cases/compiler/typeParameterConstraints1.ts (5 errors) ==== +==== tests/cases/compiler/typeParameterConstraints1.ts (3 errors) ==== function foo1(test: T) { } function foo2(test: T) { } function foo3(test: T) { } @@ -23,9 +21,5 @@ tests/cases/compiler/typeParameterConstraints1.ts(12,26): error TS2304: Cannot f ~ !!! error TS1110: Type expected. function foo11 (test: T) { } - ~~~~ -!!! error TS1110: Type expected. function foo12(test: T) { } - ~~~~~~~~~ -!!! error TS2304: Cannot find name 'undefined'. function foo13(test: T) { } \ No newline at end of file diff --git a/tests/cases/unittests/jsDocParsing.ts b/tests/cases/unittests/jsDocParsing.ts index 9c68cea3297ae..1bd7b1147ca2a 100644 --- a/tests/cases/unittests/jsDocParsing.ts +++ b/tests/cases/unittests/jsDocParsing.ts @@ -793,6 +793,7 @@ module ts { "kind": "Identifier", "pos": 1, "end": 10, + "originalKeywordKind": "UndefinedKeyword", "text": "undefined" } }`);