mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
27a02602b9
A `let` binding declared `public`, `package`, or `open` participates in its module's ABI surface as a symbol — clients link against the declaration, not against its value, so the author is free to change the value in a future release. Folding such a reference in a literal expression would risk "baking" a module-private value into client code and remove the author's room to evolve the binding. Reject these references with a new diagnostic that reports the offending access level. `internal`, `fileprivate`, and `private` bindings continue to fold, as do Clang-imported constants.
625 lines
25 KiB
C++
625 lines
25 KiB
C++
//===--- LiteralExpressionFolding.cpp - -------------------------*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Simple AST-based evaluator of supported literal expressions
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "LiteralExpressionFolding.h"
|
|
#include "MiscDiagnostics.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/ASTWalker.h"
|
|
#include "swift/AST/DiagnosticsSema.h"
|
|
#include "swift/AST/TypeCheckRequests.h"
|
|
#include "swift/Basic/Unreachable.h"
|
|
|
|
using namespace swift;
|
|
using namespace LiteralExprFolding;
|
|
|
|
namespace {
|
|
|
|
class FoldingError : public llvm::ErrorInfo<FoldingError> {
|
|
public:
|
|
static char ID;
|
|
IllegalConstError code;
|
|
SourceLoc sourceLocation;
|
|
FoldingError(IllegalConstError code) : code(code), sourceLocation() {}
|
|
FoldingError(IllegalConstError code, SourceLoc loc)
|
|
: code(code), sourceLocation(loc) {}
|
|
void log(llvm::raw_ostream &OS) const override {
|
|
OS << "Const Folding Error: "
|
|
<< static_cast<std::underlying_type<IllegalConstError>::type>(code);
|
|
}
|
|
std::error_code convertToErrorCode() const override {
|
|
return llvm::inconvertibleErrorCode();
|
|
}
|
|
};
|
|
char FoldingError::ID = 0;
|
|
|
|
static unsigned getTargetPointerBitWidth(ASTContext &ctx) {
|
|
// Matching the compiler's determination of the
|
|
// `#if _pointerBitWidth` value.
|
|
if (ctx.LangOpts.Target.isArch16Bit())
|
|
return 16;
|
|
if (ctx.LangOpts.Target.isArch32Bit())
|
|
return 32;
|
|
if (ctx.LangOpts.Target.isArch64Bit())
|
|
return 64;
|
|
swift_unreachable("Unsupported platform kind.");
|
|
}
|
|
|
|
/// Get the bitwidth for a Swift integer type for constant folding.
|
|
/// Returns 0 if the type is not a known integer type.
|
|
static unsigned getIntegerBitWidth(Type type, ASTContext &ctx) {
|
|
assert(type->isStdlibInteger());
|
|
// Map stdlib integer types to their bitwidths
|
|
if (type->isInt())
|
|
return getTargetPointerBitWidth(ctx);
|
|
if (type->isInt128())
|
|
return 128;
|
|
if (type->isInt64())
|
|
return 64;
|
|
if (type->isInt32())
|
|
return 32;
|
|
if (type->isInt16())
|
|
return 16;
|
|
if (type->isInt8())
|
|
return 8;
|
|
|
|
if (type->isUInt())
|
|
return getTargetPointerBitWidth(ctx);
|
|
if (type->isUInt128())
|
|
return 128;
|
|
if (type->isUInt64())
|
|
return 64;
|
|
if (type->isUInt32())
|
|
return 32;
|
|
if (type->isUInt16())
|
|
return 16;
|
|
if (type->isUInt8())
|
|
return 8;
|
|
|
|
swift_unreachable("Unsupported integer type.");
|
|
}
|
|
|
|
static ConcreteDeclRef getIntTypeBuiltinInit(Type type, ASTContext &ctx) {
|
|
assert(type->isStdlibInteger());
|
|
// Map stdlib integer types to their bitwidths
|
|
if (type->isInt())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getIntDecl());
|
|
if (type->isInt128())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getInt128Decl());
|
|
if (type->isInt64())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getInt64Decl());
|
|
if (type->isInt32())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getInt32Decl());
|
|
if (type->isInt16())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getInt16Decl());
|
|
if (type->isInt8())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getInt8Decl());
|
|
|
|
if (type->isUInt())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUIntDecl());
|
|
if (type->isUInt128())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUInt128Decl());
|
|
if (type->isUInt64())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUInt64Decl());
|
|
if (type->isUInt32())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUInt32Decl());
|
|
if (type->isUInt16())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUInt16Decl());
|
|
if (type->isUInt8())
|
|
return ctx.getIntBuiltinInitDecl(ctx.getUInt8Decl());
|
|
|
|
swift_unreachable("Unsupported integer type.");
|
|
}
|
|
|
|
/// Check if the type is a signed integer type
|
|
static bool isSignedIntegerType(Type type) {
|
|
return type->isInt() || type->isInt128() || type->isInt64() || type->isInt32() ||
|
|
type->isInt16() || type->isInt8();
|
|
}
|
|
|
|
/// A move-only value that's either a `FoldingError` or some parameterized type
|
|
/// value `T`. A convenience wrapper around `TaggedUnion<T, FoldingError>`.
|
|
template <typename T>
|
|
class FoldingErrorOr {
|
|
TaggedUnion<T, FoldingError> Value;
|
|
|
|
public:
|
|
FoldingErrorOr() : Value(FoldingError(IllegalConstError::Default)) {}
|
|
FoldingErrorOr(T &&t) : Value(std::move(t)) {}
|
|
FoldingErrorOr(const FoldingError &fe) : Value(fe) {}
|
|
|
|
FoldingErrorOr(FoldingErrorOr &&other) : Value(std::move(other.Value)) {}
|
|
FoldingErrorOr &operator=(FoldingErrorOr &&other) noexcept {
|
|
if (this != &other)
|
|
Value = std::move(other.Value);
|
|
return *this;
|
|
}
|
|
|
|
FoldingErrorOr(const FoldingErrorOr &) = delete;
|
|
FoldingErrorOr &operator=(const FoldingErrorOr &) = delete;
|
|
|
|
const T *operator->() const { return Value.template dyn_cast<T>(); }
|
|
FoldingError getError() const {
|
|
return *Value.template dyn_cast<FoldingError>();
|
|
}
|
|
bool isError() const {
|
|
return Value.template dyn_cast<FoldingError>() != nullptr;
|
|
}
|
|
|
|
/// Return false if there is an error.
|
|
explicit operator bool() const { return !isError(); }
|
|
};
|
|
|
|
class ConstantValue {
|
|
public:
|
|
enum class ConstantValueKind : int8_t {
|
|
FirstKind,
|
|
Integer = FirstKind,
|
|
FloatingPoint,
|
|
LastKind = FloatingPoint + 1
|
|
};
|
|
|
|
const ConstantValueKind kind;
|
|
ConstantValue(ConstantValueKind kind) : kind(kind) {}
|
|
virtual ~ConstantValue() = default;
|
|
ConstantValueKind getKind() const { return kind; }
|
|
};
|
|
using ConstantValuePtr = std::unique_ptr<ConstantValue>;
|
|
|
|
class IntegerValue : public ConstantValue {
|
|
APInt value;
|
|
bool isSigned;
|
|
|
|
public:
|
|
IntegerValue(APInt value, bool isSigned)
|
|
: ConstantValue(ConstantValueKind::Integer), value(value),
|
|
isSigned(isSigned) {}
|
|
|
|
APInt getValue() const { return value; }
|
|
bool getIsSigned() const { return isSigned; }
|
|
|
|
static bool classof(const ConstantValue *base) {
|
|
return base->getKind() == ConstantValueKind::Integer;
|
|
}
|
|
};
|
|
|
|
/// A simple constant expression folder to simplify
|
|
/// binary expressions of integer type consisting of literal
|
|
/// value operands.
|
|
class ConstantFolder {
|
|
ASTContext &Ctx;
|
|
|
|
public:
|
|
ConstantFolder(ASTContext &ctx) : Ctx(ctx) {}
|
|
Expr *fold(const Expr *expr) {
|
|
// If this expression failed to type-check, no need to attempt to
|
|
// fold it since we likely won't be able to do anything meaningful
|
|
// here.
|
|
if (!expr->getType() || expr->getType()->getAs<ErrorType>()) {
|
|
emitFoldingErrorDiagnostic(
|
|
FoldingError(IllegalConstError::UpstreamError, expr->getStartLoc()));
|
|
return nullptr;
|
|
}
|
|
|
|
ConstantWalker walker(Ctx);
|
|
const_cast<Expr *>(expr)->walk(walker);
|
|
ASSERT(walker.hasConstantValueFor(expr) &&
|
|
"No value or error computed by constant-folding AST walker");
|
|
const auto &result = walker.getConstantValueOrErrorFor(expr);
|
|
if (result)
|
|
return createIntegerLiteralExpr(expr, result->get());
|
|
else {
|
|
emitFoldingErrorDiagnostic(result.getError());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
private:
|
|
class ConstantWalker : public ASTWalker {
|
|
ASTContext &Ctx;
|
|
llvm::DenseMap<Expr *, FoldingErrorOr<ConstantValuePtr>>
|
|
ConstValuesOrErrors;
|
|
public:
|
|
ConstantWalker(ASTContext &ctx) : Ctx(ctx) {}
|
|
|
|
PostWalkResult<Expr *> walkToExprPost(Expr *expr) override {
|
|
ConstValuesOrErrors.insert({expr, tryFoldExpression(expr)});
|
|
return Action::Continue(expr);
|
|
}
|
|
|
|
bool hasConstantValueFor(const Expr *expr) {
|
|
return ConstValuesOrErrors.contains(expr);
|
|
}
|
|
|
|
const FoldingErrorOr<ConstantValuePtr> &
|
|
getConstantValueOrErrorFor(const Expr *expr) {
|
|
ASSERT(ConstValuesOrErrors.contains(expr) &&
|
|
"Querying constant value for an unfolded expression.");
|
|
return ConstValuesOrErrors.at(expr);
|
|
}
|
|
|
|
private:
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldExpression(const Expr *expr) {
|
|
if (auto *literalExpr = dyn_cast<LiteralExpr>(expr))
|
|
return tryFoldLiteralExpression(literalExpr);
|
|
if (auto *binaryExpr = dyn_cast<BinaryExpr>(expr))
|
|
return tryFoldBinaryExpr(binaryExpr);
|
|
if (auto *unaryExpr = dyn_cast<PrefixUnaryExpr>(expr))
|
|
return tryFoldUnaryExpr(unaryExpr);
|
|
if (auto *parenExpr = dyn_cast<ParenExpr>(expr))
|
|
return tryFoldParenExpr(parenExpr);
|
|
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
|
|
return foldDeclRefExpr(declRefExpr);
|
|
|
|
return FoldingError(IllegalConstError::Default, expr->getLoc());
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr>
|
|
tryFoldLiteralExpression(const LiteralExpr *expr) {
|
|
if (auto *intLiteralExpr = dyn_cast<IntegerLiteralExpr>(expr))
|
|
return foldIntegerLiteralExpr(intLiteralExpr);
|
|
|
|
return FoldingError(IllegalConstError::Default, expr->getLoc());
|
|
}
|
|
|
|
ConstantValuePtr foldIntegerLiteralExpr(const IntegerLiteralExpr *expr) {
|
|
auto exprType = expr->getType();
|
|
auto value = expr->getValue();
|
|
auto resultBitWidth = getIntegerBitWidth(exprType, Ctx);
|
|
if (isSignedIntegerType(exprType))
|
|
return std::make_unique<IntegerValue>(value.sextOrTrunc(resultBitWidth),
|
|
true);
|
|
return std::make_unique<IntegerValue>(value.zextOrTrunc(resultBitWidth),
|
|
false);
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldBinaryExpr(const BinaryExpr *expr) {
|
|
if (!expr->getType()->isStdlibInteger())
|
|
return FoldingError(IllegalConstError::TypeNotSupported,
|
|
expr->getStartLoc());
|
|
if (!supportedOperator(expr))
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
expr->getStartLoc());
|
|
|
|
if (const auto &lhsOrErr = getConstantValueOrErrorFor(expr->getLHS())) {
|
|
if (const auto &rhsOrErr = getConstantValueOrErrorFor(expr->getRHS())) {
|
|
auto lhsIntPtr = cast<IntegerValue>(lhsOrErr->get());
|
|
auto rhsIntPtr = cast<IntegerValue>(rhsOrErr->get());
|
|
auto operatorDecl = expr->getCalledValue();
|
|
auto operatorIdentifier = operatorDecl->getBaseName().getIdentifier();
|
|
if (operatorIdentifier.isArithmeticOperator())
|
|
return tryFoldIntegerBinaryArithmeticOperator(
|
|
operatorIdentifier, expr->getLoc(), lhsIntPtr, rhsIntPtr);
|
|
if (operatorIdentifier.isOverflowArithmeticOperator())
|
|
return tryFoldIntegerBinaryOverflowArithmeticOperator(
|
|
operatorIdentifier, expr->getLoc(), lhsIntPtr, rhsIntPtr);
|
|
if (operatorIdentifier.isBitwiseOperator() ||
|
|
operatorIdentifier.isShiftOperator())
|
|
return tryFoldIntegerBinaryBitwiseOperator(
|
|
operatorIdentifier, expr->getLoc(), lhsIntPtr, rhsIntPtr);
|
|
if (operatorIdentifier.isMaskingShiftOperator())
|
|
return tryFoldIntegerBinaryMaskingShiftOperator(
|
|
operatorIdentifier, expr->getLoc(), lhsIntPtr, rhsIntPtr);
|
|
llvm_unreachable("Unsupported operator");
|
|
} else
|
|
return rhsOrErr.getError();
|
|
} else
|
|
return lhsOrErr.getError();
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr>
|
|
tryFoldUnaryExpr(const PrefixUnaryExpr *expr) {
|
|
if (!expr->getType()->isStdlibInteger())
|
|
return FoldingError(IllegalConstError::TypeNotSupported,
|
|
expr->getLoc());
|
|
if (!supportedOperator(expr))
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
expr->getLoc());
|
|
|
|
const auto &operandOrErr = getConstantValueOrErrorFor(expr->getOperand());
|
|
if (operandOrErr) {
|
|
auto operatorIdentifier =
|
|
expr->getCalledValue()->getBaseName().getIdentifier();
|
|
return tryFoldIntegerUnaryArithmeticOperator(
|
|
operatorIdentifier, expr->getLoc(),
|
|
cast<IntegerValue>(operandOrErr->get()));
|
|
}
|
|
return operandOrErr.getError();
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldParenExpr(const ParenExpr *expr) {
|
|
const auto &operandOrErr = getConstantValueOrErrorFor(expr->getSubExpr());
|
|
if (operandOrErr) {
|
|
auto *intValue = cast<IntegerValue>(operandOrErr->get());
|
|
return ConstantValuePtr(std::make_unique<IntegerValue>(
|
|
intValue->getValue(), intValue->getIsSigned()));
|
|
}
|
|
return operandOrErr.getError();
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> foldDeclRefExpr(const DeclRefExpr *expr) {
|
|
if (const VarDecl *varDecl = dyn_cast<VarDecl>(expr->getDecl())) {
|
|
// Swift source `let` bindings whose access level is broader than
|
|
// internal participate in the ABI surface of their module and may
|
|
// not appear in a literal expression. Emit the diagnostic inline so
|
|
// we can carry the access level, and return `UpstreamError` so no
|
|
// further generic "not a literal expression" message is added.
|
|
// A `var` that reaches here falls through to the existing
|
|
// opaque-decl-ref path, which is the correct diagnostic for it.
|
|
if (!varDecl->hasClangNode() && varDecl->isLet()) {
|
|
auto access = varDecl->getFormalAccess();
|
|
if (access >= AccessLevel::Package) {
|
|
Ctx.Diags.diagnose(expr->getLoc(), diag::const_public_let_ref,
|
|
access);
|
|
return FoldingError(IllegalConstError::UpstreamError,
|
|
expr->getLoc());
|
|
}
|
|
}
|
|
|
|
// For other `@const` or `@section` values, we expect
|
|
// their initializer to be foldable. For other values which
|
|
// have a default value, we attempt to fold the
|
|
// corresponding initializer expression.
|
|
if (varDecl->isConstValue() || varDecl->hasInitialValue())
|
|
if (auto initExpr = varDecl->getParentInitializer())
|
|
return tryFoldDeclRefInitializerExpr(initExpr, expr->getLoc());
|
|
|
|
// Clang constants are imported as a ValueDecl
|
|
// with simple getter returning a literal value.
|
|
if (varDecl->hasClangNode() && !varDecl->isDynamic() &&
|
|
!varDecl->isObjC() &&
|
|
varDecl->getImplInfo().getReadImpl() == ReadImplKind::Get)
|
|
if (auto accessor = varDecl->getAccessor(AccessorKind::Get))
|
|
if (auto singleRetStmt = dyn_cast<ReturnStmt>(
|
|
accessor->getBody()->getSingleActiveStatement()))
|
|
return tryFoldDeclRefInitializerExpr(singleRetStmt->getResult(),
|
|
expr->getLoc());
|
|
}
|
|
|
|
return FoldingError(IllegalConstError::OpaqueDeclRef, expr->getLoc());
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr>
|
|
tryFoldDeclRefInitializerExpr(const Expr *expr, SourceLoc referenceLoc) {
|
|
bool previouslyFolded =
|
|
Ctx.evaluator.hasCachedResult(ConstantFoldExpression{expr, &Ctx});
|
|
// Request the init expression of this declaration to be
|
|
// constant-folded.
|
|
if (auto foldedLiteralExpr =
|
|
dyn_cast<LiteralExpr>(swift::foldLiteralExpression(expr, &Ctx)))
|
|
return tryFoldLiteralExpression(foldedLiteralExpr);
|
|
// If this is the first time we have requested to constant-fold this
|
|
// declaration's initializer and have failed to do so, emit a note
|
|
// with a location of the declRef from which we initiated this query.
|
|
if (!previouslyFolded)
|
|
return FoldingError(IllegalConstError::NonConstDeclRef, referenceLoc);
|
|
return FoldingError(IllegalConstError::OpaqueDeclRef, referenceLoc);
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldIntegerBinaryArithmeticOperator(
|
|
Identifier operatorIdentifier, SourceLoc sourceLocation,
|
|
const IntegerValue *lhsVal, const IntegerValue *rhsVal) {
|
|
assert(lhsVal->getIsSigned() == rhsVal->getIsSigned());
|
|
bool isSigned = lhsVal->getIsSigned();
|
|
auto lhsInt = lhsVal->getValue();
|
|
auto rhsInt = rhsVal->getValue();
|
|
APInt result;
|
|
bool overflow = false;
|
|
if (operatorIdentifier.is("+"))
|
|
result = isSigned ? lhsInt.sadd_ov(rhsInt, overflow)
|
|
: lhsInt.uadd_ov(rhsInt, overflow);
|
|
else if (operatorIdentifier.is("-"))
|
|
result = isSigned ? lhsInt.ssub_ov(rhsInt, overflow)
|
|
: lhsInt.usub_ov(rhsInt, overflow);
|
|
else if (operatorIdentifier.is("*"))
|
|
result = isSigned ? lhsInt.smul_ov(rhsInt, overflow)
|
|
: lhsInt.umul_ov(rhsInt, overflow);
|
|
else if (operatorIdentifier.is("/")) {
|
|
if (rhsInt == 0)
|
|
return FoldingError(IllegalConstError::DivideByZero, sourceLocation);
|
|
result =
|
|
isSigned ? lhsInt.sdiv_ov(rhsInt, overflow) : lhsInt.udiv(rhsInt);
|
|
} else if (operatorIdentifier.is("%")) {
|
|
if (rhsInt == 0)
|
|
return FoldingError(IllegalConstError::DivideByZero, sourceLocation);
|
|
if (isSigned) // Check for overflow
|
|
auto divResult = lhsInt.sdiv_ov(rhsInt, overflow);
|
|
result = isSigned ? lhsInt.srem(rhsInt) : lhsInt.urem(rhsInt);
|
|
} else
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
sourceLocation);
|
|
|
|
if (overflow)
|
|
return FoldingError(IllegalConstError::IntegerOverflow, sourceLocation);
|
|
|
|
return ConstantValuePtr(std::make_unique<IntegerValue>(result, isSigned));
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr>
|
|
tryFoldIntegerBinaryOverflowArithmeticOperator(
|
|
Identifier operatorIdentifier, SourceLoc sourceLocation,
|
|
const IntegerValue *lhsVal, const IntegerValue *rhsVal) {
|
|
assert(lhsVal->getIsSigned() == rhsVal->getIsSigned());
|
|
bool isSigned = lhsVal->getIsSigned();
|
|
auto lhsInt = lhsVal->getValue();
|
|
auto rhsInt = rhsVal->getValue();
|
|
// APInt's plain +/-/* wrap at the declared bit width, which matches
|
|
// Swift's overflow arithmetic semantics for &+, &-, &*.
|
|
APInt result;
|
|
if (operatorIdentifier.is("&+"))
|
|
result = lhsInt + rhsInt;
|
|
else if (operatorIdentifier.is("&-"))
|
|
result = lhsInt - rhsInt;
|
|
else if (operatorIdentifier.is("&*"))
|
|
result = lhsInt * rhsInt;
|
|
else
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
sourceLocation);
|
|
|
|
return ConstantValuePtr(std::make_unique<IntegerValue>(result, isSigned));
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldIntegerBinaryBitwiseOperator(
|
|
Identifier operatorIdentifier, SourceLoc sourceLocation,
|
|
const IntegerValue *lhsVal, const IntegerValue *rhsVal) {
|
|
bool isSigned = lhsVal->getIsSigned();
|
|
auto lhsInt = lhsVal->getValue();
|
|
auto rhsInt = rhsVal->getValue();
|
|
// The stdlib shift operators take `where RHS : BinaryInteger`, so the
|
|
// RHS does not share signedness or bit width with the LHS. The true
|
|
// bitwise operators `& | ^` do operate on two `Self` values.
|
|
bool isShift =
|
|
operatorIdentifier.is("<<") || operatorIdentifier.is(">>");
|
|
assert(isShift ||
|
|
lhsVal->getIsSigned() == rhsVal->getIsSigned());
|
|
|
|
APInt result;
|
|
if (operatorIdentifier.is("&"))
|
|
result = lhsInt & rhsInt;
|
|
else if (operatorIdentifier.is("|"))
|
|
result = lhsInt | rhsInt;
|
|
else if (operatorIdentifier.is("^"))
|
|
result = lhsInt ^ rhsInt;
|
|
else if (isShift) {
|
|
// Non-masking shifts trap at runtime when the amount is negative or
|
|
// greater-or-equal to the LHS bit width. Reject both as folding
|
|
// errors so the result matches Swift's runtime semantics. The
|
|
// dedicated diagnostics are emitted inline so they can carry the
|
|
// amount and bit width; return `UpstreamError` to suppress the
|
|
// generic "not a literal expression" follow-up.
|
|
if (rhsVal->getIsSigned() && rhsInt.isNegative()) {
|
|
Ctx.Diags.diagnose(sourceLocation, diag::const_shift_negative);
|
|
return FoldingError(IllegalConstError::UpstreamError, sourceLocation);
|
|
}
|
|
unsigned width = lhsInt.getBitWidth();
|
|
uint64_t amountValue = rhsInt.getLimitedValue();
|
|
if (amountValue >= width) {
|
|
Ctx.Diags.diagnose(sourceLocation, diag::const_shift_out_of_range,
|
|
static_cast<unsigned>(amountValue), width);
|
|
return FoldingError(IllegalConstError::UpstreamError, sourceLocation);
|
|
}
|
|
unsigned amount = static_cast<unsigned>(amountValue);
|
|
if (operatorIdentifier.is("<<"))
|
|
result = lhsInt.shl(amount);
|
|
else
|
|
result = isSigned ? lhsInt.ashr(amount) : lhsInt.lshr(amount);
|
|
} else
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
sourceLocation);
|
|
|
|
return ConstantValuePtr(std::make_unique<IntegerValue>(result, isSigned));
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr> tryFoldIntegerBinaryMaskingShiftOperator(
|
|
Identifier operatorIdentifier, SourceLoc sourceLocation,
|
|
const IntegerValue *lhsVal, const IntegerValue *rhsVal) {
|
|
// The stdlib masking-shift operators take `where RHS : BinaryInteger`,
|
|
// so the RHS does not share signedness or bit width with the LHS.
|
|
bool isSigned = lhsVal->getIsSigned();
|
|
auto lhsInt = lhsVal->getValue();
|
|
auto rhsInt = rhsVal->getValue();
|
|
// Per FixedWidthInteger.&<< / &>>, the shift amount is reduced modulo
|
|
// the result type's bit width. All Swift integer widths are powers of
|
|
// two, so urem(width) is equivalent to & (width - 1).
|
|
unsigned width = lhsInt.getBitWidth();
|
|
APInt amount = rhsInt.urem(APInt(rhsInt.getBitWidth(), width));
|
|
APInt result;
|
|
if (operatorIdentifier.is("&<<"))
|
|
result = lhsInt.shl(amount);
|
|
else if (operatorIdentifier.is("&>>"))
|
|
result = isSigned ? lhsInt.ashr(amount) : lhsInt.lshr(amount);
|
|
else
|
|
return FoldingError(IllegalConstError::UnsupportedBinaryOperator,
|
|
sourceLocation);
|
|
|
|
return ConstantValuePtr(std::make_unique<IntegerValue>(result, isSigned));
|
|
}
|
|
|
|
FoldingErrorOr<ConstantValuePtr>
|
|
tryFoldIntegerUnaryArithmeticOperator(Identifier operatorIdentifier,
|
|
SourceLoc sourceLocation,
|
|
const IntegerValue *operandVal) {
|
|
APInt result;
|
|
auto operand = operandVal->getValue();
|
|
bool overflow = false;
|
|
if (operatorIdentifier.is("-")) {
|
|
APInt zero = APInt(operand.getBitWidth(), 0);
|
|
result = operandVal->getIsSigned() ? zero.ssub_ov(operand, overflow)
|
|
: zero.usub_ov(operand, overflow);
|
|
} else if (operatorIdentifier.is("+"))
|
|
result = operand;
|
|
else if (operatorIdentifier.is("~"))
|
|
result = ~operand;
|
|
else
|
|
return FoldingError(IllegalConstError::UnsupportedUnaryOperator,
|
|
sourceLocation);
|
|
|
|
if (overflow)
|
|
return FoldingError(IllegalConstError::IntegerOverflow, sourceLocation);
|
|
|
|
return ConstantValuePtr(
|
|
std::make_unique<IntegerValue>(result, operandVal->getIsSigned()));
|
|
}
|
|
};
|
|
|
|
Expr *createIntegerLiteralExpr(const Expr *foldedExpr,
|
|
const ConstantValue *result) {
|
|
assert(isa<IntegerValue>(result));
|
|
auto intResult = cast<IntegerValue>(result)->getValue();
|
|
auto resultType = foldedExpr->getType();
|
|
assert(resultType->isStdlibInteger());
|
|
bool isSigned = isSignedIntegerType(resultType);
|
|
|
|
SmallString<32> resultStr;
|
|
// Get the absolute value for a signed integer
|
|
// because it is represented as a 'negative value' on the resulting
|
|
// `IntegerLiteralExpr`.
|
|
if (isSigned)
|
|
intResult.abs().toString(resultStr, 10, true);
|
|
else
|
|
intResult.toString(resultStr, 10, false);
|
|
|
|
auto *newLit = new (Ctx) IntegerLiteralExpr(
|
|
Ctx.getIdentifier(resultStr).str(), foldedExpr->getLoc(),
|
|
/*implicit*/ true);
|
|
newLit->setType(resultType);
|
|
newLit->setImplicit();
|
|
newLit->setBuiltinInitializer(getIntTypeBuiltinInit(resultType, Ctx));
|
|
if (isSigned && intResult.slt(0))
|
|
newLit->setNegative(foldedExpr->getLoc());
|
|
return newLit;
|
|
}
|
|
|
|
void emitFoldingErrorDiagnostic(const FoldingError &foldingError) {
|
|
diagnoseError(foldingError.sourceLocation, foldingError.code, Ctx.Diags);
|
|
}
|
|
};
|
|
} // anonymous namespace
|
|
|
|
Expr *swift::foldLiteralExpression(const Expr *expr, ASTContext *ctx) {
|
|
return evaluateOrDefault(ctx->evaluator, ConstantFoldExpression{expr, ctx},
|
|
{});
|
|
}
|
|
|
|
Expr *ConstantFoldExpression::evaluate(Evaluator &evaluator, const Expr *expr,
|
|
ASTContext *ctx) const {
|
|
if (ctx->LangOpts.hasFeature(Feature::LiteralExpressions)) {
|
|
ConstantFolder folder(*ctx);
|
|
if (auto result = folder.fold(expr))
|
|
return result;
|
|
}
|
|
return const_cast<Expr *>(expr);
|
|
}
|