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 symbols keep their declared type #8726

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 29 additions & 17 deletions src/compiler/checker.ts
Expand Up @@ -2880,10 +2880,6 @@ 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 @@ -2915,7 +2911,7 @@ namespace ts {

// Use type from type annotation if one is present
if (declaration.type) {
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ !!declaration.questionToken);
return getTypeFromTypeNode(declaration.type);
}

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

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

// If it is a short-hand property assignment, use the type of the identifier
Expand Down Expand Up @@ -3214,9 +3210,7 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
const type = createObjectType(TypeFlags.Anonymous, symbol);
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ?
addNullableKind(type, TypeFlags.Undefined) : type;
links.type = createObjectType(TypeFlags.Anonymous, symbol);
}
return links.type;
}
Expand Down Expand Up @@ -8100,11 +8094,19 @@ namespace ts {
checkCollisionWithCapturedThisVariable(node, node);
checkNestedBlockScopedBinding(node, symbol);

const type = getTypeOfSymbol(localOrExportSymbol);
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) {
let type = getTypeOfSymbol(localOrExportSymbol);
// If the identifier doesn't denote a variable, parameter, property, method, or accessor, or if the
// identifier is the target of an assignment, go with the type of the symbol.
if (!(localOrExportSymbol.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor)) || isAssignmentTarget(node)) {
return type;
}
const declaration = localOrExportSymbol.valueDeclaration;
// In strict null checking mode, for an optional parameter, property or method, include undefined
// in the type.
if (strictNullChecks && (localOrExportSymbol.flags & SymbolFlags.Optional ||
declaration && declaration.kind === SyntaxKind.Parameter && (<ParameterDeclaration>declaration).questionToken)) {
type = addNullableKind(type, TypeFlags.Undefined);
}
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
Expand Down Expand Up @@ -9950,9 +9952,19 @@ namespace ts {
checkClassPropertyAccess(node, left, apparentType, prop);
}

const propType = getTypeOfSymbol(prop);
if (node.kind !== SyntaxKind.PropertyAccessExpression || isAssignmentTarget(node) ||
!(propType.flags & TypeFlags.Union) && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor))) {
let propType = getTypeOfSymbol(prop);
// If the property access doesn't denote a variable, parameter, property, method, or accessor,
// if the property access denotes a non-optional method, or if the property access is the target
// of an assignment, go with the type of the symbol.
if (!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Method | SymbolFlags.Accessor)) ||
prop.flags & SymbolFlags.Method && !(prop.flags & SymbolFlags.Optional) || isAssignmentTarget(node)) {
return propType;
}
// In strict null checking mode, for an optional property or method, include undefined in the type.
if (strictNullChecks && prop.flags & SymbolFlags.Optional) {
propType = addNullableKind(propType, TypeFlags.Undefined);
}
if (node.kind !== SyntaxKind.PropertyAccessExpression) {
return propType;
}
const leftmostNode = getLeftmostIdentifierOrThis(node);
Expand Down Expand Up @@ -11441,8 +11453,8 @@ namespace ts {
function getTypeOfParameter(symbol: Symbol) {
const type = getTypeOfSymbol(symbol);
if (strictNullChecks) {
const declaration = symbol.valueDeclaration;
if (declaration && (<VariableLikeDeclaration>declaration).initializer) {
const declaration = <VariableLikeDeclaration>symbol.valueDeclaration;
if (declaration && (declaration.questionToken || declaration.initializer)) {
return addNullableKind(type, TypeFlags.Undefined);
}
}
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/asyncFunctionsAndStrictNullChecks.types
Expand Up @@ -9,78 +9,78 @@ declare namespace Windows.Foundation {
>TResult : TResult

then<U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>;
>then : { <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; }
>then : { <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; }
>U : U
>success : ((value: TResult) => IPromise<U>) | undefined
>success : (value: TResult) => IPromise<U>
>value : TResult
>TResult : TResult
>IPromise : IPromise<TResult>
>U : U
>error : ((error: any) => IPromise<U>) | undefined
>error : (error: any) => IPromise<U>
>error : any
>IPromise : IPromise<TResult>
>U : U
>progress : ((progress: any) => void) | undefined
>progress : (progress: any) => void
>progress : any
>IPromise : IPromise<TResult>
>U : U

then<U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>;
>then : { <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; }
>then : { <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; }
>U : U
>success : ((value: TResult) => IPromise<U>) | undefined
>success : (value: TResult) => IPromise<U>
>value : TResult
>TResult : TResult
>IPromise : IPromise<TResult>
>U : U
>error : ((error: any) => U) | undefined
>error : (error: any) => U
>error : any
>U : U
>progress : ((progress: any) => void) | undefined
>progress : (progress: any) => void
>progress : any
>IPromise : IPromise<TResult>
>U : U

then<U>(success?: (value: TResult) => U, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>;
>then : { <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; }
>then : { <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; }
>U : U
>success : ((value: TResult) => U) | undefined
>success : (value: TResult) => U
>value : TResult
>TResult : TResult
>U : U
>error : ((error: any) => IPromise<U>) | undefined
>error : (error: any) => IPromise<U>
>error : any
>IPromise : IPromise<TResult>
>U : U
>progress : ((progress: any) => void) | undefined
>progress : (progress: any) => void
>progress : any
>IPromise : IPromise<TResult>
>U : U

then<U>(success?: (value: TResult) => U, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>;
>then : { <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => IPromise<U>) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => IPromise<U>) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; <U>(success?: ((value: TResult) => U) | undefined, error?: ((error: any) => U) | undefined, progress?: ((progress: any) => void) | undefined): IPromise<U>; }
>then : { <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => IPromise<U>, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => IPromise<U>, progress?: (progress: any) => void): IPromise<U>; <U>(success?: (value: TResult) => U, error?: (error: any) => U, progress?: (progress: any) => void): IPromise<U>; }
>U : U
>success : ((value: TResult) => U) | undefined
>success : (value: TResult) => U
>value : TResult
>TResult : TResult
>U : U
>error : ((error: any) => U) | undefined
>error : (error: any) => U
>error : any
>U : U
>progress : ((progress: any) => void) | undefined
>progress : (progress: any) => void
>progress : any
>IPromise : IPromise<TResult>
>U : U

done<U>(success?: (value: TResult) => any, error?: (error: any) => any, progress?: (progress: any) => void): void;
>done : <U>(success?: ((value: TResult) => any) | undefined, error?: ((error: any) => any) | undefined, progress?: ((progress: any) => void) | undefined) => void
>done : <U>(success?: (value: TResult) => any, error?: (error: any) => any, progress?: (progress: any) => void) => void
>U : U
>success : ((value: TResult) => any) | undefined
>success : (value: TResult) => any
>value : TResult
>TResult : TResult
>error : ((error: any) => any) | undefined
>error : (error: any) => any
>error : any
>progress : ((progress: any) => void) | undefined
>progress : (progress: any) => void
>progress : any

cancel(): void;
Expand Down Expand Up @@ -121,8 +121,8 @@ declare function resolve2<T>(value: T): Windows.Foundation.IPromise<T>;
>T : T

async function sample2(x?: number) {
>sample2 : (x?: number | undefined) => Promise<void>
>x : number | undefined
>sample2 : (x?: number) => Promise<void>
>x : number

let x1 = await resolve1(x);
>x1 : number | undefined
Expand Down
34 changes: 17 additions & 17 deletions tests/baselines/reference/controlFlowDeleteOperator.types
Expand Up @@ -4,76 +4,76 @@ function f() {
>f : () => void

let x: { a?: number | string, b: number | string } = { b: 1 };
>x : { a?: number | string | undefined; b: number | string; }
>a : number | string | undefined
>x : { a?: number | string; b: number | string; }
>a : number | string
>b : number | string
>{ b: 1 } : { b: number; }
>b : number
>1 : number

x.a;
>x.a : number | string | undefined
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>a : number | string | undefined

x.b;
>x.b : number | string
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>b : number | string

x.a = 1;
>x.a = 1 : number
>x.a : number | string | undefined
>x : { a?: number | string | undefined; b: number | string; }
>a : number | string | undefined
>x.a : number | string
>x : { a?: number | string; b: number | string; }
>a : number | string
>1 : number

x.b = 1;
>x.b = 1 : number
>x.b : number | string
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>b : number | string
>1 : number

x.a;
>x.a : number
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>a : number

x.b;
>x.b : number
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>b : number

delete x.a;
>delete x.a : boolean
>x.a : number
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>a : number

delete x.b;
>delete x.b : boolean
>x.b : number
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>b : number

x.a;
>x.a : undefined
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>a : undefined

x.b;
>x.b : number | string
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
>b : number | string

x;
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }

delete x; // No effect
>delete x : boolean
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }

x;
>x : { a?: number | string | undefined; b: number | string; }
>x : { a?: number | string; b: number | string; }
}
Expand Up @@ -115,8 +115,8 @@ function f5() {
>f5 : () => void

let { x }: { x?: string | number } = { x: 1 };
>x : string | number | undefined
>x : string | number | undefined
>x : string | number
>x : string | number
>{ x: 1 } : { x: number; }
>x : number
>1 : number
Expand Down Expand Up @@ -150,12 +150,12 @@ function f6() {
>f6 : () => void

let { x }: { x?: string | number } = {};
>x : string | number | undefined
>x : string | number | undefined
>x : string | number
>x : string | number
>{} : {}

x;
>x : string | number | undefined
>x : string | number

let { y }: { y?: string | undefined } = {};
>y : string | undefined
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/iteratorsAndStrictNullChecks.types
Expand Up @@ -8,9 +8,9 @@ for (const x of ["a", "b"]) {
>"b" : string

x.substring;
>x.substring : (start: number, end?: number | undefined) => string
>x.substring : (start: number, end?: number) => string
>x : string
>substring : (start: number, end?: number | undefined) => string
>substring : (start: number, end?: number) => string
}

// Spread
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/optionalMethods.js
Expand Up @@ -130,7 +130,7 @@ declare class Bar {
e: number;
a: number;
b?: number;
c?: number | undefined;
c?: number;
constructor(d?: number, e?: number);
f(): number;
g?(): number;
Expand Down