X Tutup
Skip to content
Merged
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
52 changes: 43 additions & 9 deletions src/transformation/utils/typescript/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@ import * as ts from "typescript";
import { TransformationContext } from "../../context";

export function isTypeWithFlags(context: TransformationContext, type: ts.Type, flags: ts.TypeFlags): boolean {
const predicate = (type: ts.Type) => {
if (type.symbol) {
const baseConstraint = context.checker.getBaseConstraintOfType(type);
if (baseConstraint && baseConstraint !== type) {
return isTypeWithFlags(context, baseConstraint, flags);
}
}
return (type.flags & flags) !== 0;
};
const predicate = (type: ts.Type) => (type.flags & flags) !== 0;

return typeAlwaysSatisfies(context, type, predicate);
}
Expand All @@ -20,6 +12,11 @@ export function typeAlwaysSatisfies(
type: ts.Type,
predicate: (type: ts.Type) => boolean
): boolean {
const baseConstraint = context.checker.getBaseConstraintOfType(type);
if (baseConstraint) {
type = baseConstraint;
}

if (predicate(type)) {
return true;
}
Expand All @@ -40,6 +37,14 @@ export function typeCanSatisfy(
type: ts.Type,
predicate: (type: ts.Type) => boolean
): boolean {
const baseConstraint = context.checker.getBaseConstraintOfType(type);
if (!baseConstraint) {
// type parameter with no constraint can be anything, assume it might satisfy predicate
if (type.isTypeParameter()) return true;
} else {
type = baseConstraint;
}

if (predicate(type)) {
return true;
}
Expand Down Expand Up @@ -110,3 +115,32 @@ export function isArrayType(context: TransformationContext, type: ts.Type): bool
export function isFunctionType(type: ts.Type): boolean {
return type.getCallSignatures().length > 0;
}

export function canBeFalsy(context: TransformationContext, type: ts.Type): boolean {
const strictNullChecks = context.options.strict === true || context.options.strictNullChecks === true;
const falsyFlags =
ts.TypeFlags.Boolean |
ts.TypeFlags.BooleanLiteral |
ts.TypeFlags.Never |
ts.TypeFlags.Void |
ts.TypeFlags.Unknown |
ts.TypeFlags.Any |
ts.TypeFlags.Undefined |
ts.TypeFlags.Null;
return typeCanSatisfy(
context,
type,
type => (type.flags & falsyFlags) !== 0 || (!strictNullChecks && !type.isLiteral())
);
}

export function canBeFalsyWhenNotNull(context: TransformationContext, type: ts.Type): boolean {
const falsyFlags =
ts.TypeFlags.Boolean |
ts.TypeFlags.BooleanLiteral |
ts.TypeFlags.Never |
ts.TypeFlags.Void |
ts.TypeFlags.Unknown |
ts.TypeFlags.Any;
return typeCanSatisfy(context, type, type => (type.flags & falsyFlags) !== 0);
}
7 changes: 2 additions & 5 deletions src/transformation/visitors/binary-expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as lua from "../../../LuaAST";
import { FunctionVisitor, TransformationContext } from "../../context";
import { wrapInToStringForConcat } from "../../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
import { isStandardLibraryType, isStringType, typeCanSatisfy } from "../../utils/typescript";
import { canBeFalsyWhenNotNull, isStandardLibraryType, isStringType } from "../../utils/typescript";
import { transformTypeOfBinaryExpression } from "../typeof";
import { transformAssignmentExpression, transformAssignmentStatement } from "./assignments";
import { BitOperator, isBitOperator, transformBinaryBitOperation } from "./bit";
Expand Down Expand Up @@ -269,10 +269,7 @@ function transformNullishCoalescingOperationNoPrecedingStatements(
const lhsType = context.checker.getTypeAtLocation(node.left);

// Check if we can take a shortcut to 'lhs or rhs' if the left-hand side cannot be 'false'.
const typeCanBeFalse = (type: ts.Type) =>
(type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Boolean)) !== 0 ||
(type.flags & ts.TypeFlags.BooleanLiteral & ts.TypeFlags.PossiblyFalsy) !== 0;
if (typeCanSatisfy(context, lhsType, typeCanBeFalse)) {
if (canBeFalsyWhenNotNull(context, lhsType)) {
// reuse logic from case with preceding statements
const [precedingStatements, result] = createShortCircuitBinaryExpressionPrecedingStatements(
context,
Expand Down
24 changes: 1 addition & 23 deletions src/transformation/visitors/conditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,7 @@ import { FunctionVisitor, TransformationContext } from "../context";
import { transformInPrecedingStatementScope } from "../utils/preceding-statements";
import { performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";
import { transformBlockOrStatement } from "./block";

function canBeFalsy(context: TransformationContext, type: ts.Type): boolean {
const strictNullChecks = context.options.strict === true || context.options.strictNullChecks === true;

const falsyFlags =
ts.TypeFlags.Boolean |
ts.TypeFlags.BooleanLiteral |
ts.TypeFlags.Undefined |
ts.TypeFlags.Null |
ts.TypeFlags.Never |
ts.TypeFlags.Void |
ts.TypeFlags.Any;

if (type.flags & falsyFlags) {
return true;
} else if (!strictNullChecks && !type.isLiteral()) {
return true;
} else if (type.isUnion()) {
return type.types.some(subType => canBeFalsy(context, subType));
} else {
return false;
}
}
import { canBeFalsy } from "../utils/typescript";

function transformProtectedConditionalExpression(
context: TransformationContext,
Expand Down
13 changes: 13 additions & 0 deletions test/unit/conditionals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,16 @@ test.each(["true", "false", "a < 4", "a == 8"])("Ternary Conditional Delayed (%p
return delay();
`.expectToMatchJsResult();
});

test.each([false, true, null])("Ternary conditional with generic whenTrue branch (%p)", trueVal => {
util.testFunction`
function ternary<B, C>(a: boolean, b: B, c: C) {
return a ? b : c
}
return ternary(true, ${trueVal}, "wasFalse")
`
.setOptions({
strictNullChecks: true,
})
.expectToMatchJsResult();
});
11 changes: 10 additions & 1 deletion test/unit/nullishCoalescing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ test("nullish-coalescing operator with side effect rhs", () => {

test("nullish-coalescing operator with vararg", () => {
util.testFunction`

function foo(...args: any[]){
return args
}
Expand All @@ -54,3 +54,12 @@ test("nullish-coalescing operator with vararg", () => {
return bar(1, 2)
`.expectToMatchJsResult();
});

test.each([true, false, null])("nullish-coalescing with generic lhs (%p)", lhs => {
util.testFunction`
function coalesce<A, B>(a: A, b: B) {
return a ?? b
}
return coalesce(${lhs}, "wasNull")
`.expectToMatchJsResult();
});
X Tutup