blob: 2f38ab8f3e3842bdbb16b706a66308d8be187649 [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/sksl/ir/SkSLTernaryExpression.h"
#include "include/sksl/SkSLErrorReporter.h"
#include "include/sksl/SkSLOperator.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLConstantFolder.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLLiteral.h"
namespace SkSL {
std::unique_ptr<Expression> TernaryExpression::Convert(const Context& context,
Position pos,
std::unique_ptr<Expression> test,
std::unique_ptr<Expression> ifTrue,
std::unique_ptr<Expression> ifFalse) {
test = context.fTypes.fBool->coerceExpression(std::move(test), context);
if (!test || !ifTrue || !ifFalse) {
return nullptr;
}
if (ifTrue->type().componentType().isOpaque()) {
context.fErrors->error(pos, "ternary expression of opaque type '" +
ifTrue->type().displayName() + "' not allowed");
return nullptr;
}
const Type* trueType;
const Type* falseType;
const Type* resultType;
Operator equalityOp(Operator::Kind::EQEQ);
if (!equalityOp.determineBinaryType(context, ifTrue->type(), ifFalse->type(),
&trueType, &falseType, &resultType) ||
!trueType->matches(*falseType)) {
context.fErrors->error(ifTrue->fPosition.rangeThrough(ifFalse->fPosition),
"ternary operator result mismatch: '" + ifTrue->type().displayName() + "', '" +
ifFalse->type().displayName() + "'");
return nullptr;
}
if (context.fConfig->strictES2Mode() && trueType->isOrContainsArray()) {
context.fErrors->error(pos, "ternary operator result may not be an array (or struct "
"containing an array)");
return nullptr;
}
ifTrue = trueType->coerceExpression(std::move(ifTrue), context);
if (!ifTrue) {
return nullptr;
}
ifFalse = falseType->coerceExpression(std::move(ifFalse), context);
if (!ifFalse) {
return nullptr;
}
return TernaryExpression::Make(context, pos, std::move(test), std::move(ifTrue),
std::move(ifFalse));
}
std::unique_ptr<Expression> TernaryExpression::Make(const Context& context,
Position pos,
std::unique_ptr<Expression> test,
std::unique_ptr<Expression> ifTrue,
std::unique_ptr<Expression> ifFalse) {
SkASSERT(ifTrue->type().matches(ifFalse->type()));
SkASSERT(!ifTrue->type().componentType().isOpaque());
SkASSERT(!context.fConfig->strictES2Mode() || !ifTrue->type().isOrContainsArray());
const Expression* testExpr = ConstantFolder::GetConstantValueForVariable(*test);
if (testExpr->isBoolLiteral()) {
// static boolean test, just return one of the branches
if (testExpr->as<Literal>().boolValue()) {
ifTrue->fPosition = pos;
return ifTrue;
} else {
ifFalse->fPosition = pos;
return ifFalse;
}
}
// A ternary with matching true- and false-cases does not need to branch.
if (context.fConfig->fSettings.fOptimize) {
const Expression* ifTrueExpr = ConstantFolder::GetConstantValueForVariable(*ifTrue);
const Expression* ifFalseExpr = ConstantFolder::GetConstantValueForVariable(*ifFalse);
if (Analysis::IsSameExpressionTree(*ifTrueExpr, *ifFalseExpr)) {
// If `test` has no side-effects, we can eliminate it too, and just return `ifTrue`.
if (!Analysis::HasSideEffects(*test)) {
ifTrue->fPosition = pos;
return ifTrue;
}
// Return a comma-expression containing `(test, ifTrue)`.
return BinaryExpression::Make(context, pos, std::move(test),
Operator::Kind::COMMA, std::move(ifTrue));
}
}
return std::make_unique<TernaryExpression>(pos, std::move(test), std::move(ifTrue),
std::move(ifFalse));
}
std::string TernaryExpression::description(OperatorPrecedence parentPrecedence) const {
bool needsParens = (OperatorPrecedence::kTernary >= parentPrecedence);
return std::string(needsParens ? "(" : "") +
this->test()->description(OperatorPrecedence::kTernary) + " ? " +
this->ifTrue()->description(OperatorPrecedence::kTernary) + " : " +
this->ifFalse()->description(OperatorPrecedence::kTernary) +
std::string(needsParens ? ")" : "");
}
} // namespace SkSL