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
33 changes: 17 additions & 16 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 @@ -3216,7 +3217,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 +7618,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 +8097,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 +8349,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 +9924,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 +9942,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 +13400,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 +18294,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 +18303,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 +18524,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
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
}
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 @@ -16,8 +14,6 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith

class C {
x?: number; // error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments are out of date now.

~
!!! error TS1112: A class member cannot be declared optional.
}

interface I2<T> {
Expand All @@ -26,8 +22,6 @@ tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWith

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

var b = {
Expand Down
76 changes: 76 additions & 0 deletions tests/baselines/reference/optionalMethods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//// [optionalMethods.ts]

interface Foo {
a: number;
b?: number;
f(): number;
g?(): number;
}

function test1(x: Foo) {
x.a;
x.b;
x.f;
x.g;
let f1 = x.f();
let g1 = x.g && x.g();
let g2 = x.g ? x.g() : 0;
}

class Bar {
a: number;
b?: number;
f() {
return 1;
}
g?(): number; // Body of optional method can be omitted
h?() {
return 2;
}
}

function test2(x: Bar) {
x.a;
x.b;
x.f;
x.g;
let f1 = x.f();
let g1 = x.g && x.g();
let g2 = x.g ? x.g() : 0;
let h1 = x.h && x.h();
let h2 = x.h ? x.h() : 0;
}


//// [optionalMethods.js]
function test1(x) {
x.a;
x.b;
x.f;
x.g;
var f1 = x.f();
var g1 = x.g && x.g();
var g2 = x.g ? x.g() : 0;
}
var Bar = (function () {
function Bar() {
}
Bar.prototype.f = function () {
return 1;
};
Bar.prototype.h = function () {
return 2;
};
return Bar;
}());
function test2(x) {
x.a;
x.b;
x.f;
x.g;
var f1 = x.f();
var g1 = x.g && x.g();
var g2 = x.g ? x.g() : 0;
var h1 = x.h && x.h();
var h2 = x.h ? x.h() : 0;
}
Loading