Skip to content

Don't error with "used before assigned" when declared type includes void #52054

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

Closed
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19541,10 +19541,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return false;
}

function containsUndefinedType(type: Type) {
function containsUndefinedType(type: Type): boolean {
return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined);
}

function containsVoidType(type: Type): boolean {
return type.flags & TypeFlags.Union ? containsType((type as UnionType).types, voidType) : type === voidType;
}

function isStringIndexSignatureOnlyType(type: Type): boolean {
return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) ||
type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) ||
Expand Down Expand Up @@ -27313,7 +27317,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return convertAutoToAny(flowType);
}
}
else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) {
else if (!assumeInitialized && !containsUndefinedType(type) && !containsVoidType(type) && containsUndefinedType(flowType)) {
Copy link
Contributor Author

@Andarist Andarist Dec 30, 2022

Choose a reason for hiding this comment

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

The problem is that the typeof expression is the antecedent of the value variable here and it changes the void type somewhat unexpectedly.

narrowTypeByLiteralExpression discards void from string | void type for the assumeTrue === false case here.

So when we get to this line here we have:

type // string | void
flowType // string | undefined

As the other antecedent pushes undefined into the antecedentTypes in the getTypeAtFlowBranchLabel.

I don't think this can be changed since this is how TS behaves today:

declare const a: string | void

if (typeof a === 'undefined') {
  a // undefined
}

if (typeof a !== 'undefined') {
  a // string
}

So checking against voidType here was my best shot at fixing this.

EDIT: Alternatively... maybe the antecedents are set incorrectly here. I don't quite understand why value is "linked" to this binary expression. I didn't dive into CFA implementation too much yet so I didn't even attempt to explore the possibilities there

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
=== tests/cases/conformance/controlFlow/controlFlowVoidNoUsedBeforeAssigned.ts ===
// repro #52003

declare const fn: () => string | void;
>fn : Symbol(fn, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 2, 13))

const value = fn();
>value : Symbol(value, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 3, 5))
>fn : Symbol(fn, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 2, 13))

const result = {
>result : Symbol(result, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 5, 5))

type: typeof value === "undefined" ? "A" : "B",
>type : Symbol(type, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 5, 16))
>value : Symbol(value, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 3, 5))

value: value,
>value : Symbol(value, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 6, 51))
>value : Symbol(value, Decl(controlFlowVoidNoUsedBeforeAssigned.ts, 3, 5))

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
=== tests/cases/conformance/controlFlow/controlFlowVoidNoUsedBeforeAssigned.ts ===
// repro #52003

declare const fn: () => string | void;
>fn : () => string | void

const value = fn();
>value : string | void
>fn() : string | void
>fn : () => string | void

const result = {
>result : { type: string; value: string | undefined; }
>{ type: typeof value === "undefined" ? "A" : "B", value: value,} : { type: string; value: string | undefined; }

type: typeof value === "undefined" ? "A" : "B",
>type : string
>typeof value === "undefined" ? "A" : "B" : "A" | "B"
>typeof value === "undefined" : boolean
>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>value : string | void
>"undefined" : "undefined"
>"A" : "A"
>"B" : "B"

value: value,
>value : string | undefined
>value : string | undefined

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @strict: true
// @noEmit: true

// repro #52003

declare const fn: () => string | void;
const value = fn();

const result = {
type: typeof value === "undefined" ? "A" : "B",
value: value,
};