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
75 changes: 60 additions & 15 deletions tslang/include/TypeScript/LowerToLLVM/OptionalLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ class OptionalLogicHelper
template <typename StdIOpTy, typename V1, V1 v1, typename StdFOpTy, typename V2, V2 v2>
mlir::Value logicalOp(SyntaxKind opCmpCode)
{
auto loc = binOp->getLoc();

auto left = binOp->getOperand(0);
auto right = binOp->getOperand(1);
auto leftType = left.getType();
Expand Down Expand Up @@ -67,21 +65,68 @@ class OptionalLogicHelper
return whenOneValueIsUndef(opCmpCode, right, left);
}

// TODO: rewrite code to take in account that Opt value can be undefined
if (leftOptType)
{
auto leftSubType = leftOptType.getElementType();
left = rewriter.create<mlir_ts::ValueOp>(loc, leftSubType, left);
}
return whenOneOptValue<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(opCmpCode);
}

if (rightOptType)
{
auto rightSubType = rightOptType.getElementType();
right = rewriter.create<mlir_ts::ValueOp>(loc, rightSubType, right);
}
// One side is optional, the other a value. An optional holding undefined equals no value - not
// 0, not false, not null - so the values are compared only when it holds one: unwrapped without
// that test it read whatever the empty optional stored, and `undefined === 0` was true.
template <typename StdIOpTy, typename V1, V1 v1, typename StdFOpTy, typename V2, V2 v2>
mlir::Value whenOneOptValue(SyntaxKind opCmpCode)
{
auto loc = binOp->getLoc();

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

auto llvmBoolType = typeConverter.convertType(th.getBooleanType());

return LogicOp<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(binOp, opCmpCode, left, left.getType(), right, right.getType(), rewriter,
typeConverter, compileOptions);
auto left = binOp->getOperand(0);
auto right = binOp->getOperand(1);
auto leftOptType = dyn_cast<mlir_ts::OptionalType>(left.getType());
auto rightOptType = dyn_cast<mlir_ts::OptionalType>(right.getType());
auto otherIsNull = isa<mlir_ts::NullType>((leftOptType ? right : left).getType());

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

return clh.conditionalExpressionLowering(
loc, llvmBoolType, hasValue,
[&](OpBuilder &builder, Location loc) {
if (leftOptType)
{
left = rewriter.create<mlir_ts::ValueOp>(loc, leftOptType.getElementType(), left);
}

if (rightOptType)
{
right = rewriter.create<mlir_ts::ValueOp>(loc, rightOptType.getElementType(), right);
}

mlir::Value result = LogicOp<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(
binOp, opCmpCode, left, left.getType(), right, right.getType(), rewriter, typeConverter, compileOptions);
if (result && result.getType() != llvmBoolType)
{
result = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmBoolType, result);
}

return result;
},
[&](OpBuilder &builder, Location loc) {
// undefined against a value: only "not equal" holds, and no ordering does - except
// that loosely undefined equals null
switch (opCmpCode)
{
case SyntaxKind::EqualsEqualsToken:
return clh.createI1ConstantOf(otherIsNull);
case SyntaxKind::ExclamationEqualsToken:
return clh.createI1ConstantOf(!otherIsNull);
case SyntaxKind::ExclamationEqualsEqualsToken:
return clh.createI1ConstantOf(true);
default:
return clh.createI1ConstantOf(false);
}
});
}

template <typename StdIOpTy, typename V1, V1 v1, typename StdFOpTy, typename V2, V2 v2>
Expand Down
17 changes: 13 additions & 4 deletions tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ

LLVMTypeConverterHelper llvmtch(&typeConverter);

// A union's tag is read out of its LLVM struct. The lowering pattern hands over its operands
// converted already, but an optional's unwrapped value (OptionalLogicHelper) is still a ts.union.
auto asLLVMUnion = [&](mlir::Value value, mlir_ts::UnionType unionType) -> mlir::Value {
auto llvmUnionType = typeConverter.convertType(unionType);
return value.getType() == llvmUnionType
? value
: builder.create<mlir_ts::DialectCastOp>(loc, llvmUnionType, value).getResult();
};

if (isa<mlir_ts::OptionalType>(leftType) || isa<mlir_ts::OptionalType>(rightType))
{
return OptionalTypeLogicalOp<StdIOpTy, V1, v1, StdFOpTy, V2, v2>(binOp, op, builder, typeConverter, compileOptions);
Expand Down Expand Up @@ -212,8 +221,8 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ
mlir::Type baseType;
if (mth.isUnionTypeNeedsTag(loc, unionType, baseType))
{
auto tagValue = builder.create<LLVM::ExtractValueOp>(loc, llvmtch.typeConverter->convertType(mth.getStringType()), right,
MLIRHelper::getStructIndex(builder, UNION_TAG_INDEX));
auto tagValue = builder.create<LLVM::ExtractValueOp>(loc, llvmtch.typeConverter->convertType(mth.getStringType()),
asLLVMUnion(right, unionType), MLIRHelper::getStructIndex(builder, UNION_TAG_INDEX));
auto nullStr = builder.create<mlir_ts::ConstantOp>(loc, mth.getStringType(), builder.getStringAttr("null"));
auto cmpOp = builder.create<mlir_ts::StringCompareOp>(
loc, mth.getBooleanType(), tagValue, nullStr, builder.getI32IntegerAttr((int)op));
Expand Down Expand Up @@ -302,8 +311,8 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ
right.getDefiningOp<mlir_ts::NullOp>() || right.getDefiningOp<LLVM::ZeroOp>() || matchPattern(right, m_Zero());
if (isRightNullValue)
{
auto tagValue = builder.create<LLVM::ExtractValueOp>(loc, llvmtch.typeConverter->convertType(mth.getStringType()), left,
MLIRHelper::getStructIndex(builder, UNION_TAG_INDEX));
auto tagValue = builder.create<LLVM::ExtractValueOp>(loc, llvmtch.typeConverter->convertType(mth.getStringType()),
asLLVMUnion(left, unionType), MLIRHelper::getStructIndex(builder, UNION_TAG_INDEX));
auto nullStr = builder.create<mlir_ts::ConstantOp>(loc, mth.getStringType(), builder.getStringAttr("null"));
auto cmpOp = builder.create<mlir_ts::StringCompareOp>(
loc, mth.getBooleanType(), tagValue, nullStr, builder.getI32IntegerAttr((int)op));
Expand Down
37 changes: 29 additions & 8 deletions tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ class MLIRTypeHelper
{
}

// the options are a copy, taken before a file's `// @strict-null` pragma is read: the pragma
// has to reach this copy too, or unions are built strict in a file compiled non-strict
void setStrictNullChecks(bool strictNullChecks)
{
compileOptions.strictNullChecks = strictNullChecks;
}

MLIRTypeHelper(
mlir::MLIRContext *context,
CompileOptions compileOptions,
Expand Down Expand Up @@ -3166,6 +3173,14 @@ class MLIRTypeHelper
return getUnionTypeWithMerge(location, types, mergeLiterals, mergeTypes);
}

// Without strict null checks a nullable type (string, a class) holds null in its own pointer, so
// null goes away beside it. A union of several types is a tagged value, not one pointer: there
// null has nowhere to go but a member of its own.
bool needsNullMember(mlir::ArrayRef<mlir::Type> types)
{
return llvm::count_if(types, [](mlir::Type type) { return !isa<mlir_ts::NullType>(type); }) > 1;
}

// TODO: review all union merge logic
mlir::Type getUnionTypeMergeTypes(mlir::Location location, UnionTypeProcessContext &unionContext, bool mergeLiterals = true, bool mergeTypes = true, bool disableStrickNullCheck = false)
{
Expand Down Expand Up @@ -3213,17 +3228,22 @@ class MLIRTypeHelper
if (typesAll.size() == 1)
{
auto resType = typesAll.front();
if (unionContext.isUndefined)

// the null the checker needs goes in before undefined wraps the type: undefined makes it
// optional, it must not take the null away - `string | null | undefined` accepts null as
// `string | null` does
if (compileOptions.strictNullChecks && !disableStrickNullCheck && unionContext.isNullable
&& !isa<mlir_ts::NullType>(resType))
{
return mlir_ts::OptionalType::get(resType);
}
resType = mlir_ts::UnionType::get(context, {resType, getNullType()});
}

if (compileOptions.strictNullChecks && !disableStrickNullCheck && unionContext.isNullable)
if (unionContext.isUndefined)
{
return mlir_ts::UnionType::get(context, {resType, getNullType()});
return mlir_ts::OptionalType::get(resType);
}

return resType;
return resType;
}

// merge types
Expand All @@ -3233,7 +3253,7 @@ class MLIRTypeHelper
mlir::SmallVector<mlir::Type> mergedTypesAll;
this->mergeTypes(location, typesAll, mergedTypesAll);

if (compileOptions.strictNullChecks && unionContext.isNullable)
if (unionContext.isNullable && (compileOptions.strictNullChecks || needsNullMember(mergedTypesAll)))
{
mergedTypesAll.push_back(getNullType());
}
Expand All @@ -3247,7 +3267,8 @@ class MLIRTypeHelper
return retType;
}

if (compileOptions.strictNullChecks && !disableStrickNullCheck && unionContext.isNullable)
if (unionContext.isNullable
&& ((compileOptions.strictNullChecks && !disableStrickNullCheck) || needsNullMember(typesAll)))
{
typesAll.push_back(getNullType());
}
Expand Down
39 changes: 39 additions & 0 deletions tslang/lib/TypeScript/MLIRGenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,11 @@ namespace mlirgen
return *result;
}

if (auto result = castToNullableUnionType(location, type, value, valueType, genContext))
{
return *result;
}

// const dest: cast via the unwrapped source type instead
if (auto constType = dyn_cast<mlir_ts::ConstType>(type))
{
Expand Down Expand Up @@ -1342,6 +1347,40 @@ namespace mlirgen
return std::nullopt;
}

// `T | null` without a tag is T's own pointer, and null only there for the checker (strict null
// checks). A value of another type becomes a T first - an object literal an interface, a const
// array an array - the way it would be cast to T alone: cast straight to the union, that
// conversion was left undone for the lowering, which can't do it.
std::optional<ValueOrLogicalResult> MLIRGenImpl::castToNullableUnionType(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext)
{
auto unionType = dyn_cast<mlir_ts::UnionType>(type);
if (!unionType)
{
return std::nullopt;
}

mlir::Type baseType;
if (mth.isUnionTypeNeedsTag(location, unionType, baseType) || !baseType || baseType == valueType)
{
return std::nullopt;
}

if (isa<mlir_ts::NullType>(valueType) || isa<mlir_ts::UndefinedType>(valueType) || isa<mlir_ts::AnyType>(valueType)
|| isa<mlir_ts::OptionalType>(valueType) || isa<mlir_ts::UnionType>(valueType))
{
return std::nullopt;
}

auto types = unionType.getTypes();
if (std::find(types.begin(), types.end(), valueType) != types.end())
{
return std::nullopt;
}

CAST(value, location, baseType, value, genContext);
return V(builder.create<mlir_ts::CastOp>(location, type, value));
}

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 Down
64 changes: 64 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5861,6 +5861,54 @@ class MLIRGenImpl
return mlir::success();
}

// An optional number compared with a number. The lowering compares them only when the optional
// holds a value (OptionalLogicHelper), so it stays optional here: synced as a plain value it was
// cast out of the optional first, an empty one reading as 0 - and the promotion saw only the other
// operand's type, so against an integer literal 0.5 became an s32 and `0.5 === 0` was true. Both
// sides take the wider of the optional's element type and the other operand's type.
ValueOrLogicalResult syncOptionalNumericTypes(mlir::Location location, mlir::Value &leftExpressionValue, mlir::Value &rightExpressionValue, const GenContext &genContext)
{
auto leftOptType = dyn_cast<mlir_ts::OptionalType>(leftExpressionValue.getType());
auto rightOptType = dyn_cast<mlir_ts::OptionalType>(rightExpressionValue.getType());
if (!leftOptType == !rightOptType)
{
return mlir::success();
}

auto &optValue = leftOptType ? leftExpressionValue : rightExpressionValue;
auto &otherValue = leftOptType ? rightExpressionValue : leftExpressionValue;
auto elementType = (leftOptType ? leftOptType : rightOptType).getElementType();
auto otherType = otherValue.getType();
if (auto literalType = dyn_cast<mlir_ts::LiteralType>(otherType))
{
otherType = literalType.getElementType();
}

if (!isPromotableNumeric(elementType) || !isPromotableNumeric(otherType))
{
return mlir::success();
}

auto types = numericPromotionOrder();
auto commonType = llvm::find_if(types, [&](mlir::Type type) { return type == elementType || type == otherType; });
if (commonType == types.end())
{
return mlir::success();
}

if (elementType != *commonType)
{
CAST(optValue, location, getOptionalType(*commonType), optValue, genContext);
}

if (otherValue.getType() != *commonType)
{
CAST(otherValue, location, *commonType, otherValue, genContext);
}

return optValue;
}

// The order the arithmetic operators promote in, widest first: the first type either
// operand already has is the one both are cast to. Shared by `+` and by the general
// arithmetic/comparison path below, which is the point -- they disagreed, and `+` was
Expand Down Expand Up @@ -6169,6 +6217,21 @@ class MLIRGenImpl
// TODO: do we need to sync type for all Ops?
auto types = numericPromotionOrder();

// `-` and `*` fall into this case too: arithmetic on an optional is left as it was
if (opCode != SyntaxKind::MinusToken && opCode != SyntaxKind::AsteriskToken)
{
auto o = syncOptionalNumericTypes(location, leftExpressionValue, rightExpressionValue, genContext);
if (o.value)
{
break;
}

if (mlir::failed(o.result))
{
return mlir::failure();
}
}

auto r = syncUnionTypes(location, leftExpressionValue, rightExpressionValue, genContext);
if (r.value)
{
Expand Down Expand Up @@ -11335,6 +11398,7 @@ class MLIRGenImpl
std::optional<ValueOrLogicalResult> castToOptionalType(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext);

std::optional<ValueOrLogicalResult> castToTaggedUnionType(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext);
std::optional<ValueOrLogicalResult> castToNullableUnionType(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext);

// union or optional or any or opaque source type
std::optional<ValueOrLogicalResult> castFromSourceSpecialCases(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext);
Expand Down
1 change: 1 addition & 0 deletions tslang/lib/TypeScript/MLIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ namespace mlirgen
if (option != strictNull->second.front().end())
{
compileOptions.strictNullChecks = option->second._arg.value == S("true");
mth.setStrictNullChecks(compileOptions.strictNullChecks);
}
}
}
Expand Down
Loading
Loading