Skip to content
44 changes: 24 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ namespace ts {
getIndexTypeOfType,
getBaseTypes,
getReturnTypeOfSignature,
getNonNullableType,
getSymbolsInScope,
getSymbolAtLocation,
getShorthandAssignmentValueSymbol,
Expand Down Expand Up @@ -2885,6 +2886,10 @@ namespace ts {
return undefined;
}

function addOptionality(type: Type, optional: boolean): Type {
return strictNullChecks && optional ? addNullableKind(type, TypeFlags.Undefined) : type;
}

// Return the inferred type for a variable, parameter, or property declaration
function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type {
if (declaration.flags & NodeFlags.JavaScriptFile) {
Expand Down Expand Up @@ -2916,8 +2921,7 @@ namespace ts {

// Use type from type annotation if one is present
if (declaration.type) {
const type = getTypeFromTypeNode(declaration.type);
return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type;
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ !!declaration.questionToken);
}

if (declaration.kind === SyntaxKind.Parameter) {
Expand All @@ -2939,13 +2943,13 @@ namespace ts {
? getContextuallyTypedThisType(func)
: getContextuallyTypedParameterType(<ParameterDeclaration>declaration);
if (type) {
return strictNullChecks && declaration.questionToken ? addNullableKind(type, TypeFlags.Undefined) : type;
return addOptionality(type, /*optional*/ !!declaration.questionToken);
}
}

// Use the type of the initializer expression if one is present
if (declaration.initializer) {
return checkExpressionCached(declaration.initializer);
return addOptionality(checkExpressionCached(declaration.initializer), /*optional*/ !!declaration.questionToken);
}

// If it is a short-hand property assignment, use the type of the identifier
Expand Down Expand Up @@ -3216,7 +3220,9 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = createObjectType(TypeFlags.Anonymous, symbol);
const type = createObjectType(TypeFlags.Anonymous, symbol);
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ?
addNullableKind(type, TypeFlags.Undefined) : type;
}
return links.type;
}
Expand Down Expand Up @@ -7615,11 +7621,12 @@ namespace ts {
getInitialTypeOfBindingElement(<BindingElement>node);
}

function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType: Type) {
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) {
let key: string;
if (!reference.flowNode || declaredType === initialType && !(declaredType.flags & TypeFlags.Narrowable)) {
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
const initialType = assumeInitialized ? declaredType : addNullableKind(declaredType, TypeFlags.Undefined);
const visitedFlowStart = visitedFlowCount;
const result = getTypeAtFlowNode(reference.flowNode);
visitedFlowCount = visitedFlowStart;
Expand Down Expand Up @@ -8093,11 +8100,11 @@ namespace ts {
return type;
}
const declaration = localOrExportSymbol.valueDeclaration;
const defaultsToDeclaredType = !strictNullChecks || type.flags & TypeFlags.Any || !declaration ||
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
const flowType = getFlowTypeOfReference(node, type, defaultsToDeclaredType ? type : addNullableKind(type, TypeFlags.Undefined));
if (strictNullChecks && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
const flowType = getFlowTypeOfReference(node, type, assumeInitialized);
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
return type;
Expand Down Expand Up @@ -8345,7 +8352,7 @@ namespace ts {
if (isClassLike(container.parent)) {
const symbol = getSymbolOfNode(container.parent);
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
return getFlowTypeOfReference(node, type, type);
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true);
}

if (isInJavaScriptFile(node)) {
Expand Down Expand Up @@ -9920,7 +9927,8 @@ namespace ts {
}

const propType = getTypeOfSymbol(prop);
if (node.kind !== SyntaxKind.PropertyAccessExpression || !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) || isAssignmentTarget(node)) {
if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) ||
!(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) {
return propType;
}
const leftmostNode = getLeftmostIdentifierOrThis(node);
Expand All @@ -9937,7 +9945,7 @@ namespace ts {
return propType;
}
}
return getFlowTypeOfReference(node, propType, propType);
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true);
}

function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {
Expand Down Expand Up @@ -13395,7 +13403,7 @@ namespace ts {

// Abstract methods can't have an implementation -- in particular, they don't need one.
if (!isExportSymbolInsideModule && lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body &&
!(lastSeenNonAmbientDeclaration.flags & NodeFlags.Abstract)) {
!(lastSeenNonAmbientDeclaration.flags & NodeFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) {
reportImplementationExpectedError(lastSeenNonAmbientDeclaration);
}

Expand Down Expand Up @@ -18289,7 +18297,7 @@ namespace ts {
}

if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) {
if (checkGrammarForInvalidQuestionMark(node, node.questionToken, Diagnostics.A_class_member_cannot_be_declared_optional)) {
if (checkGrammarForInvalidQuestionMark(node, node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) {
return true;
}
else if (node.body === undefined) {
Expand All @@ -18298,9 +18306,6 @@ namespace ts {
}

if (isClassLike(node.parent)) {
if (checkGrammarForInvalidQuestionMark(node, node.questionToken, Diagnostics.A_class_member_cannot_be_declared_optional)) {
return true;
}
// Technically, computed properties in ambient contexts is disallowed
// for property declarations and accessors too, not just methods.
// However, property declarations disallow computed names in general,
Expand Down Expand Up @@ -18522,8 +18527,7 @@ namespace ts {

function checkGrammarProperty(node: PropertyDeclaration) {
if (isClassLike(node.parent)) {
if (checkGrammarForInvalidQuestionMark(node, node.questionToken, Diagnostics.A_class_member_cannot_be_declared_optional) ||
checkGrammarForNonSymbolComputedProperty(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_directly_refer_to_a_built_in_symbol)) {
if (checkGrammarForNonSymbolComputedProperty(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_directly_refer_to_a_built_in_symbol)) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@ namespace ts {
// what we want, namely the name expression enclosed in brackets.
writeTextOfNode(currentText, node.name);
// If optional property emit ?
if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) && hasQuestionToken(node)) {
if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.Parameter) && hasQuestionToken(node)) {
write("?");
}
if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) && node.parent.kind === SyntaxKind.TypeLiteral) {
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,6 @@
"category": "Error",
"code": 1110
},
"A class member cannot be declared optional.": {
"category": "Error",
"code": 1112
},
"A 'default' clause cannot appear more than once in a 'switch' statement.": {
"category": "Error",
"code": 1113
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,7 @@ namespace ts {
getIndexTypeOfType(type: Type, kind: IndexKind): Type;
getBaseTypes(type: InterfaceType): ObjectType[];
getReturnTypeOfSignature(signature: Signature): Type;
getNonNullableType(type: Type): Type;

getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[];
getSymbolAtLocation(node: Node): Symbol;
Expand Down
8 changes: 6 additions & 2 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace ts {
getStringIndexType(): Type;
getNumberIndexType(): Type;
getBaseTypes(): ObjectType[];
getNonNullableType(): Type;
}

export interface Signature {
Expand Down Expand Up @@ -735,6 +736,9 @@ namespace ts {
? this.checker.getBaseTypes(<InterfaceType><Type>this)
: undefined;
}
getNonNullableType(): Type {
return this.checker.getNonNullableType(this);
}
}

class SignatureObject implements Signature {
Expand Down Expand Up @@ -4366,7 +4370,7 @@ namespace ts {
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
// get the signature from the declaration and write it
const functionDeclaration = <FunctionLikeDeclaration>location.parent;
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getConstructSignatures() : type.getCallSignatures();
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
}
Expand Down Expand Up @@ -4564,7 +4568,7 @@ namespace ts {
symbolFlags & SymbolFlags.Signature ||
symbolFlags & SymbolFlags.Accessor ||
symbolKind === ScriptElementKind.memberFunctionElement) {
const allSignatures = type.getCallSignatures();
const allSignatures = type.getNonNullableType().getCallSignatures();
addSignatureDisplayParts(allSignatures[0], allSignatures);
}
}
Expand Down
26 changes: 0 additions & 26 deletions tests/baselines/reference/classWithOptionalParameter.errors.txt

This file was deleted.

26 changes: 26 additions & 0 deletions tests/baselines/reference/classWithOptionalParameter.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== tests/cases/conformance/types/namedTypes/classWithOptionalParameter.ts ===
// classes do not permit optional parameters, these are errors

class C {
>C : Symbol(C, Decl(classWithOptionalParameter.ts, 0, 0))

x?: string;
>x : Symbol(C.x, Decl(classWithOptionalParameter.ts, 2, 9))

f?() {}
>f : Symbol(C.f, Decl(classWithOptionalParameter.ts, 3, 15))
}

class C2<T> {
>C2 : Symbol(C2, Decl(classWithOptionalParameter.ts, 5, 1))
>T : Symbol(T, Decl(classWithOptionalParameter.ts, 7, 9))

x?: T;
>x : Symbol(C2.x, Decl(classWithOptionalParameter.ts, 7, 13))
>T : Symbol(T, Decl(classWithOptionalParameter.ts, 7, 9))

f?(x: T) {}
>f : Symbol(C2.f, Decl(classWithOptionalParameter.ts, 8, 10))
>x : Symbol(x, Decl(classWithOptionalParameter.ts, 9, 7))
>T : Symbol(T, Decl(classWithOptionalParameter.ts, 7, 9))
}
26 changes: 26 additions & 0 deletions tests/baselines/reference/classWithOptionalParameter.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== tests/cases/conformance/types/namedTypes/classWithOptionalParameter.ts ===
// classes do not permit optional parameters, these are errors

class C {
>C : C

x?: string;
>x : string

f?() {}
>f : () => void
}

class C2<T> {
>C2 : C2<T>
>T : T

x?: T;
>x : T
>T : T

f?(x: T) {}
>f : (x: T) => void
>x : T
>T : T
}
4 changes: 2 additions & 2 deletions tests/baselines/reference/declFileConstructors.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export declare class ConstructorWithPrivateParameterProperty {
constructor(x: string);
}
export declare class ConstructorWithOptionalParameterProperty {
x: string;
x?: string;
constructor(x?: string);
}
export declare class ConstructorWithParameterInitializer {
Expand Down Expand Up @@ -281,7 +281,7 @@ declare class GlobalConstructorWithPrivateParameterProperty {
constructor(x: string);
}
declare class GlobalConstructorWithOptionalParameterProperty {
x: string;
x?: string;
constructor(x?: string);
}
declare class GlobalConstructorWithParameterInitializer {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts(1,14): error TS1112: A class member cannot be declared optional.
tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts(1,14): error TS1162: An object member cannot be declared optional.


==== tests/cases/compiler/objectLiteralMemberWithQuestionMark1.ts (1 errors) ====
var v = { foo?() { } }
~
!!! error TS1112: A class member cannot be declared optional.
!!! error TS1162: An object member cannot be declared optional.
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties.ts(12,6): error TS1112: A class member cannot be declared optional.
tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties.ts(20,6): error TS1112: A class member cannot be declared optional.
tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties.ts(24,6): error TS1162: An object member cannot be declared optional.


==== tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties.ts (3 errors) ====
==== tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties.ts (1 errors) ====
// Basic uses of optional properties

var a: {
Expand All @@ -15,19 +13,15 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith
}

class C {
x?: number; // error
~
!!! error TS1112: A class member cannot be declared optional.
x?: number; // ok
}

interface I2<T> {
x?: T; // ok
}

class C2<T> {
x?: T; // error
~
!!! error TS1112: A class member cannot be declared optional.
x?: T; // ok
}

var b = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ interface I {
}

class C {
x?: number; // error
x?: number; // ok
}

interface I2<T> {
x?: T; // ok
}

class C2<T> {
x?: T; // error
x?: T; // ok
}

var b = {
Expand Down
Loading