Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional class properties #8625

Merged
merged 11 commits into from
May 18, 2016
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