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
12 changes: 12 additions & 0 deletions tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,18 @@ class CastLogicHelper

if (auto arrType = dyn_cast<mlir_ts::ArrayType>(resType))
{
// `number[] | null` has no tag - it is the array itself, null when it holds null (the
// union below does the same for every other type, but an array never got that far)
if (auto inUnionType = dyn_cast<mlir_ts::UnionType>(inType))
{
MLIRTypeHelper mth(inUnionType.getContext(), compileOptions);
mlir::Type baseType;
if (!mth.isUnionTypeNeedsTag(loc, inUnionType, baseType))
{
return cast(in, baseType, tch.convertType(baseType), resType, resLLVMType);
}
}

return castToArrayType(in, inType, resType);
}

Expand Down
65 changes: 59 additions & 6 deletions tslang/include/TypeScript/LowerToLLVM/OptionalLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,72 @@ class OptionalLogicHelper
return WhenBothOptValues<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(opCmpCode);
}

if (isa<mlir_ts::UndefinedType>(rightType))
if (isa<mlir_ts::UndefinedType>(rightType) || isa<mlir_ts::UndefinedType>(leftType))
{
auto optValue = isa<mlir_ts::UndefinedType>(rightType) ? left : right;
auto undefValue = isa<mlir_ts::UndefinedType>(rightType) ? right : left;
if (isLooseEquality(opCmpCode) && canHoldNull(mlir::cast<mlir_ts::OptionalType>(optValue.getType()).getElementType()))
{
return whenLooseUndefAgainstNullable<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(opCmpCode, optValue);
}

// when we have undef in 1 of values we do not condition to test actual values
return whenOneValueIsUndef(opCmpCode, left, right);
return whenOneValueIsUndef(opCmpCode, optValue, undefValue);
}

if (isa<mlir_ts::UndefinedType>(leftType))
return whenOneOptValue<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(opCmpCode);
}

static bool isLooseEquality(SyntaxKind opCmpCode)
{
return opCmpCode == SyntaxKind::EqualsEqualsToken || opCmpCode == SyntaxKind::ExclamationEqualsToken;
}

// a `T | null` (strict null checks), or a type whose own pointer holds null (without them)
static bool canHoldNull(mlir::Type type)
{
if (auto unionType = dyn_cast<mlir_ts::UnionType>(type))
{
// when we have undef in 1 of values we do not condition to test actual values
return whenOneValueIsUndef(opCmpCode, right, left);
return llvm::any_of(unionType.getTypes(), [](mlir::Type member) { return isa<mlir_ts::NullType>(member); });
}

return whenOneOptValue<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(opCmpCode);
return MLIRTypeCore::isNullableTypeNoUnion(type);
}

// Loosely, null equals undefined: an optional holding null is `== undefined` as well, so it
// is not enough to ask whether it holds a value - a value it holds may be null. The value is
// read only when there is one.
template <typename StdIOpTy, typename V1, V1 v1, typename StdFOpTy, typename V2, V2 v2>
mlir::Value whenLooseUndefAgainstNullable(SyntaxKind opCmpCode, mlir::Value optValue)
{
auto loc = binOp->getLoc();

TypeHelper th(rewriter);
CodeLogicHelper clh(binOp, rewriter);

auto llvmBoolType = typeConverter.convertType(th.getBooleanType());
auto optType = mlir::cast<mlir_ts::OptionalType>(optValue.getType());

auto hasValueBool = rewriter.create<mlir_ts::HasValueOp>(loc, th.getBooleanType(), optValue);
auto hasValue = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmBoolType, hasValueBool);

return clh.conditionalExpressionLowering(
loc, llvmBoolType, hasValue,
[&](OpBuilder &builder, Location loc) {
mlir::Value value = rewriter.create<mlir_ts::ValueOp>(loc, optType.getElementType(), optValue);
mlir::Value nullValue = rewriter.create<mlir_ts::NullOp>(loc, mlir_ts::NullType::get(rewriter.getContext()));
mlir::Value result = LogicOp<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(
binOp, opCmpCode, value, value.getType(), nullValue, nullValue.getType(), rewriter, typeConverter, compileOptions);
if (result && result.getType() != llvmBoolType)
{
result = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmBoolType, result);
}

return result;
},
[&](OpBuilder &builder, Location loc) {
return clh.createI1ConstantOf(opCmpCode == SyntaxKind::EqualsEqualsToken);
});
}

// One side is optional, the other a value. An optional holding undefined equals no value - not
Expand Down
72 changes: 72 additions & 0 deletions tslang/lib/TypeScript/MLIRGenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,16 @@ namespace mlirgen
return V(builder.create<mlir_ts::CastOp>(location, type, value));
}
}

// one member and null (`{ k: number } | null`): what is not null is that member -
// an object literal `{ k: 5 }`, whose field is an s32, is converted to it
if (!isa<mlir_ts::NullType>(valueType) && types.size() == 2
&& llvm::any_of(types, [](mlir::Type subType) { return isa<mlir_ts::NullType>(subType); }))
{
auto memberType = isa<mlir_ts::NullType>(types[0]) ? types[1] : types[0];
CAST(value, location, memberType, value, genContext);
return V(builder.create<mlir_ts::CastOp>(location, type, value));
}
}
else
{
Expand Down Expand Up @@ -1381,6 +1391,63 @@ namespace mlirgen
return V(builder.create<mlir_ts::CastOp>(location, type, value));
}

// `number | null`, `{ k: number } | null`: a value type or null is a union with a tag - one member
// and null. castFromUnion turns every member into the target type, and null is not a boolean
// ("can't cast from 'null' to 'boolean'"), nor does it know an object type at all. Tested for
// truth it is false when it holds null, as text it is "null". Anything else stays with
// castFromUnion: `let x: number = n` is refused, as TypeScript refuses it - a narrowing or `n!`
// reads the member.
std::optional<ValueOrLogicalResult> MLIRGenImpl::castFromNullableUnion(mlir::Location location, mlir::Type type, mlir::Value value, mlir_ts::UnionType unionType, const GenContext &genContext)
{
if (!isa<mlir_ts::BooleanType>(type) && !isa<mlir_ts::StringType>(type))
{
return std::nullopt;
}

mlir::Type memberType;
auto hasNull = false;
for (auto subType : unionType.getTypes())
{
if (isa<mlir_ts::NullType>(subType))
{
hasNull = true;
continue;
}

if (memberType)
{
return std::nullopt;
}

memberType = subType;
}

if (!hasNull || !memberType)
{
return std::nullopt;
}

auto readMember = [&]() { return V(builder.create<mlir_ts::GetValueFromUnionOp>(location, memberType, value)); };

auto nullValue = builder.create<mlir_ts::NullOp>(location, getNullType());
auto isNull = builder.create<mlir_ts::LogicalBinaryOp>(
location, getBooleanType(), builder.getI32IntegerAttr((int)SyntaxKind::EqualsEqualsEqualsToken), value, nullValue);

MLIRCodeLogicHelper mclh(builder, location, compileOptions);
return mclh.conditionalValue(isNull,
[&]() -> ValueOrLogicalResult {
if (isa<mlir_ts::BooleanType>(type))
{
return V(builder.create<mlir_ts::ConstantOp>(location, getBooleanType(), builder.getBoolAttr(false)));
}

return V(builder.create<mlir_ts::ConstantOp>(location, getStringType(), builder.getStringAttr("null")));
},
[&](mlir::Type) -> ValueOrLogicalResult {
return cast(location, type, readMember(), genContext);
});
}

std::optional<ValueOrLogicalResult> MLIRGenImpl::castFromSourceSpecialCases(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext)
{
// union type to <basic type>
Expand All @@ -1391,6 +1458,11 @@ namespace mlirgen
mlir::Type baseType;
if (!toAny && mth.isUnionTypeNeedsTag(location, unionType, baseType))
{
if (auto result = castFromNullableUnion(location, type, value, unionType, genContext))
{
return *result;
}

return castFromUnion(location, type, value, genContext);
}

Expand Down
31 changes: 30 additions & 1 deletion tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,13 @@ namespace mlirgen
return resultReturnUnions;
}

auto resultReturnUnionAndValue =
binaryOpLogicForUnionAndValue(location, opCode, leftExpressionValue, rightExpressionValue, genContext);
if (resultReturnUnionAndValue.value || resultReturnUnionAndValue.failed())
{
return resultReturnUnionAndValue;
}

auto leftExpressionValueBeforeCast = leftExpressionValue;
auto rightExpressionValueBeforeCast = rightExpressionValue;

Expand Down Expand Up @@ -1284,7 +1291,29 @@ namespace mlirgen

ValueOrLogicalResult MLIRGenImpl::mlirGen(NonNullExpression nonNullExpression, const GenContext &genContext)
{
return mlirGen(nonNullExpression->expression, genContext);
auto result = mlirGen(nonNullExpression->expression, genContext);
EXIT_IF_FAILED_OR_NO_VALUE(result)
auto value = V(result);

// `n!` of a `number | null` (a union with a tag, one member and null) is that member, read out
// unchecked - the way a narrowing reads it. Left a union, it was cast to the member as any
// union is, and null is not a number.
if (auto unionType = dyn_cast<mlir_ts::UnionType>(value.getType()))
{
mlir::Type baseType;
if (mth.isUnionTypeNeedsTag(loc(nonNullExpression), unionType, baseType))
{
SmallVector<mlir::Type> members;
llvm::copy_if(unionType.getTypes(), std::back_inserter(members),
[](mlir::Type member) { return !isa<mlir_ts::NullType>(member); });
if (members.size() == 1 && members.size() != unionType.getTypes().size())
{
return V(builder.create<mlir_ts::GetValueFromUnionOp>(loc(nonNullExpression), members.front(), value));
}
}
}

return value;
}

ValueOrLogicalResult MLIRGenImpl::mlirGen(OmittedExpression ommitedExpression, const GenContext &genContext)
Expand Down
Loading
Loading