diff --git a/.bazelrc b/.bazelrc index 35a1201c7..2521e741d 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,5 @@ build --cxxopt=-std=c++17 +build --cxxopt=-fsized-deallocation # Enable matchers in googletest build --define absl=1 diff --git a/base/BUILD b/base/BUILD new file mode 100644 index 000000000..7a547dd68 --- /dev/null +++ b/base/BUILD @@ -0,0 +1,277 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + # Under active development, not yet being released. + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) + +cc_library( + name = "handle", + hdrs = ["handle.h"], + deps = [ + "//base/internal:handle", + "//internal:casts", + "@com_google_absl//absl/base:core_headers", + ], +) + +cc_library( + name = "kind", + srcs = ["kind.cc"], + hdrs = ["kind.h"], + deps = [ + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "kind_test", + srcs = ["kind_test.cc"], + deps = [ + ":kind", + "//internal:testing", + ], +) + +cc_library( + name = "memory_manager", + srcs = ["memory_manager.cc"], + hdrs = ["memory_manager.h"], + deps = [ + "//base/internal:memory_manager", + "//internal:no_destructor", + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:config", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:dynamic_annotations", + "@com_google_absl//absl/numeric:bits", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:optional", + ], +) + +cc_test( + name = "memory_manager_test", + srcs = ["memory_manager_test.cc"], + deps = [ + ":memory_manager", + "//internal:testing", + ], +) + +cc_library( + name = "operators", + srcs = ["operators.cc"], + hdrs = ["operators.h"], + deps = [ + "//base/internal:operators", + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "operators_test", + srcs = ["operators_test.cc"], + deps = [ + ":operators", + "//internal:testing", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + ], +) + +cc_library( + name = "type", + srcs = [ + "type.cc", + "type_factory.cc", + "type_manager.cc", + "type_provider.cc", + ], + hdrs = [ + "type.h", + "type_factory.h", + "type_manager.h", + "type_provider.h", + "type_registry.h", + ], + deps = [ + ":handle", + ":kind", + ":memory_manager", + "//base/internal:type", + "//internal:casts", + "//internal:no_destructor", + "//internal:rtti", + "//internal:status_macros", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", + "@com_google_absl//absl/types:variant", + ], +) + +cc_test( + name = "type_test", + srcs = [ + "type_factory_test.cc", + "type_test.cc", + ], + deps = [ + ":handle", + ":memory_manager", + ":type", + ":value", + "//base/internal:memory_manager_testing", + "//internal:testing", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + ], +) + +cc_library( + name = "value", + srcs = [ + "value.cc", + "value_factory.cc", + ], + hdrs = [ + "value.h", + "value_factory.h", + ], + deps = [ + ":handle", + ":kind", + ":memory_manager", + ":type", + "//base/internal:value", + "//internal:casts", + "//internal:no_destructor", + "//internal:rtti", + "//internal:status_macros", + "//internal:strings", + "//internal:time", + "//internal:utf8", + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:btree", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:cord", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:variant", + ], +) + +cc_test( + name = "value_test", + srcs = [ + "value_factory_test.cc", + "value_test.cc", + ], + deps = [ + ":memory_manager", + ":type", + ":value", + "//base/internal:memory_manager_testing", + "//internal:strings", + "//internal:testing", + "//internal:time", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + ], +) + +cc_library( + name = "ast", + hdrs = [ + "ast.h", + ], + deps = [ + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:variant", + ], +) + +cc_test( + name = "ast_test", + srcs = [ + "ast_test.cc", + ], + deps = [ + ":ast", + "//internal:testing", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/types:variant", + ], +) + +cc_library( + name = "ast_utility", + srcs = ["ast_utility.cc"], + hdrs = ["ast_utility.h"], + deps = [ + ":ast", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/time", + "@com_google_googleapis//google/api/expr/v1alpha1:checked_cc_proto", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "ast_utility_test", + srcs = [ + "ast_utility_test.cc", + ], + deps = [ + ":ast", + ":ast_utility", + "//internal:testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:variant", + "@com_google_googleapis//google/api/expr/v1alpha1:checked_cc_proto", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) diff --git a/base/ast.h b/base/ast.h new file mode 100644 index 000000000..a4fcc34ac --- /dev/null +++ b/base/ast.h @@ -0,0 +1,1008 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_BASE_AST_H_ +#define THIRD_PARTY_CEL_CPP_BASE_AST_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/time.h" +#include "absl/types/variant.h" +namespace cel::ast::internal { + +enum class NullValue { kNullValue = 0 }; + +// Represents a primitive literal. +// +// This is similar as the primitives supported in the well-known type +// `google.protobuf.Value`, but richer so it can represent CEL's full range of +// primitives. +// +// Lists and structs are not included as constants as these aggregate types may +// contain [Expr][] elements which require evaluation and are thus not constant. +// +// Examples of constants include: `"hello"`, `b'bytes'`, `1u`, `4.2`, `-2`, +// `true`, `null`. +// +// (-- +// TODO(issues/5): Extend or replace the constant with a canonical Value +// message that can hold any constant object representation supplied or +// produced at evaluation time. +// --) +using Constant = absl::variant; + +class Expr; + +// An identifier expression. e.g. `request`. +class Ident { + public: + explicit Ident(std::string name) : name_(std::move(name)) {} + + void set_name(std::string name) { name_ = std::move(name); } + + const std::string& name() const { return name_; } + + private: + // Required. Holds a single, unqualified identifier, possibly preceded by a + // '.'. + // + // Qualified names are represented by the [Expr.Select][] expression. + std::string name_; +}; + +// A field selection expression. e.g. `request.auth`. +class Select { + public: + Select() {} + Select(std::unique_ptr operand, std::string field, + bool test_only = false) + : operand_(std::move(operand)), + field_(std::move(field)), + test_only_(test_only) {} + + void set_operand(std::unique_ptr operand) { + operand_ = std::move(operand); + } + + void set_field(std::string field) { field_ = std::move(field); } + + void set_test_only(bool test_only) { test_only_ = test_only; } + + const Expr* operand() const { return operand_.get(); } + + Expr& mutable_operand() { + if (operand_ == nullptr) { + operand_ = std::make_unique(); + } + return *operand_; + } + + const std::string& field() const { return field_; } + + bool test_only() const { return test_only_; } + + private: + // Required. The target of the selection expression. + // + // For example, in the select expression `request.auth`, the `request` + // portion of the expression is the `operand`. + std::unique_ptr operand_; + // Required. The name of the field to select. + // + // For example, in the select expression `request.auth`, the `auth` portion + // of the expression would be the `field`. + std::string field_; + // Whether the select is to be interpreted as a field presence test. + // + // This results from the macro `has(request.auth)`. + bool test_only_; +}; + +// A call expression, including calls to predefined functions and operators. +// +// For example, `value == 10`, `size(map_value)`. +// (-- TODO(issues/5): Convert built-in globals to instance methods --) +class Call { + public: + Call() {} + Call(std::unique_ptr target, std::string function, + std::vector args) + : target_(std::move(target)), + function_(std::move(function)), + args_(std::move(args)) {} + + void set_target(std::unique_ptr target) { target_ = std::move(target); } + + void set_function(std::string function) { function_ = std::move(function); } + + void set_args(std::vector args) { args_ = std::move(args); } + + const Expr* target() const { return target_.get(); } + + Expr& mutable_target() { + if (target_ == nullptr) { + target_ = std::make_unique(); + } + return *target_; + } + + const std::string& function() const { return function_; } + + const std::vector& args() const { return args_; } + + std::vector& mutable_args() { return args_; } + + private: + // The target of an method call-style expression. For example, `x` in + // `x.f()`. + std::unique_ptr target_; + // Required. The name of the function or method being called. + std::string function_; + // The arguments. + std::vector args_; +}; + +// A list creation expression. +// +// Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g. +// `dyn([1, 'hello', 2.0])` +// (-- +// TODO(issues/5): Determine how to disable heterogeneous types as a feature +// of type-checking rather than through the language construct 'dyn'. +// --) +class CreateList { + public: + CreateList() {} + explicit CreateList(std::vector elements) + : elements_(std::move(elements)) {} + + void set_elements(std::vector elements) { + elements_ = std::move(elements); + } + + const std::vector& elements() const { return elements_; } + + std::vector& mutable_elements() { return elements_; } + + private: + // The elements part of the list. + std::vector elements_; +}; + +// A map or message creation expression. +// +// Maps are constructed as `{'key_name': 'value'}`. Message construction is +// similar, but prefixed with a type name and composed of field ids: +// `types.MyType{field_id: 'value'}`. +class CreateStruct { + public: + // Represents an entry. + class Entry { + public: + using KeyKind = absl::variant>; + Entry() {} + Entry(int64_t id, KeyKind key_kind, std::unique_ptr value) + : id_(id), key_kind_(std::move(key_kind)), value_(std::move(value)) {} + + void set_id(int64_t id) { id_ = id; } + + void set_key_kind(KeyKind key_kind) { key_kind_ = std::move(key_kind); } + + void set_value(std::unique_ptr value) { value_ = std::move(value); } + + int64_t id() const { return id_; } + + const KeyKind& key_kind() const { return key_kind_; } + + KeyKind& mutable_key_kind() { return key_kind_; } + + const Expr* value() const { return value_.get(); } + + Expr& mutable_value() { + if (value_ == nullptr) { + value_ = std::make_unique(); + } + return *value_; + } + + private: + // Required. An id assigned to this node by the parser which is unique + // in a given expression tree. This is used to associate type + // information and other attributes to the node. + int64_t id_; + // The `Entry` key kinds. + KeyKind key_kind_; + // Required. The value assigned to the key. + std::unique_ptr value_; + }; + + CreateStruct() {} + CreateStruct(std::string message_name, std::vector entries) + : message_name_(std::move(message_name)), entries_(std::move(entries)) {} + + void set_message_name(std::string message_name) { + message_name_ = std::move(message_name); + } + + void set_entries(std::vector entries) { + entries_ = std::move(entries); + } + + const std::vector& entries() const { return entries_; } + + std::vector& mutable_entries() { return entries_; } + + private: + // The type name of the message to be created, empty when creating map + // literals. + std::string message_name_; + // The entries in the creation expression. + std::vector entries_; +}; + +// A comprehension expression applied to a list or map. +// +// Comprehensions are not part of the core syntax, but enabled with macros. +// A macro matches a specific call signature within a parsed AST and replaces +// the call with an alternate AST block. Macro expansion happens at parse +// time. +// +// The following macros are supported within CEL: +// +// Aggregate type macros may be applied to all elements in a list or all keys +// in a map: +// +// * `all`, `exists`, `exists_one` - test a predicate expression against +// the inputs and return `true` if the predicate is satisfied for all, +// any, or only one value `list.all(x, x < 10)`. +// * `filter` - test a predicate expression against the inputs and return +// the subset of elements which satisfy the predicate: +// `payments.filter(p, p > 1000)`. +// * `map` - apply an expression to all elements in the input and return the +// output aggregate type: `[1, 2, 3].map(i, i * i)`. +// +// The `has(m.x)` macro tests whether the property `x` is present in struct +// `m`. The semantics of this macro depend on the type of `m`. For proto2 +// messages `has(m.x)` is defined as 'defined, but not set`. For proto3, the +// macro tests whether the property is set to its default. For map and struct +// types, the macro tests whether the property `x` is defined on `m`. +// +// Comprehension evaluation can be best visualized as the following +// pseudocode: +// +// ``` +// let `accu_var` = `accu_init` +// for (let `iter_var` in `iter_range`) { +// if (!`loop_condition`) { +// break +// } +// `accu_var` = `loop_step` +// } +// return `result` +// ``` +// +// (-- +// TODO(issues/5): ensure comprehensions work equally well on maps and +// messages. +// --) +class Comprehension { + public: + Comprehension() {} + Comprehension(std::string iter_var, std::unique_ptr iter_range, + std::string accu_var, std::unique_ptr accu_init, + std::unique_ptr loop_condition, + std::unique_ptr loop_step, std::unique_ptr result) + : iter_var_(std::move(iter_var)), + iter_range_(std::move(iter_range)), + accu_var_(std::move(accu_var)), + accu_init_(std::move(accu_init)), + loop_condition_(std::move(loop_condition)), + loop_step_(std::move(loop_step)), + result_(std::move(result)) {} + + void set_iter_var(std::string iter_var) { iter_var_ = std::move(iter_var); } + + void set_iter_range(std::unique_ptr iter_range) { + iter_range_ = std::move(iter_range); + } + + void set_accu_var(std::string accu_var) { accu_var_ = std::move(accu_var); } + + void set_accu_init(std::unique_ptr accu_init) { + accu_init_ = std::move(accu_init); + } + + void set_loop_condition(std::unique_ptr loop_condition) { + loop_condition_ = std::move(loop_condition); + } + + void set_loop_step(std::unique_ptr loop_step) { + loop_step_ = std::move(loop_step); + } + + void set_result(std::unique_ptr result) { result_ = std::move(result); } + + const std::string& iter_var() const { return iter_var_; } + + const Expr* iter_range() const { return iter_range_.get(); } + + Expr& mutable_iter_range() { + if (iter_range_ == nullptr) { + iter_range_ = std::make_unique(); + } + return *iter_range_; + } + + const std::string& accu_var() const { return accu_var_; } + + const Expr* accu_init() const { return accu_init_.get(); } + + Expr& mutable_accu_init() { + if (accu_init_ == nullptr) { + accu_init_ = std::make_unique(); + } + return *accu_init_; + } + + const Expr* loop_condition() const { return loop_condition_.get(); } + + Expr& mutable_loop_condition() { + if (loop_condition_ == nullptr) { + loop_condition_ = std::make_unique(); + } + return *loop_condition_; + } + + const Expr* loop_step() const { return loop_step_.get(); } + + Expr& mutable_loop_step() { + if (loop_step_ == nullptr) { + loop_step_ = std::make_unique(); + } + return *loop_step_; + } + + const Expr* result() const { return result_.get(); } + + Expr& mutable_result() { + if (result_ == nullptr) { + result_ = std::make_unique(); + } + return *result_; + } + + private: + // The name of the iteration variable. + std::string iter_var_; + + // The range over which var iterates. + std::unique_ptr iter_range_; + + // The name of the variable used for accumulation of the result. + std::string accu_var_; + + // The initial value of the accumulator. + std::unique_ptr accu_init_; + + // An expression which can contain iter_var and accu_var. + // + // Returns false when the result has been computed and may be used as + // a hint to short-circuit the remainder of the comprehension. + std::unique_ptr loop_condition_; + + // An expression which can contain iter_var and accu_var. + // + // Computes the next value of accu_var. + std::unique_ptr loop_step_; + + // An expression which can contain accu_var. + // + // Computes the result. + std::unique_ptr result_; +}; + +using ExprKind = absl::variant; + +// Analogous to google::api::expr::v1alpha1::Expr +// An abstract representation of a common expression. +// +// Expressions are abstractly represented as a collection of identifiers, +// select statements, function calls, literals, and comprehensions. All +// operators with the exception of the '.' operator are modelled as function +// calls. This makes it easy to represent new operators into the existing AST. +// +// All references within expressions must resolve to a [Decl][] provided at +// type-check for an expression to be valid. A reference may either be a bare +// identifier `name` or a qualified identifier `google.api.name`. References +// may either refer to a value or a function declaration. +// +// For example, the expression `google.api.name.startsWith('expr')` references +// the declaration `google.api.name` within a [Expr.Select][] expression, and +// the function declaration `startsWith`. +// Move-only type. +class Expr { + public: + Expr() {} + Expr(int64_t id, ExprKind expr_kind) + : id_(id), expr_kind_(std::move(expr_kind)) {} + + Expr(Expr&& rhs) = default; + Expr& operator=(Expr&& rhs) = default; + + void set_id(int64_t id) { id_ = id; } + + void set_expr_kind(ExprKind expr_kind) { expr_kind_ = std::move(expr_kind); } + + int64_t id() const { return id_; } + + const ExprKind& expr_kind() const { return expr_kind_; } + + ExprKind& mutable_expr_kind() { return expr_kind_; } + + private: + // Required. An id assigned to this node by the parser which is unique in a + // given expression tree. This is used to associate type information and other + // attributes to a node in the parse tree. + int64_t id_ = 0; + // Required. Variants of expressions. + ExprKind expr_kind_; +}; + +// Source information collected at parse time. +class SourceInfo { + public: + SourceInfo() {} + SourceInfo(std::string syntax_version, std::string location, + std::vector line_offsets, + absl::flat_hash_map positions, + absl::flat_hash_map macro_calls) + : syntax_version_(std::move(syntax_version)), + location_(std::move(location)), + line_offsets_(std::move(line_offsets)), + positions_(std::move(positions)), + macro_calls_(std::move(macro_calls)) {} + + void set_syntax_version(std::string syntax_version) { + syntax_version_ = std::move(syntax_version); + } + + void set_location(std::string location) { location_ = std::move(location); } + + void set_line_offsets(std::vector line_offsets) { + line_offsets_ = std::move(line_offsets); + } + + void set_positions(absl::flat_hash_map positions) { + positions_ = std::move(positions); + } + + void set_macro_calls(absl::flat_hash_map macro_calls) { + macro_calls_ = std::move(macro_calls); + } + + const std::string& syntax_version() const { return syntax_version_; } + + const std::string& location() const { return location_; } + + const std::vector& line_offsets() const { return line_offsets_; } + + std::vector& mutable_line_offsets() { return line_offsets_; } + + const absl::flat_hash_map& positions() const { + return positions_; + } + + absl::flat_hash_map& mutable_positions() { + return positions_; + } + + const absl::flat_hash_map& macro_calls() const { + return macro_calls_; + } + + absl::flat_hash_map& mutable_macro_calls() { + return macro_calls_; + } + + private: + // The syntax version of the source, e.g. `cel1`. + std::string syntax_version_; + + // The location name. All position information attached to an expression is + // relative to this location. + // + // The location could be a file, UI element, or similar. For example, + // `acme/app/AnvilPolicy.cel`. + std::string location_; + + // Monotonically increasing list of code point offsets where newlines + // `\n` appear. + // + // The line number of a given position is the index `i` where for a given + // `id` the `line_offsets[i] < id_positions[id] < line_offsets[i+1]`. The + // column may be derivd from `id_positions[id] - line_offsets[i]`. + // + // TODO(issues/5): clarify this documentation + std::vector line_offsets_; + + // A map from the parse node id (e.g. `Expr.id`) to the code point offset + // within source. + absl::flat_hash_map positions_; + + // A map from the parse node id where a macro replacement was made to the + // call `Expr` that resulted in a macro expansion. + // + // For example, `has(value.field)` is a function call that is replaced by a + // `test_only` field selection in the AST. Likewise, the call + // `list.exists(e, e > 10)` translates to a comprehension expression. The key + // in the map corresponds to the expression id of the expanded macro, and the + // value is the call `Expr` that was replaced. + absl::flat_hash_map macro_calls_; +}; + +// Analogous to google::api::expr::v1alpha1::ParsedExpr +// An expression together with source information as returned by the parser. +// Move-only type. +class ParsedExpr { + public: + ParsedExpr() {} + ParsedExpr(Expr expr, SourceInfo source_info) + : expr_(std::move(expr)), source_info_(std::move(source_info)) {} + + ParsedExpr(ParsedExpr&& rhs) = default; + ParsedExpr& operator=(ParsedExpr&& rhs) = default; + + void set_expr(Expr expr) { expr_ = std::move(expr); } + + void set_source_info(SourceInfo source_info) { + source_info_ = std::move(source_info); + } + + const Expr& expr() const { return expr_; } + + Expr& mutable_expr() { return expr_; } + + const SourceInfo& source_info() const { return source_info_; } + + SourceInfo& mutable_source_info() { return source_info_; } + + private: + // The parsed expression. + Expr expr_; + // The source info derived from input that generated the parsed `expr`. + SourceInfo source_info_; +}; + +// CEL primitive types. +enum class PrimitiveType { + // Unspecified type. + kPrimitiveTypeUnspecified = 0, + // Boolean type. + kBool = 1, + // Int64 type. + // + // Proto-based integer values are widened to int64_t. + kInt64 = 2, + // Uint64 type. + // + // Proto-based unsigned integer values are widened to uint64_t. + kUint64 = 3, + // Double type. + // + // Proto-based float values are widened to double values. + kDouble = 4, + // String type. + kString = 5, + // Bytes type. + kBytes = 6, +}; + +// Well-known protobuf types treated with first-class support in CEL. +// +// TODO(issues/5): represent well-known via abstract types (or however) +// they will be named. +enum class WellKnownType { + // Unspecified type. + kWellKnownTypeUnspecified = 0, + // Well-known protobuf.Any type. + // + // Any types are a polymorphic message type. During type-checking they are + // treated like `DYN` types, but at runtime they are resolved to a specific + // message type specified at evaluation time. + kAny = 1, + // Well-known protobuf.Timestamp type, internally referenced as `timestamp`. + kTimestamp = 2, + // Well-known protobuf.Duration type, internally referenced as `duration`. + kDuration = 3, +}; + +class Type; + +// List type with typed elements, e.g. `list`. +class ListType { + public: + ListType() {} + explicit ListType(std::unique_ptr elem_type) + : elem_type_(std::move(elem_type)) {} + + void set_elem_type(std::unique_ptr elem_type) { + elem_type_ = std::move(elem_type); + } + + const Type* elem_type() const { return elem_type_.get(); } + + Type& mutable_elem_type() { + if (elem_type_ == nullptr) { + elem_type_ = std::make_unique(); + } + return *elem_type_; + } + + private: + std::unique_ptr elem_type_; +}; + +// Map type with parameterized key and value types, e.g. `map`. +class MapType { + public: + MapType() {} + MapType(std::unique_ptr key_type, std::unique_ptr value_type) + : key_type_(std::move(key_type)), value_type_(std::move(value_type)) {} + + void set_key_type(std::unique_ptr key_type) { + key_type_ = std::move(key_type); + } + + void set_value_type(std::unique_ptr value_type) { + value_type_ = std::move(value_type); + } + + const Type* key_type() const { return key_type_.get(); } + + const Type* value_type() const { return value_type_.get(); } + + Type& mutable_key_type() { + if (key_type_ == nullptr) { + key_type_ = std::make_unique(); + } + return *key_type_; + } + + Type& mutable_value_type() { + if (value_type_ == nullptr) { + value_type_ = std::make_unique(); + } + return *value_type_; + } + + private: + // The type of the key. + std::unique_ptr key_type_; + + // The type of the value. + std::unique_ptr value_type_; +}; + +// Function type with result and arg types. +// +// (-- +// NOTE: function type represents a lambda-style argument to another function. +// Supported through macros, but not yet a first-class concept in CEL. +// --) +class FunctionType { + public: + FunctionType() {} + FunctionType(std::unique_ptr result_type, std::vector arg_types) + : result_type_(std::move(result_type)), + arg_types_(std::move(arg_types)) {} + + void set_result_type(std::unique_ptr result_type) { + result_type_ = std::move(result_type); + } + + void set_arg_types(std::vector arg_types) { + arg_types_ = std::move(arg_types); + } + + const Type* result_type() const { return result_type_.get(); } + + Type& mutable_result_type() { + if (result_type_ == nullptr) { + result_type_ = std::make_unique(); + } + return *result_type_; + } + + const std::vector& arg_types() const { return arg_types_; } + + std::vector& mutable_arg_types() { return arg_types_; } + + private: + // Result type of the function. + std::unique_ptr result_type_; + + // Argument types of the function. + std::vector arg_types_; +}; + +// Application defined abstract type. +// +// TODO(issues/5): decide on final naming for this. +class AbstractType { + public: + AbstractType(std::string name, std::vector parameter_types) + : name_(std::move(name)), parameter_types_(std::move(parameter_types)) {} + + void set_name(std::string name) { name_ = std::move(name); } + + void set_parameter_types(std::vector parameter_types) { + parameter_types_ = std::move(parameter_types); + } + + const std::string& name() const { return name_; } + + const std::vector& parameter_types() const { return parameter_types_; } + + std::vector& mutable_parameter_types() { return parameter_types_; } + + private: + // The fully qualified name of this abstract type. + std::string name_; + + // Parameter types for this abstract type. + std::vector parameter_types_; +}; + +// Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`. +class PrimitiveTypeWrapper { + public: + explicit PrimitiveTypeWrapper(PrimitiveType type) : type_(std::move(type)) {} + + void set_type(PrimitiveType type) { type_ = std::move(type); } + + const PrimitiveType& type() const { return type_; } + + PrimitiveType& mutable_type() { return type_; } + + private: + PrimitiveType type_; +}; + +// Protocol buffer message type. +// +// The `message_type` string specifies the qualified message type name. For +// example, `google.plus.Profile`. +class MessageType { + public: + explicit MessageType(std::string type) : type_(std::move(type)) {} + + void set_type(std::string type) { type_ = std::move(type); } + + const std::string& type() const { return type_; } + + private: + std::string type_; +}; + +// Type param type. +// +// The `type_param` string specifies the type parameter name, e.g. `list` +// would be a `list_type` whose element type was a `type_param` type +// named `E`. +class ParamType { + public: + explicit ParamType(std::string type) : type_(std::move(type)) {} + + void set_type(std::string type) { type_ = std::move(type); } + + const std::string& type() const { return type_; } + + private: + std::string type_; +}; + +// Error type. +// +// During type-checking if an expression is an error, its type is propagated +// as the `ERROR` type. This permits the type-checker to discover other +// errors present in the expression. +enum class ErrorType { kErrorTypeValue = 0 }; + +using DynamicType = absl::monostate; + +using TypeKind = + absl::variant, ErrorType, AbstractType>; + +// Analogous to google::api::expr::v1alpha1::Type. +// Represents a CEL type. +// +// TODO(issues/5): align with value.proto +class Type { + public: + Type() {} + explicit Type(TypeKind type_kind) : type_kind_(std::move(type_kind)) {} + + Type(Type&& rhs) = default; + Type& operator=(Type&& rhs) = default; + + void set_type_kind(TypeKind type_kind) { type_kind_ = std::move(type_kind); } + + const TypeKind& type_kind() const { return type_kind_; } + + TypeKind& mutable_type_kind() { return type_kind_; } + + private: + TypeKind type_kind_; +}; + +// Describes a resolved reference to a declaration. +class Reference { + public: + Reference(std::string name, std::vector overload_id, + Constant value) + : name_(std::move(name)), + overload_id_(std::move(overload_id)), + value_(std::move(value)) {} + + void set_name(std::string name) { name_ = std::move(name); } + + void set_overload_id(std::vector overload_id) { + overload_id_ = std::move(overload_id); + } + + void set_value(Constant value) { value_ = std::move(value); } + + const std::string& name() const { return name_; } + + const std::vector& overload_id() const { return overload_id_; } + + const Constant& value() const { return value_; } + + std::vector& mutable_overload_id() { return overload_id_; } + + Constant& mutable_value() { return value_; } + + private: + // The fully qualified name of the declaration. + std::string name_; + // For references to functions, this is a list of `Overload.overload_id` + // values which match according to typing rules. + // + // If the list has more than one element, overload resolution among the + // presented candidates must happen at runtime because of dynamic types. The + // type checker attempts to narrow down this list as much as possible. + // + // Empty if this is not a reference to a [Decl.FunctionDecl][]. + std::vector overload_id_; + // For references to constants, this may contain the value of the + // constant if known at compile time. + Constant value_; +}; + +// Analogous to google::api::expr::v1alpha1::CheckedExpr +// A CEL expression which has been successfully type checked. +// Move-only type. +class CheckedExpr { + public: + CheckedExpr() {} + CheckedExpr(absl::flat_hash_map reference_map, + absl::flat_hash_map type_map, + SourceInfo source_info, std::string expr_version, Expr expr) + : reference_map_(std::move(reference_map)), + type_map_(std::move(type_map)), + source_info_(std::move(source_info)), + expr_version_(std::move(expr_version)), + expr_(std::move(expr)) {} + + CheckedExpr(CheckedExpr&& rhs) = default; + CheckedExpr& operator=(CheckedExpr&& rhs) = default; + + void set_reference_map( + absl::flat_hash_map reference_map) { + reference_map_ = std::move(reference_map); + } + + void set_type_map(absl::flat_hash_map type_map) { + type_map_ = std::move(type_map); + } + + void set_source_info(SourceInfo source_info) { + source_info_ = std::move(source_info); + } + + void set_expr_version(std::string expr_version) { + expr_version_ = std::move(expr_version); + } + + void set_expr(Expr expr) { expr_ = std::move(expr); } + + const absl::flat_hash_map& reference_map() const { + return reference_map_; + } + + absl::flat_hash_map& mutable_reference_map() { + return reference_map_; + } + + const absl::flat_hash_map& type_map() const { + return type_map_; + } + + absl::flat_hash_map& mutable_type_map() { return type_map_; } + + const SourceInfo& source_info() const { return source_info_; } + + SourceInfo& mutable_source_info() { return source_info_; } + + const std::string& expr_version() const { return expr_version_; } + + const Expr& expr() const { return expr_; } + + Expr& mutable_expr() { return expr_; } + + private: + // A map from expression ids to resolved references. + // + // The following entries are in this table: + // + // - An Ident or Select expression is represented here if it resolves to a + // declaration. For instance, if `a.b.c` is represented by + // `select(select(id(a), b), c)`, and `a.b` resolves to a declaration, + // while `c` is a field selection, then the reference is attached to the + // nested select expression (but not to the id or or the outer select). + // In turn, if `a` resolves to a declaration and `b.c` are field selections, + // the reference is attached to the ident expression. + // - Every Call expression has an entry here, identifying the function being + // called. + // - Every CreateStruct expression for a message has an entry, identifying + // the message. + absl::flat_hash_map reference_map_; + // A map from expression ids to types. + // + // Every expression node which has a type different than DYN has a mapping + // here. If an expression has type DYN, it is omitted from this map to save + // space. + absl::flat_hash_map type_map_; + // The source info derived from input that generated the parsed `expr` and + // any optimizations made during the type-checking pass. + SourceInfo source_info_; + // The expr version indicates the major / minor version number of the `expr` + // representation. + // + // The most common reason for a version change will be to indicate to the CEL + // runtimes that transformations have been performed on the expr during static + // analysis. In some cases, this will save the runtime the work of applying + // the same or similar transformations prior to evaluation. + std::string expr_version_; + // The checked expression. Semantically equivalent to the parsed `expr`, but + // may have structural differences. + Expr expr_; +}; + +} // namespace cel::ast::internal + +#endif // THIRD_PARTY_CEL_CPP_BASE_AST_H_ diff --git a/base/ast_test.cc b/base/ast_test.cc new file mode 100644 index 000000000..8f1bf3bd7 --- /dev/null +++ b/base/ast_test.cc @@ -0,0 +1,254 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/ast.h" + +#include +#include + +#include "absl/memory/memory.h" +#include "absl/types/variant.h" +#include "internal/testing.h" + +namespace cel { +namespace ast { +namespace internal { +namespace { +TEST(AstTest, ExprConstructionConstant) { + Expr expr(1, true); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + const auto& constant = absl::get(expr.expr_kind()); + ASSERT_TRUE(absl::holds_alternative(constant)); + ASSERT_TRUE(absl::get(constant)); +} + +TEST(AstTest, ExprConstructionIdent) { + Expr expr(1, Ident("var")); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + ASSERT_EQ(absl::get(expr.expr_kind()).name(), "var"); +} + +TEST(AstTest, ExprConstructionSelect) { + Expr expr(1, Select(std::make_unique(2, Ident("var")), "field")); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind()); + ASSERT_TRUE(absl::holds_alternative(select.operand()->expr_kind())); + ASSERT_EQ(absl::get(select.operand()->expr_kind()).name(), "var"); + ASSERT_EQ(select.field(), "field"); +} + +TEST(AstTest, SelectMutableOperand) { + Select select; + select.mutable_operand().set_expr_kind(Ident("var")); + ASSERT_TRUE(absl::holds_alternative(select.operand()->expr_kind())); + ASSERT_EQ(absl::get(select.operand()->expr_kind()).name(), "var"); +} + +TEST(AstTest, ExprConstructionCall) { + Expr expr(1, Call(std::make_unique(2, Ident("var")), "function", {})); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + const auto& call = absl::get(expr.expr_kind()); + ASSERT_TRUE(absl::holds_alternative(call.target()->expr_kind())); + ASSERT_EQ(absl::get(call.target()->expr_kind()).name(), "var"); + ASSERT_EQ(call.function(), "function"); + ASSERT_TRUE(call.args().empty()); +} + +TEST(AstTest, CallMutableTarget) { + Call call; + call.mutable_target().set_expr_kind(Ident("var")); + ASSERT_TRUE(absl::holds_alternative(call.target()->expr_kind())); + ASSERT_EQ(absl::get(call.target()->expr_kind()).name(), "var"); +} + +TEST(AstTest, ExprConstructionCreateList) { + CreateList create_list; + create_list.mutable_elements().emplace_back(Expr(2, Ident("var1"))); + create_list.mutable_elements().emplace_back(Expr(3, Ident("var2"))); + create_list.mutable_elements().emplace_back(Expr(4, Ident("var3"))); + Expr expr(1, std::move(create_list)); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + const auto& elements = absl::get(expr.expr_kind()).elements(); + ASSERT_EQ(absl::get(elements[0].expr_kind()).name(), "var1"); + ASSERT_EQ(absl::get(elements[1].expr_kind()).name(), "var2"); + ASSERT_EQ(absl::get(elements[2].expr_kind()).name(), "var3"); +} + +TEST(AstTest, ExprConstructionCreateStruct) { + CreateStruct create_struct; + create_struct.set_message_name("name"); + create_struct.mutable_entries().emplace_back(CreateStruct::Entry( + 1, "key1", std::make_unique(2, Ident("value1")))); + create_struct.mutable_entries().emplace_back(CreateStruct::Entry( + 3, "key2", std::make_unique(4, Ident("value2")))); + create_struct.mutable_entries().emplace_back( + CreateStruct::Entry(5, std::make_unique(6, Ident("key3")), + std::make_unique(6, Ident("value3")))); + Expr expr(1, std::move(create_struct)); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + const auto& entries = absl::get(expr.expr_kind()).entries(); + ASSERT_EQ(absl::get(entries[0].key_kind()), "key1"); + ASSERT_EQ(absl::get(entries[0].value()->expr_kind()).name(), "value1"); + ASSERT_EQ(absl::get(entries[1].key_kind()), "key2"); + ASSERT_EQ(absl::get(entries[1].value()->expr_kind()).name(), "value2"); + ASSERT_EQ( + absl::get( + absl::get>(entries[2].key_kind())->expr_kind()) + .name(), + "key3"); + ASSERT_EQ(absl::get(entries[2].value()->expr_kind()).name(), "value3"); +} + +TEST(AstTest, CreateStructEntryMutableValue) { + CreateStruct::Entry entry; + entry.mutable_value().set_expr_kind(Ident("var")); + ASSERT_TRUE(absl::holds_alternative(entry.value()->expr_kind())); + ASSERT_EQ(absl::get(entry.value()->expr_kind()).name(), "var"); +} + +TEST(AstTest, ExprConstructionComprehension) { + Comprehension comprehension; + comprehension.set_iter_var("iter_var"); + comprehension.set_iter_range(std::make_unique(1, Ident("range"))); + comprehension.set_accu_var("accu_var"); + comprehension.set_accu_init(std::make_unique(2, Ident("init"))); + comprehension.set_loop_condition(std::make_unique(3, Ident("cond"))); + comprehension.set_loop_step(std::make_unique(4, Ident("step"))); + comprehension.set_result(std::make_unique(5, Ident("result"))); + Expr expr(6, std::move(comprehension)); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + auto& created_expr = absl::get(expr.expr_kind()); + ASSERT_EQ(created_expr.iter_var(), "iter_var"); + ASSERT_EQ(absl::get(created_expr.iter_range()->expr_kind()).name(), + "range"); + ASSERT_EQ(created_expr.accu_var(), "accu_var"); + ASSERT_EQ(absl::get(created_expr.accu_init()->expr_kind()).name(), + "init"); + ASSERT_EQ(absl::get(created_expr.loop_condition()->expr_kind()).name(), + "cond"); + ASSERT_EQ(absl::get(created_expr.loop_step()->expr_kind()).name(), + "step"); + ASSERT_EQ(absl::get(created_expr.result()->expr_kind()).name(), + "result"); +} + +TEST(AstTest, ComprehensionMutableConstruction) { + Comprehension comprehension; + comprehension.mutable_iter_range().set_expr_kind(Ident("var")); + ASSERT_TRUE( + absl::holds_alternative(comprehension.iter_range()->expr_kind())); + ASSERT_EQ(absl::get(comprehension.iter_range()->expr_kind()).name(), + "var"); + comprehension.mutable_accu_init().set_expr_kind(Ident("var")); + ASSERT_TRUE( + absl::holds_alternative(comprehension.accu_init()->expr_kind())); + ASSERT_EQ(absl::get(comprehension.accu_init()->expr_kind()).name(), + "var"); + comprehension.mutable_loop_condition().set_expr_kind(Ident("var")); + ASSERT_TRUE(absl::holds_alternative( + comprehension.loop_condition()->expr_kind())); + ASSERT_EQ( + absl::get(comprehension.loop_condition()->expr_kind()).name(), + "var"); + comprehension.mutable_loop_step().set_expr_kind(Ident("var")); + ASSERT_TRUE( + absl::holds_alternative(comprehension.loop_step()->expr_kind())); + ASSERT_EQ(absl::get(comprehension.loop_step()->expr_kind()).name(), + "var"); + comprehension.mutable_result().set_expr_kind(Ident("var")); + ASSERT_TRUE( + absl::holds_alternative(comprehension.result()->expr_kind())); + ASSERT_EQ(absl::get(comprehension.result()->expr_kind()).name(), + "var"); +} + +TEST(AstTest, ExprMoveTest) { + Expr expr(1, Ident("var")); + ASSERT_TRUE(absl::holds_alternative(expr.expr_kind())); + ASSERT_EQ(absl::get(expr.expr_kind()).name(), "var"); + Expr new_expr = std::move(expr); + ASSERT_TRUE(absl::holds_alternative(new_expr.expr_kind())); + ASSERT_EQ(absl::get(new_expr.expr_kind()).name(), "var"); +} + +TEST(AstTest, ParsedExpr) { + ParsedExpr parsed_expr; + parsed_expr.set_expr(Expr(1, Ident("name"))); + auto& source_info = parsed_expr.mutable_source_info(); + source_info.set_syntax_version("syntax_version"); + source_info.set_location("location"); + source_info.set_line_offsets({1, 2, 3}); + source_info.set_positions({{1, 1}, {2, 2}}); + ASSERT_TRUE(absl::holds_alternative(parsed_expr.expr().expr_kind())); + ASSERT_EQ(absl::get(parsed_expr.expr().expr_kind()).name(), "name"); + ASSERT_EQ(parsed_expr.source_info().syntax_version(), "syntax_version"); + ASSERT_EQ(parsed_expr.source_info().location(), "location"); + EXPECT_THAT(parsed_expr.source_info().line_offsets(), + testing::UnorderedElementsAre(1, 2, 3)); + EXPECT_THAT( + parsed_expr.source_info().positions(), + testing::UnorderedElementsAre(testing::Pair(1, 1), testing::Pair(2, 2))); +} + +TEST(AstTest, ListTypeMutableConstruction) { + ListType type; + type.mutable_elem_type() = Type(PrimitiveType::kBool); + EXPECT_EQ(absl::get(type.elem_type()->type_kind()), + PrimitiveType::kBool); +} + +TEST(AstTest, MapTypeMutableConstruction) { + MapType type; + type.mutable_key_type() = Type(PrimitiveType::kBool); + type.mutable_value_type() = Type(PrimitiveType::kBool); + EXPECT_EQ(absl::get(type.key_type()->type_kind()), + PrimitiveType::kBool); + EXPECT_EQ(absl::get(type.value_type()->type_kind()), + PrimitiveType::kBool); +} + +TEST(AstTest, FunctionTypeMutableConstruction) { + FunctionType type; + type.mutable_result_type() = Type(PrimitiveType::kBool); + EXPECT_EQ(absl::get(type.result_type()->type_kind()), + PrimitiveType::kBool); +} + +TEST(AstTest, CheckedExpr) { + CheckedExpr checked_expr; + checked_expr.set_expr(Expr(1, Ident("name"))); + auto& source_info = checked_expr.mutable_source_info(); + source_info.set_syntax_version("syntax_version"); + source_info.set_location("location"); + source_info.set_line_offsets({1, 2, 3}); + source_info.set_positions({{1, 1}, {2, 2}}); + checked_expr.set_expr_version("expr_version"); + checked_expr.mutable_type_map().insert( + {1, Type(PrimitiveType(PrimitiveType::kBool))}); + ASSERT_TRUE(absl::holds_alternative(checked_expr.expr().expr_kind())); + ASSERT_EQ(absl::get(checked_expr.expr().expr_kind()).name(), "name"); + ASSERT_EQ(checked_expr.source_info().syntax_version(), "syntax_version"); + ASSERT_EQ(checked_expr.source_info().location(), "location"); + EXPECT_THAT(checked_expr.source_info().line_offsets(), + testing::UnorderedElementsAre(1, 2, 3)); + EXPECT_THAT( + checked_expr.source_info().positions(), + testing::UnorderedElementsAre(testing::Pair(1, 1), testing::Pair(2, 2))); + EXPECT_EQ(checked_expr.expr_version(), "expr_version"); +} + +} // namespace +} // namespace internal +} // namespace ast +} // namespace cel diff --git a/base/ast_utility.cc b/base/ast_utility.cc new file mode 100644 index 000000000..812470d8b --- /dev/null +++ b/base/ast_utility.cc @@ -0,0 +1,506 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/ast_utility.h" + +#include +#include +#include +#include +#include + +#include "google/api/expr/v1alpha1/checked.pb.h" +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "google/protobuf/duration.pb.h" +#include "google/protobuf/timestamp.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/memory/memory.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/time/time.h" +#include "base/ast.h" + +namespace cel::ast::internal { + +absl::StatusOr ToNative(const google::api::expr::v1alpha1::Constant& constant) { + switch (constant.constant_kind_case()) { + case google::api::expr::v1alpha1::Constant::kNullValue: + return NullValue::kNullValue; + case google::api::expr::v1alpha1::Constant::kBoolValue: + return constant.bool_value(); + case google::api::expr::v1alpha1::Constant::kInt64Value: + return constant.int64_value(); + case google::api::expr::v1alpha1::Constant::kUint64Value: + return constant.uint64_value(); + case google::api::expr::v1alpha1::Constant::kDoubleValue: + return constant.double_value(); + case google::api::expr::v1alpha1::Constant::kStringValue: + return constant.string_value(); + case google::api::expr::v1alpha1::Constant::kBytesValue: + return constant.bytes_value(); + case google::api::expr::v1alpha1::Constant::kDurationValue: + return absl::Seconds(constant.duration_value().seconds()) + + absl::Nanoseconds(constant.duration_value().nanos()); + case google::api::expr::v1alpha1::Constant::kTimestampValue: + return absl::FromUnixSeconds(constant.timestamp_value().seconds()) + + absl::Nanoseconds(constant.timestamp_value().nanos()); + default: + return absl::InvalidArgumentError( + "Illegal type supplied for google::api::expr::v1alpha1::Constant."); + } +} + +Ident ToNative(const google::api::expr::v1alpha1::Expr::Ident& ident) { + return Ident(ident.name()); +} + +absl::StatusOr(native_expr->expr_kind())); + auto& native_select = absl::get