Relax typing for comparison operators (#12537)

* Relax typing for comparison operators

Ports https://github.com/mapbox/mapbox-gl-js/pull/6961

* Review comments

* Lint fixes
This commit is contained in:
Anand Thakker
2018-08-02 13:48:47 -04:00
committed by GitHub
parent 952c4131e6
commit d25e1a1e7f
11 changed files with 367 additions and 176 deletions

View File

@@ -455,9 +455,9 @@ set(MBGL_CORE_FILES
include/mbgl/style/expression/coercion.hpp
include/mbgl/style/expression/collator.hpp
include/mbgl/style/expression/collator_expression.hpp
include/mbgl/style/expression/comparison.hpp
include/mbgl/style/expression/compound_expression.hpp
include/mbgl/style/expression/dsl.hpp
include/mbgl/style/expression/equals.hpp
include/mbgl/style/expression/error.hpp
include/mbgl/style/expression/expression.hpp
include/mbgl/style/expression/find_zoom_curve.hpp
@@ -483,9 +483,9 @@ set(MBGL_CORE_FILES
src/mbgl/style/expression/coalesce.cpp
src/mbgl/style/expression/coercion.cpp
src/mbgl/style/expression/collator_expression.cpp
src/mbgl/style/expression/comparison.cpp
src/mbgl/style/expression/compound_expression.cpp
src/mbgl/style/expression/dsl.cpp
src/mbgl/style/expression/equals.cpp
src/mbgl/style/expression/expression.cpp
src/mbgl/style/expression/find_zoom_curve.cpp
src/mbgl/style/expression/get_covering_stops.cpp

View File

@@ -0,0 +1,68 @@
#pragma once
#include <mbgl/style/expression/collator_expression.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
ParseResult parseComparison(const mbgl::style::conversion::Convertible&, ParsingContext&);
class BasicComparison : public Expression {
public:
using CompareFunctionType = bool (*) (Value, Value);
BasicComparison(
std::string op,
std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs);
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression&) const override;
EvaluationResult evaluate(const EvaluationContext&) const override;
std::vector<optional<Value>> possibleOutputs() const override;
std::string getOperator() const override;
private:
std::string op;
CompareFunctionType compare;
std::unique_ptr<Expression> lhs;
std::unique_ptr<Expression> rhs;
bool needsRuntimeTypeCheck;
};
class CollatorComparison : public Expression {
public:
using CompareFunctionType = bool (*) (std::string, std::string, Collator);
CollatorComparison(
std::string op,
std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs,
std::unique_ptr<Expression> collator);
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression&) const override;
EvaluationResult evaluate(const EvaluationContext&) const override;
std::vector<optional<Value>> possibleOutputs() const override;
std::string getOperator() const override;
private:
std::string op;
CompareFunctionType compare;
std::unique_ptr<Expression> lhs;
std::unique_ptr<Expression> rhs;
std::unique_ptr<Expression> collator;
bool needsRuntimeTypeCheck;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -31,6 +31,7 @@ std::unique_ptr<Expression> literal(Value value);
std::unique_ptr<Expression> literal(std::initializer_list<double> value);
std::unique_ptr<Expression> literal(std::initializer_list<const char *> value);
std::unique_ptr<Expression> assertion(type::Type, std::unique_ptr<Expression>);
std::unique_ptr<Expression> number(std::unique_ptr<Expression>);
std::unique_ptr<Expression> string(std::unique_ptr<Expression>);
std::unique_ptr<Expression> boolean(std::unique_ptr<Expression>);

View File

@@ -1,35 +0,0 @@
#pragma once
#include <mbgl/style/expression/collator_expression.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>
#include <mbgl/style/conversion.hpp>
#include <memory>
namespace mbgl {
namespace style {
namespace expression {
class Equals : public Expression {
public:
Equals(std::unique_ptr<Expression> lhs, std::unique_ptr<Expression> rhs, optional<std::unique_ptr<Expression>> collator, bool negate);
static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression&) const override;
EvaluationResult evaluate(const EvaluationContext&) const override;
std::vector<optional<Value>> possibleOutputs() const override;
std::string getOperator() const override { return negate ? "!=" : "=="; }
private:
std::unique_ptr<Expression> lhs;
std::unique_ptr<Expression> rhs;
optional<std::unique_ptr<Expression>> collator;
bool negate;
};
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -133,7 +133,7 @@ enum class Kind : int32_t {
Case,
Any,
All,
Equals,
Comparison,
};
class Expression {

View File

@@ -0,0 +1,275 @@
#include <mbgl/style/expression/collator.hpp>
#include <mbgl/style/expression/comparison.hpp>
#include <mbgl/style/expression/dsl.hpp>
namespace mbgl {
namespace style {
namespace expression {
static bool isComparableType(const std::string& op, const type::Type& type) {
if (op == "==" || op == "!=") {
return type == type::String ||
type == type::Number ||
type == type::Boolean ||
type == type::Null ||
type == type::Value;
} else {
return type == type::String ||
type == type::Number ||
type == type::Value;
}
}
bool eq(Value a, Value b) { return a == b; }
bool neq(Value a, Value b) { return a != b; }
bool lt(Value lhs, Value rhs) {
return lhs.match(
[&](const std::string& a) { return a < rhs.get<std::string>(); },
[&](double a) { return a < rhs.get<double>(); },
[&](const auto&) { assert(false); return false; }
);
}
bool gt(Value lhs, Value rhs) {
return lhs.match(
[&](const std::string& a) { return a > rhs.get<std::string>(); },
[&](double a) { return a > rhs.get<double>(); },
[&](const auto&) { assert(false); return false; }
);
}
bool lteq(Value lhs, Value rhs) {
return lhs.match(
[&](const std::string& a) { return a <= rhs.get<std::string>(); },
[&](double a) { return a <= rhs.get<double>(); },
[&](const auto&) { assert(false); return false; }
);
}
bool gteq(Value lhs, Value rhs) {
return lhs.match(
[&](const std::string& a) { return a >= rhs.get<std::string>(); },
[&](double a) { return a >= rhs.get<double>(); },
[&](const auto&) { assert(false); return false; }
);
}
bool eqCollate(std::string a, std::string b, Collator c) { return c.compare(a, b) == 0; }
bool neqCollate(std::string a, std::string b, Collator c) { return !eqCollate(a, b, c); }
bool ltCollate(std::string a, std::string b, Collator c) { return c.compare(a, b) < 0; }
bool gtCollate(std::string a, std::string b, Collator c) { return c.compare(a, b) > 0; }
bool lteqCollate(std::string a, std::string b, Collator c) { return c.compare(a, b) <= 0; }
bool gteqCollate(std::string a, std::string b, Collator c) { return c.compare(a, b) >= 0; }
static BasicComparison::CompareFunctionType getBasicCompareFunction(const std::string& op) {
if (op == "==") return eq;
else if (op == "!=") return neq;
else if (op == ">") return gt;
else if (op == "<") return lt;
else if (op == ">=") return gteq;
else if (op == "<=") return lteq;
assert(false);
return nullptr;
}
static CollatorComparison::CompareFunctionType getCollatorComparisonFunction(const std::string& op) {
if (op == "==") return eqCollate;
else if (op == "!=") return neqCollate;
else if (op == ">") return gtCollate;
else if (op == "<") return ltCollate;
else if (op == ">=") return gteqCollate;
else if (op == "<=") return lteqCollate;
assert(false);
return nullptr;
}
BasicComparison::BasicComparison(
std::string op_,
std::unique_ptr<Expression> lhs_,
std::unique_ptr<Expression> rhs_)
: Expression(Kind::Comparison, type::Boolean),
op(std::move(op_)),
compare(getBasicCompareFunction(op)),
lhs(std::move(lhs_)),
rhs(std::move(rhs_)) {
assert(isComparableType(op, lhs->getType()) && isComparableType(op, rhs->getType()));
assert(lhs->getType() == rhs->getType() || lhs->getType() == type::Value || rhs->getType() == type::Value);
needsRuntimeTypeCheck = (op != "==" && op != "!=") &&
(lhs->getType() == type::Value || rhs->getType() == type::Value);
}
EvaluationResult BasicComparison::evaluate(const EvaluationContext& params) const {
EvaluationResult lhsResult = lhs->evaluate(params);
if (!lhsResult) return lhsResult;
EvaluationResult rhsResult = rhs->evaluate(params);
if (!rhsResult) return rhsResult;
if (needsRuntimeTypeCheck) {
type::Type lhsType = typeOf(*lhsResult);
type::Type rhsType = typeOf(*rhsResult);
// check that type is string or number, and equal
if (lhsType != rhsType || !(lhsType == type::String || lhsType == type::Number)) {
return EvaluationError {
R"(Expected arguments for ")" + op + R"(")" +
" to be (string, string) or (number, number), but found (" + toString(lhsType) + ", " +
toString(rhsType) + ") instead."
};
}
}
return compare(*lhsResult, *rhsResult);
}
void BasicComparison::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*lhs);
visit(*rhs);
}
std::string BasicComparison::getOperator() const { return op; }
bool BasicComparison::operator==(const Expression& e) const {
if (e.getKind() == Kind::Comparison) {
auto comp = static_cast<const BasicComparison*>(&e);
return comp->op == op &&
*comp->lhs == *lhs &&
*comp->rhs == *rhs;
}
return false;
}
std::vector<optional<Value>> BasicComparison::possibleOutputs() const {
return {{ true }, { false }};
}
CollatorComparison::CollatorComparison(
std::string op_,
std::unique_ptr<Expression> lhs_,
std::unique_ptr<Expression> rhs_,
std::unique_ptr<Expression> collator_)
: Expression(Kind::Comparison, type::Boolean),
op(op_),
compare(getCollatorComparisonFunction(op)),
lhs(std::move(lhs_)),
rhs(std::move(rhs_)),
collator(std::move(collator_)) {
assert(isComparableType(op, lhs->getType()) && isComparableType(op, rhs->getType()));
assert(lhs->getType() == rhs->getType() || lhs->getType() == type::Value || rhs->getType() == type::Value);
needsRuntimeTypeCheck = (op == "==" || op == "!=") &&
(lhs->getType() == type::Value || rhs->getType() == type::Value);
}
EvaluationResult CollatorComparison::evaluate(const EvaluationContext& params) const {
EvaluationResult lhsResult = lhs->evaluate(params);
if (!lhsResult) return lhsResult;
EvaluationResult rhsResult = rhs->evaluate(params);
if (!rhsResult) return lhsResult;
if (needsRuntimeTypeCheck) {
if (typeOf(*lhsResult) != type::String || typeOf(*rhsResult) != type::String) {
return getBasicCompareFunction(op)(*lhsResult, *rhsResult);
}
}
auto collatorResult = collator->evaluate(params);
if (!collatorResult) return collatorResult;
const Collator& c = collatorResult->get<Collator>();
return compare(lhsResult->get<std::string>(), rhsResult->get<std::string>(), c);
}
void CollatorComparison::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*lhs);
visit(*rhs);
visit(*collator);
}
std::string CollatorComparison::getOperator() const { return op; }
bool CollatorComparison::operator==(const Expression& e) const {
if (e.getKind() == Kind::Comparison) {
auto comp = static_cast<const CollatorComparison*>(&e);
return comp->op == op &&
*comp->collator == *collator &&
*comp->lhs == *lhs &&
*comp->rhs == *rhs;
}
return false;
}
std::vector<optional<Value>> CollatorComparison::possibleOutputs() const {
return {{ true }, { false }};
}
using namespace mbgl::style::conversion;
ParseResult parseComparison(const Convertible& value, ParsingContext& ctx) {
std::size_t length = arrayLength(value);
if (length != 3 && length != 4) {
ctx.error("Expected two or three arguments.");
return ParseResult();
}
std::string op = *toString(arrayMember(value, 0));
assert(getBasicCompareFunction(op));
ParseResult lhs = ctx.parse(arrayMember(value, 1), 1, {type::Value});
if (!lhs) return ParseResult();
type::Type lhsType = (*lhs)->getType();
if (!isComparableType(op, lhsType)) {
ctx.error(R"(")" + op + R"(" comparisons are not supported for type ')" + toString(lhsType) + "'.", 1);
return ParseResult();
}
ParseResult rhs = ctx.parse(arrayMember(value, 2), 2, {type::Value});
if (!rhs) return ParseResult();
type::Type rhsType = (*rhs)->getType();
if (!isComparableType(op, rhsType)) {
ctx.error(R"(")" + op + R"(" comparisons are not supported for type ')" + toString(rhsType) + "'.", 2);
return ParseResult();
}
if (
lhsType != rhsType &&
lhsType != type::Value &&
rhsType != type::Value
) {
ctx.error("Cannot compare types '" + toString(lhsType) + "' and '" + toString(rhsType) + "'.");
return ParseResult();
}
if (op != "==" && op != "!=") {
// typing rules specific to less/greater than operators
if (lhsType == type::Value && rhsType != type::Value) {
// (value, T)
lhs = dsl::assertion(rhsType, std::move(*lhs));
} else if (lhsType != type::Value && rhsType == type::Value) {
// (T, value)
rhs = dsl::assertion(lhsType, std::move(*rhs));
}
}
if (length == 4) {
if (
lhsType != type::String &&
rhsType != type::String &&
lhsType != type::Value &&
rhsType != type::Value
) {
ctx.error("Cannot use collator to compare non-string types.");
return ParseResult();
}
ParseResult collatorParseResult = ctx.parse(arrayMember(value, 3), 3, {type::Collator});
if (!collatorParseResult) return ParseResult();
return ParseResult(std::make_unique<CollatorComparison>(op, std::move(*lhs), std::move(*rhs), std::move(*collatorParseResult)));
}
return ParseResult(std::make_unique<BasicComparison>(op, std::move(*lhs), std::move(*rhs)));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -485,19 +485,6 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
define("ceil", [](double x) -> Result<double> { return std::ceil(x); });
define("abs", [](double x) -> Result<double> { return std::abs(x); });
define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; });
define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; });
define(">", [](const std::string& lhs, const std::string& rhs, const Collator& c) -> Result<bool> { return c.compare(lhs, rhs) > 0; });
define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; });
define(">=",[](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs >= rhs; });
define(">=", [](const std::string& lhs, const std::string& rhs, const Collator& c) -> Result<bool> { return c.compare(lhs, rhs) >= 0; });
define("<", [](double lhs, double rhs) -> Result<bool> { return lhs < rhs; });
define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; });
define("<", [](const std::string& lhs, const std::string& rhs, const Collator& c) -> Result<bool> { return c.compare(lhs, rhs) < 0; });
define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; });
define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; });
define("<=", [](const std::string& lhs, const std::string& rhs, const Collator& c) -> Result<bool> { return c.compare(lhs, rhs) <= 0; });
define("!", [](bool e) -> Result<bool> { return !e; });
define("is-supported-script", [](const std::string& x) -> Result<bool> {

View File

@@ -3,7 +3,7 @@
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/style/expression/assertion.hpp>
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/equals.hpp>
#include <mbgl/style/expression/comparison.hpp>
#include <mbgl/style/expression/step.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
@@ -53,16 +53,20 @@ std::unique_ptr<Expression> literal(std::initializer_list<const char *> value) {
return literal(values);
}
std::unique_ptr<Expression> assertion(type::Type type, std::unique_ptr<Expression> value) {
return std::make_unique<Assertion>(type, vec(std::move(value)));
}
std::unique_ptr<Expression> number(std::unique_ptr<Expression> value) {
return std::make_unique<Assertion>(type::Number, vec(std::move(value)));
return assertion(type::Number, std::move(value));
}
std::unique_ptr<Expression> string(std::unique_ptr<Expression> value) {
return std::make_unique<Assertion>(type::String, vec(std::move(value)));
return assertion(type::String, std::move(value));
}
std::unique_ptr<Expression> boolean(std::unique_ptr<Expression> value) {
return std::make_unique<Assertion>(type::Boolean, vec(std::move(value)));
return assertion(type::Boolean, std::move(value));
}
std::unique_ptr<Expression> toColor(std::unique_ptr<Expression> value) {
@@ -91,22 +95,22 @@ std::unique_ptr<Expression> zoom() {
std::unique_ptr<Expression> eq(std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs) {
return std::make_unique<Equals>(std::move(lhs), std::move(rhs), nullopt, false);
return std::make_unique<BasicComparison>("==", std::move(lhs), std::move(rhs));
}
std::unique_ptr<Expression> ne(std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs) {
return std::make_unique<Equals>(std::move(lhs), std::move(rhs), nullopt, true);
return std::make_unique<BasicComparison>("!=", std::move(lhs), std::move(rhs));
}
std::unique_ptr<Expression> gt(std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs) {
return compound(">", std::move(lhs), std::move(rhs));
return std::make_unique<BasicComparison>(">", std::move(lhs), std::move(rhs));
}
std::unique_ptr<Expression> lt(std::unique_ptr<Expression> lhs,
std::unique_ptr<Expression> rhs) {
return compound("<", std::move(lhs), std::move(rhs));
return std::make_unique<BasicComparison>("<", std::move(lhs), std::move(rhs));
}
std::unique_ptr<Expression> step(std::unique_ptr<Expression> input,

View File

@@ -1,113 +0,0 @@
#include <mbgl/style/expression/collator.hpp>
#include <mbgl/style/expression/equals.hpp>
namespace mbgl {
namespace style {
namespace expression {
static bool isComparableType(const type::Type& type) {
return type == type::String ||
type == type::Number ||
type == type::Boolean ||
type == type::Null;
}
Equals::Equals(std::unique_ptr<Expression> lhs_, std::unique_ptr<Expression> rhs_, optional<std::unique_ptr<Expression>> collator_, bool negate_)
: Expression(Kind::Equals, type::Boolean),
lhs(std::move(lhs_)),
rhs(std::move(rhs_)),
collator(std::move(collator_)),
negate(negate_) {
assert(isComparableType(lhs->getType()) || isComparableType(rhs->getType()));
assert(lhs->getType() == rhs->getType() || lhs->getType() == type::Value || rhs->getType() == type::Value);
}
EvaluationResult Equals::evaluate(const EvaluationContext& params) const {
EvaluationResult lhsResult = lhs->evaluate(params);
if (!lhsResult) return lhsResult;
EvaluationResult rhsResult = rhs->evaluate(params);
if (!rhsResult) return lhsResult;
bool result;
if (collator) {
auto collatorResult = (*collator)->evaluate(params);
const Collator& c = collatorResult->get<Collator>();
result = c.compare(lhsResult->get<std::string>(), rhsResult->get<std::string>()) == 0;
} else {
result = *lhsResult == *rhsResult;
}
if (negate) {
result = !result;
}
return result;
}
void Equals::eachChild(const std::function<void(const Expression&)>& visit) const {
visit(*lhs);
visit(*rhs);
if (collator) {
visit(**collator);
}
}
bool Equals::operator==(const Expression& e) const {
if (e.getKind() == Kind::Equals) {
auto eq = static_cast<const Equals*>(&e);
return eq->negate == negate && *eq->lhs == *lhs && *eq->rhs == *rhs;
}
return false;
}
std::vector<optional<Value>> Equals::possibleOutputs() const {
return {{ true }, { false }};
}
using namespace mbgl::style::conversion;
ParseResult Equals::parse(const Convertible& value, ParsingContext& ctx) {
std::size_t length = arrayLength(value);
if (length != 3 && length != 4) {
ctx.error("Expected two or three arguments.");
return ParseResult();
}
bool negate = toString(arrayMember(value, 0)) == std::string("!=");
ParseResult lhs = ctx.parse(arrayMember(value, 1), 1, {type::Value});
if (!lhs) return ParseResult();
ParseResult rhs = ctx.parse(arrayMember(value, 2), 2, {type::Value});
if (!rhs) return ParseResult();
type::Type lhsType = (*lhs)->getType();
type::Type rhsType = (*rhs)->getType();
if (!isComparableType(lhsType) && !isComparableType(rhsType)) {
ctx.error("Expected at least one argument to be a string, number, boolean, or null, but found (" +
toString(lhsType) + ", " + toString(rhsType) + ") instead.");
return ParseResult();
}
if (lhsType != rhsType && lhsType != type::Value && rhsType != type::Value) {
ctx.error("Cannot compare " + toString(lhsType) + " and " + toString(rhsType) + ".");
return ParseResult();
}
ParseResult collatorParseResult;
if (length == 4) {
if (lhsType != type::String && rhsType != type::String) {
ctx.error("Cannot use collator to compare non-string types.");
return ParseResult();
}
collatorParseResult = ctx.parse(arrayMember(value, 3), 3, {type::Collator});
if (!collatorParseResult) return ParseResult();
}
return ParseResult(std::make_unique<Equals>(std::move(*lhs), std::move(*rhs), std::move(collatorParseResult), negate));
}
} // namespace expression
} // namespace style
} // namespace mbgl

View File

@@ -13,7 +13,7 @@
#include <mbgl/style/expression/coalesce.hpp>
#include <mbgl/style/expression/coercion.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/equals.hpp>
#include <mbgl/style/expression/comparison.hpp>
#include <mbgl/style/expression/interpolate.hpp>
#include <mbgl/style/expression/length.hpp>
#include <mbgl/style/expression/let.hpp>
@@ -95,8 +95,12 @@ ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_,
const ExpressionRegistry& getExpressionRegistry() {
static ExpressionRegistry registry {{
{"==", Equals::parse},
{"!=", Equals::parse},
{"==", parseComparison},
{"!=", parseComparison},
{">", parseComparison},
{"<", parseComparison},
{">=", parseComparison},
{"<=", parseComparison},
{"all", All::parse},
{"any", Any::parse},
{"array", ArrayAssertion::parse},