1 /*
2 * Copyright 2021 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/sksl/ir/SkSLTernaryExpression.h"
9
10 #include "src/sksl/SkSLAnalysis.h"
11 #include "src/sksl/SkSLBuiltinTypes.h"
12 #include "src/sksl/SkSLConstantFolder.h"
13 #include "src/sksl/SkSLContext.h"
14 #include "src/sksl/SkSLErrorReporter.h"
15 #include "src/sksl/SkSLOperator.h"
16 #include "src/sksl/SkSLProgramSettings.h"
17 #include "src/sksl/ir/SkSLBinaryExpression.h"
18 #include "src/sksl/ir/SkSLConstructorScalarCast.h"
19 #include "src/sksl/ir/SkSLLiteral.h"
20 #include "src/sksl/ir/SkSLPrefixExpression.h"
21
22 namespace SkSL {
23
Convert(const Context & context,Position pos,std::unique_ptr<Expression> test,std::unique_ptr<Expression> ifTrue,std::unique_ptr<Expression> ifFalse)24 std::unique_ptr<Expression> TernaryExpression::Convert(const Context& context,
25 Position pos,
26 std::unique_ptr<Expression> test,
27 std::unique_ptr<Expression> ifTrue,
28 std::unique_ptr<Expression> ifFalse) {
29 test = context.fTypes.fBool->coerceExpression(std::move(test), context);
30 if (!test || !ifTrue || !ifFalse) {
31 return nullptr;
32 }
33 if (ifTrue->type().componentType().isOpaque()) {
34 context.fErrors->error(pos, "ternary expression of opaque type '" +
35 ifTrue->type().displayName() + "' is not allowed");
36 return nullptr;
37 }
38 const Type* trueType;
39 const Type* falseType;
40 const Type* resultType;
41 Operator equalityOp(Operator::Kind::EQEQ);
42 if (!equalityOp.determineBinaryType(context, ifTrue->type(), ifFalse->type(),
43 &trueType, &falseType, &resultType) ||
44 !trueType->matches(*falseType)) {
45 Position errorPos = ifTrue->fPosition.rangeThrough(ifFalse->fPosition);
46 if (ifTrue->type().isVoid()) {
47 context.fErrors->error(errorPos, "ternary expression of type 'void' is not allowed");
48 } else {
49 context.fErrors->error(errorPos, "ternary operator result mismatch: '" +
50 ifTrue->type().displayName() + "', '" +
51 ifFalse->type().displayName() + "'");
52 }
53 return nullptr;
54 }
55 if (trueType->isOrContainsArray()) {
56 context.fErrors->error(pos, "ternary operator result may not be an array (or struct "
57 "containing an array)");
58 return nullptr;
59 }
60 ifTrue = trueType->coerceExpression(std::move(ifTrue), context);
61 if (!ifTrue) {
62 return nullptr;
63 }
64 ifFalse = falseType->coerceExpression(std::move(ifFalse), context);
65 if (!ifFalse) {
66 return nullptr;
67 }
68 return TernaryExpression::Make(context, pos, std::move(test), std::move(ifTrue),
69 std::move(ifFalse));
70 }
71
Make(const Context & context,Position pos,std::unique_ptr<Expression> test,std::unique_ptr<Expression> ifTrue,std::unique_ptr<Expression> ifFalse)72 std::unique_ptr<Expression> TernaryExpression::Make(const Context& context,
73 Position pos,
74 std::unique_ptr<Expression> test,
75 std::unique_ptr<Expression> ifTrue,
76 std::unique_ptr<Expression> ifFalse) {
77 SkASSERT(ifTrue->type().matches(ifFalse->type()));
78 SkASSERT(!ifTrue->type().componentType().isOpaque());
79 SkASSERT(!context.fConfig->strictES2Mode() || !ifTrue->type().isOrContainsArray());
80
81 const Expression* testExpr = ConstantFolder::GetConstantValueForVariable(*test);
82 if (testExpr->isBoolLiteral()) {
83 // static boolean test, just return one of the branches
84 if (testExpr->as<Literal>().boolValue()) {
85 ifTrue->fPosition = pos;
86 return ifTrue;
87 } else {
88 ifFalse->fPosition = pos;
89 return ifFalse;
90 }
91 }
92
93 if (context.fConfig->fSettings.fOptimize) {
94 const Expression* ifTrueExpr = ConstantFolder::GetConstantValueForVariable(*ifTrue);
95 const Expression* ifFalseExpr = ConstantFolder::GetConstantValueForVariable(*ifFalse);
96
97 // A ternary with matching true- and false-cases does not need to branch.
98 if (Analysis::IsSameExpressionTree(*ifTrueExpr, *ifFalseExpr)) {
99 // If `test` has no side-effects, we can eliminate it too, and just return `ifTrue`.
100 if (!Analysis::HasSideEffects(*test)) {
101 ifTrue->fPosition = pos;
102 return ifTrue;
103 }
104 // Return a comma-expression containing `(test, ifTrue)`.
105 return BinaryExpression::Make(context, pos, std::move(test),
106 Operator::Kind::COMMA, std::move(ifTrue));
107 }
108
109 // A ternary of the form `test ? expr : false` can be simplified to `test && expr`.
110 if (ifFalseExpr->isBoolLiteral() && !ifFalseExpr->as<Literal>().boolValue()) {
111 return BinaryExpression::Make(context, pos, std::move(test),
112 Operator::Kind::LOGICALAND, std::move(ifTrue));
113 }
114
115 // A ternary of the form `test ? true : expr` can be simplified to `test || expr`.
116 if (ifTrueExpr->isBoolLiteral() && ifTrueExpr->as<Literal>().boolValue()) {
117 return BinaryExpression::Make(context, pos, std::move(test),
118 Operator::Kind::LOGICALOR, std::move(ifFalse));
119 }
120
121 // A ternary of the form `test ? false : true` can be simplified to `!test`.
122 if (ifTrueExpr->isBoolLiteral() && !ifTrueExpr->as<Literal>().boolValue() &&
123 ifFalseExpr->isBoolLiteral() && ifFalseExpr->as<Literal>().boolValue()) {
124 return PrefixExpression::Make(context, pos, Operator::Kind::LOGICALNOT,
125 std::move(test));
126 }
127
128 // A ternary of the form `test ? 1 : 0` can be simplified to `cast(test)`.
129 if (ifTrueExpr->is<Literal>() && ifTrueExpr->as<Literal>().value() == 1.0 &&
130 ifFalseExpr->is<Literal>() && ifFalseExpr->as<Literal>().value() == 0.0) {
131 return ConstructorScalarCast::Make(context, pos, ifTrue->type(), std::move(test));
132 }
133 }
134
135 return std::make_unique<TernaryExpression>(pos, std::move(test), std::move(ifTrue),
136 std::move(ifFalse));
137 }
138
description(OperatorPrecedence parentPrecedence) const139 std::string TernaryExpression::description(OperatorPrecedence parentPrecedence) const {
140 bool needsParens = (OperatorPrecedence::kTernary >= parentPrecedence);
141 return std::string(needsParens ? "(" : "") +
142 this->test()->description(OperatorPrecedence::kTernary) + " ? " +
143 this->ifTrue()->description(OperatorPrecedence::kTernary) + " : " +
144 this->ifFalse()->description(OperatorPrecedence::kTernary) +
145 std::string(needsParens ? ")" : "");
146 }
147
148 } // namespace SkSL
149