From 9aa8bbf1148d7c51671b1ea359407086f57484fb Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Thu, 26 Mar 2026 15:54:53 -0700 Subject: [PATCH] Expose TypeInfoToType and NewCompilerBuilder APIs PiperOrigin-RevId: 890090916 --- env/BUILD | 39 ++++++--- env/env.cc | 168 ++++----------------------------------- env/env.h | 5 ++ env/env_test.cc | 112 -------------------------- env/env_yaml.cc | 5 +- env/env_yaml_test.cc | 6 +- env/type_info.cc | 178 ++++++++++++++++++++++++++++++++++++++++++ env/type_info.h | 35 +++++++++ env/type_info_test.cc | 127 ++++++++++++++++++++++++++++++ 9 files changed, 397 insertions(+), 278 deletions(-) create mode 100644 env/type_info.cc create mode 100644 env/type_info.h create mode 100644 env/type_info_test.cc diff --git a/env/BUILD b/env/BUILD index f5ce35557..8d477cc1f 100644 --- a/env/BUILD +++ b/env/BUILD @@ -19,17 +19,28 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "config", - srcs = ["config.cc"], - hdrs = ["config.h"], + srcs = [ + "config.cc", + "type_info.cc", + ], + hdrs = [ + "config.h", + "type_info.h", + ], deps = [ "//common:constant", + "//common:type", + "//common:type_kind", "//internal:status_macros", + "@com_google_absl//absl/base:no_destructor", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/functional:overload", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", - "@com_google_absl//absl/types:variant", + "@com_google_protobuf//:protobuf", ], ) @@ -43,23 +54,16 @@ cc_library( "//common:constant", "//common:decl", "//common:type", - "//common:type_kind", "//compiler", "//compiler:compiler_factory", "//compiler:standard_library", "//env/internal:ext_registry", "//internal:status_macros", "//parser:macro", - "@com_google_absl//absl/base:no_destructor", "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/functional:any_invocable", - "@com_google_absl//absl/functional:overload", - "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - "@com_google_absl//absl/types:variant", "@com_google_protobuf//:protobuf", ], ) @@ -163,6 +167,20 @@ cc_test( ], ) +cc_test( + name = "type_info_test", + srcs = ["type_info_test.cc"], + deps = [ + ":config", + "//common:type", + "//common:type_proto", + "//internal:proto_matchers", + "//internal:testing", + "//internal:testing_descriptor_pool", + "@com_google_protobuf//:protobuf", + ], +) + cc_test( name = "env_test", srcs = ["env_test.cc"], @@ -173,7 +191,6 @@ cc_test( "//checker:type_checker_builder", "//checker:validation_result", "//common:ast", - "//common:ast_proto", "//common:constant", "//common:decl", "//common:expr", diff --git a/env/env.cc b/env/env.cc index 2c2555f14..5a4198497 100644 --- a/env/env.cc +++ b/env/env.cc @@ -15,12 +15,10 @@ #include "env/env.h" #include -#include #include #include #include -#include "absl/base/no_destructor.h" #include "absl/container/flat_hash_map.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" @@ -28,11 +26,11 @@ #include "common/constant.h" #include "common/decl.h" #include "common/type.h" -#include "common/type_kind.h" #include "compiler/compiler.h" #include "compiler/compiler_factory.h" #include "compiler/standard_library.h" #include "env/config.h" +#include "env/type_info.h" #include "internal/status_macros.h" #include "parser/macro.h" #include "google/protobuf/arena.h" @@ -95,149 +93,6 @@ absl::StatusOr MakeStdlibSubset( return subset; } -std::optional TypeNameToTypeKind(absl::string_view type_name) { - // Excluded types: - // kUnknown - // kError - // kTypeParam - // kFunction - // kEnum - - static const absl::NoDestructor< - absl::flat_hash_map> - kTypeNameToTypeKind({ - {"null", TypeKind::kNull}, - {"bool", TypeKind::kBool}, - {"int", TypeKind::kInt}, - {"uint", TypeKind::kUint}, - {"double", TypeKind::kDouble}, - {"string", TypeKind::kString}, - {"bytes", TypeKind::kBytes}, - {"timestamp", TypeKind::kTimestamp}, - {TimestampType::kName, TypeKind::kTimestamp}, - {"duration", TypeKind::kDuration}, - {DurationType::kName, TypeKind::kDuration}, - {"list", TypeKind::kList}, - {"map", TypeKind::kMap}, - {"", TypeKind::kDyn}, - {"any", TypeKind::kAny}, - {"dyn", TypeKind::kDyn}, - {BoolWrapperType::kName, TypeKind::kBoolWrapper}, - {IntWrapperType::kName, TypeKind::kIntWrapper}, - {UintWrapperType::kName, TypeKind::kUintWrapper}, - {DoubleWrapperType::kName, TypeKind::kDoubleWrapper}, - {StringWrapperType::kName, TypeKind::kStringWrapper}, - {BytesWrapperType::kName, TypeKind::kBytesWrapper}, - {"type", TypeKind::kType}, - }); - if (auto it = kTypeNameToTypeKind->find(type_name); - it != kTypeNameToTypeKind->end()) { - return it->second; - } - - return std::nullopt; -} - -absl::StatusOr TypeInfoToType( - const Config::TypeInfo& type_info, google::protobuf::Arena* arena, - const google::protobuf::DescriptorPool* descriptor_pool) { - if (type_info.is_type_param) { - return TypeParamType(type_info.name); - } - - std::optional type_kind = TypeNameToTypeKind(type_info.name); - if (!type_kind.has_value()) { - if (type_info.params.empty() && descriptor_pool != nullptr) { - const google::protobuf::Descriptor* type = - descriptor_pool->FindMessageTypeByName(type_info.name); - if (type != nullptr) { - return MessageType(type); - } - } - // TODO(uncreated-issue/88): use a TypeIntrospector to validate opaque types - std::vector parameter_types; - for (const Config::TypeInfo& param : type_info.params) { - CEL_ASSIGN_OR_RETURN(Type parameter_type, - TypeInfoToType(param, arena, descriptor_pool)); - parameter_types.push_back(parameter_type); - } - - return OpaqueType(arena, type_info.name, parameter_types); - } - - switch (*type_kind) { - case TypeKind::kNull: - return NullType(); - case TypeKind::kBool: - return BoolType(); - case TypeKind::kInt: - return IntType(); - case TypeKind::kUint: - return UintType(); - case TypeKind::kDouble: - return DoubleType(); - case TypeKind::kString: - return StringType(); - case TypeKind::kBytes: - return BytesType(); - case TypeKind::kDuration: - return DurationType(); - case TypeKind::kTimestamp: - return TimestampType(); - case TypeKind::kList: { - Type element_type; - if (!type_info.params.empty()) { - CEL_ASSIGN_OR_RETURN( - element_type, - TypeInfoToType(type_info.params[0], arena, descriptor_pool)); - } else { - element_type = DynType(); - } - return ListType(arena, element_type); - } - case TypeKind::kMap: { - Type key_type = DynType(); - Type value_type = DynType(); - if (!type_info.params.empty()) { - CEL_ASSIGN_OR_RETURN(key_type, TypeInfoToType(type_info.params[0], - arena, descriptor_pool)); - } - if (type_info.params.size() > 1) { - CEL_ASSIGN_OR_RETURN( - value_type, - TypeInfoToType(type_info.params[1], arena, descriptor_pool)); - } - return MapType(arena, key_type, value_type); - } - case TypeKind::kDyn: - return DynType(); - case TypeKind::kAny: - return AnyType(); - case TypeKind::kBoolWrapper: - return BoolWrapperType(); - case TypeKind::kIntWrapper: - return IntWrapperType(); - case TypeKind::kUintWrapper: - return UintWrapperType(); - case TypeKind::kDoubleWrapper: - return DoubleWrapperType(); - case TypeKind::kStringWrapper: - return StringWrapperType(); - case TypeKind::kBytesWrapper: - return BytesWrapperType(); - case TypeKind::kType: { - if (type_info.params.empty()) { - return TypeType(arena, DynType()); - } - CEL_ASSIGN_OR_RETURN(Type type, TypeInfoToType(type_info.params[0], arena, - descriptor_pool)); - return TypeType(arena, type); - } - default: - return DynType(); - } -} - absl::StatusOr FunctionConfigToFunctionDecl( const Config::FunctionConfig& function_config, google::protobuf::Arena* arena, const google::protobuf::DescriptorPool* descriptor_pool) { @@ -250,12 +105,12 @@ absl::StatusOr FunctionConfigToFunctionDecl( overload_decl.set_member(overload_config.is_member_function); for (const Config::TypeInfo& parameter : overload_config.parameters) { CEL_ASSIGN_OR_RETURN(Type parameter_type, - TypeInfoToType(parameter, arena, descriptor_pool)); + TypeInfoToType(parameter, descriptor_pool, arena)); overload_decl.mutable_args().push_back(parameter_type); } CEL_ASSIGN_OR_RETURN( Type return_type, - TypeInfoToType(overload_config.return_type, arena, descriptor_pool)); + TypeInfoToType(overload_config.return_type, descriptor_pool, arena)); overload_decl.set_result(return_type); CEL_RETURN_IF_ERROR(function_decl.AddOverload(overload_decl)); } @@ -264,7 +119,11 @@ absl::StatusOr FunctionConfigToFunctionDecl( } // namespace -absl::StatusOr> Env::NewCompiler() { +Env::Env() { + compiler_options_.parser_options.enable_quoted_identifiers = true; +} + +absl::StatusOr> Env::NewCompilerBuilder() { CEL_ASSIGN_OR_RETURN( std::unique_ptr compiler_builder, cel::NewCompilerBuilder(descriptor_pool_, compiler_options_)); @@ -295,8 +154,8 @@ absl::StatusOr> Env::NewCompiler() { VariableDecl variable_decl; variable_decl.set_name(variable_config.name); CEL_ASSIGN_OR_RETURN(Type type, - TypeInfoToType(variable_config.type_info, arena, - descriptor_pool_.get())); + TypeInfoToType(variable_config.type_info, + descriptor_pool_.get(), arena)); variable_decl.set_type(type); if (variable_config.value.has_value()) { variable_decl.set_value(variable_config.value); @@ -312,7 +171,12 @@ absl::StatusOr> Env::NewCompiler() { CEL_RETURN_IF_ERROR(checker_builder.AddFunction(function_decl)); } - return compiler_builder->Build(); + return compiler_builder; } +absl::StatusOr> Env::NewCompiler() { + CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler_builder, + NewCompilerBuilder()); + return compiler_builder->Build(); +} } // namespace cel diff --git a/env/env.h b/env/env.h index f46e5947c..9830b67d7 100644 --- a/env/env.h +++ b/env/env.h @@ -36,6 +36,8 @@ namespace cel { // customizable CEL features. class Env { public: + Env(); + // Registers a `CompilerLibrary` with the environment. Note that the library // does not automatically get added to a `Compiler`. `NewCompiler` relies // on `Config` to determine which libraries to load. @@ -57,6 +59,9 @@ class Env { void SetConfig(const Config& config) { config_ = config; } + absl::StatusOr> NewCompilerBuilder(); + + // Shortcut for NewCompilerBuilder() followed by Build(). absl::StatusOr> NewCompiler(); private: diff --git a/env/env_test.cc b/env/env_test.cc index dcd2d97fa..076eb57bc 100644 --- a/env/env_test.cc +++ b/env/env_test.cc @@ -30,7 +30,6 @@ #include "checker/type_checker_builder.h" #include "checker/validation_result.h" #include "common/ast.h" -#include "common/ast_proto.h" #include "common/constant.h" #include "common/decl.h" #include "common/expr.h" @@ -38,7 +37,6 @@ #include "common/value.h" #include "compiler/compiler.h" #include "env/config.h" -#include "internal/proto_matchers.h" #include "internal/status_macros.h" #include "internal/testing.h" #include "internal/testing_descriptor_pool.h" @@ -52,16 +50,13 @@ #include "runtime/runtime_options.h" #include "runtime/standard_runtime_builder_factory.h" #include "google/protobuf/arena.h" -#include "google/protobuf/text_format.h" namespace cel { namespace { using ::absl_testing::IsOk; -using ::cel::internal::test::EqualsProto; using ::testing::HasSubstr; using ::testing::IsEmpty; -using ::testing::NotNull; using ::testing::Property; using ::testing::UnorderedElementsAre; using ::testing::Values; @@ -319,113 +314,6 @@ TEST(ContainerConfigTest, ContainerConfig) { EXPECT_THAT(result.GetIssues(), IsEmpty()) << result.FormatError(); } -struct TypeInfoTestCase { - Config::TypeInfo type_info; - std::string expected_type_pb; -}; - -using TypeInfoTest = testing::TestWithParam; - -TEST_P(TypeInfoTest, TypeInfo) { - const TypeInfoTestCase& param = GetParam(); - cel::expr::Type expected_type_pb; - ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(param.expected_type_pb, - &expected_type_pb)); - - Env env; - env.SetDescriptorPool(internal::GetSharedTestingDescriptorPool()); - Config config; - Config::VariableConfig variable_config; - variable_config.name = "test"; - variable_config.type_info = param.type_info; - ASSERT_THAT(config.AddVariableConfig(variable_config), IsOk()); - env.SetConfig(config); - - ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, env.NewCompiler()); - ASSERT_THAT(compiler, NotNull()); - ASSERT_OK_AND_ASSIGN(ValidationResult result, compiler->Compile("test")); - EXPECT_THAT(result.GetIssues(), IsEmpty()) - << " error: " << result.FormatError(); - - // Obtain the inferred return type of the expression `test`. - const Ast* ast = result.GetAst(); - ASSERT_THAT(ast, NotNull()); - cel::expr::CheckedExpr checked_expr; - ASSERT_THAT(cel::AstToCheckedExpr(*ast, &checked_expr), IsOk()); - auto it = checked_expr.type_map().find(checked_expr.expr().id()); - ASSERT_NE(it, checked_expr.type_map().end()); - - cel::expr::Type actual_type_pb = it->second; - EXPECT_THAT(actual_type_pb, EqualsProto(expected_type_pb)); -} - -std::vector GetTypeInfoTestCases() { - return { - TypeInfoTestCase{ - .type_info = {.name = "int"}, - .expected_type_pb = "primitive: INT64", - }, - TypeInfoTestCase{ - .type_info = {.name = "list", - .params = {Config::TypeInfo{.name = "int"}}}, - .expected_type_pb = "list_type { elem_type { primitive: INT64 } }", - }, - TypeInfoTestCase{ - .type_info = {.name = "list"}, - .expected_type_pb = "list_type { elem_type { dyn {} }}", - }, - TypeInfoTestCase{ - .type_info = {.name = "map", - .params = {Config::TypeInfo{.name = "string"}, - Config::TypeInfo{.name = "int"}}}, - .expected_type_pb = "map_type { key_type { primitive: STRING } " - "value_type { primitive: INT64 }}", - }, - TypeInfoTestCase{ - .type_info = {.name = "cel.expr.conformance.proto2.TestAllTypes"}, - .expected_type_pb = - "message_type: 'cel.expr.conformance.proto2.TestAllTypes'", - }, - TypeInfoTestCase{ - .type_info = {.name = "A", - .params = {Config::TypeInfo{.name = "B", - .is_type_param = true}}}, - // TypeParam is replaced with dyn by the type checker. - .expected_type_pb = - "abstract_type { name: 'A' parameter_types { dyn {} } }", - }, - TypeInfoTestCase{ - .type_info = {.name = "any"}, - .expected_type_pb = "well_known: ANY", - }, - TypeInfoTestCase{ - .type_info = {.name = "timestamp"}, - .expected_type_pb = "well_known: TIMESTAMP", - }, - TypeInfoTestCase{ - .type_info = {.name = "google.protobuf.DoubleValue"}, - .expected_type_pb = "wrapper: DOUBLE", - }, - TypeInfoTestCase{ - .type_info = {.name = "type", - .params = {Config::TypeInfo{.name = "duration"}}}, - .expected_type_pb = "type: { well_known: DURATION }", - }, - TypeInfoTestCase{ - .type_info = {.name = "parameterized", - .params = {{.name = "A", .is_type_param = true}, - {.name = "double"}}}, - // TypeParam is replaced with dyn by the type checker. - .expected_type_pb = "abstract_type { name: 'parameterized' " - "parameter_types { dyn {} } " - "parameter_types { primitive: DOUBLE } }", - }, - }; -} - -INSTANTIATE_TEST_SUITE_P(VariableConfigTest, TypeInfoTest, - ValuesIn(GetTypeInfoTestCases())); - struct VariableConfigWithValueTestCase { Config::VariableConfig variable_config; std::string validate_type_expr; diff --git a/env/env_yaml.cc b/env/env_yaml.cc index 5e7c9631d..a6f66bd83 100644 --- a/env/env_yaml.cc +++ b/env/env_yaml.cc @@ -552,10 +552,13 @@ absl::Status ParseVariableConfigs(Config& config, absl::string_view yaml, variable_config.type_info = type_info; - if (constant_kind_case != ConstantKindCase::kUnspecified) { + if (constant_kind_case != ConstantKindCase::kUnspecified && + !value_str.empty()) { CEL_ASSIGN_OR_RETURN( variable_config.value, ParseConstantValue(yaml, value, constant_kind_case, value_str)); + } else if (constant_kind_case == ConstantKindCase::kNull) { + variable_config.value = Constant(nullptr); } CEL_RETURN_IF_ERROR(config.AddVariableConfig(variable_config)); diff --git a/env/env_yaml_test.cc b/env/env_yaml_test.cc index b34c25254..c3e4839af 100644 --- a/env/env_yaml_test.cc +++ b/env/env_yaml_test.cc @@ -226,8 +226,10 @@ TEST_P(EnvYamlParseConstantTest, EnvYamlParseConstant) { const Config::VariableConfig& variable_config = config.GetVariableConfigs()[0]; EXPECT_EQ(variable_config.name, "const"); - EXPECT_EQ(variable_config.type_info.name, param.type_name); - EXPECT_EQ(variable_config.value, param.expected_constant); + EXPECT_EQ(variable_config.type_info.name, param.type_name) + << " yaml: " << yaml; + EXPECT_EQ(variable_config.value, param.expected_constant) + << " yaml: " << yaml; } std::vector GetParseConstantTestCases() { diff --git a/env/type_info.cc b/env/type_info.cc new file mode 100644 index 000000000..ed72a842f --- /dev/null +++ b/env/type_info.cc @@ -0,0 +1,178 @@ +// Copyright 2026 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 "env/type_info.h" + +#include +#include + +#include "absl/base/no_destructor.h" +#include "absl/container/flat_hash_map.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "common/type.h" +#include "common/type_kind.h" +#include "env/config.h" +#include "internal/status_macros.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" + +namespace cel { +namespace { + +std::optional TypeNameToTypeKind(absl::string_view type_name) { + // Excluded types: + // kUnknown + // kError + // kTypeParam + // kFunction + // kEnum + + static const absl::NoDestructor< + absl::flat_hash_map> + kTypeNameToTypeKind({ + {"null", TypeKind::kNull}, + {"bool", TypeKind::kBool}, + {"int", TypeKind::kInt}, + {"uint", TypeKind::kUint}, + {"double", TypeKind::kDouble}, + {"string", TypeKind::kString}, + {"bytes", TypeKind::kBytes}, + {"timestamp", TypeKind::kTimestamp}, + {TimestampType::kName, TypeKind::kTimestamp}, + {"duration", TypeKind::kDuration}, + {DurationType::kName, TypeKind::kDuration}, + {"list", TypeKind::kList}, + {"map", TypeKind::kMap}, + {"", TypeKind::kDyn}, + {"any", TypeKind::kAny}, + {"dyn", TypeKind::kDyn}, + {BoolWrapperType::kName, TypeKind::kBoolWrapper}, + {IntWrapperType::kName, TypeKind::kIntWrapper}, + {UintWrapperType::kName, TypeKind::kUintWrapper}, + {DoubleWrapperType::kName, TypeKind::kDoubleWrapper}, + {StringWrapperType::kName, TypeKind::kStringWrapper}, + {BytesWrapperType::kName, TypeKind::kBytesWrapper}, + {"type", TypeKind::kType}, + }); + if (auto it = kTypeNameToTypeKind->find(type_name); + it != kTypeNameToTypeKind->end()) { + return it->second; + } + + return std::nullopt; +} +} // namespace + +absl::StatusOr TypeInfoToType( + const Config::TypeInfo& type_info, + const google::protobuf::DescriptorPool* descriptor_pool, google::protobuf::Arena* arena) { + if (type_info.is_type_param) { + return TypeParamType(type_info.name); + } + + std::optional type_kind = TypeNameToTypeKind(type_info.name); + if (!type_kind.has_value()) { + if (type_info.params.empty() && descriptor_pool != nullptr) { + const google::protobuf::Descriptor* type = + descriptor_pool->FindMessageTypeByName(type_info.name); + if (type != nullptr) { + return Type::Message(type); + } + } + // TODO(uncreated-issue/88): use a TypeIntrospector to validate opaque types + std::vector parameter_types; + for (const Config::TypeInfo& param : type_info.params) { + CEL_ASSIGN_OR_RETURN(Type parameter_type, + TypeInfoToType(param, descriptor_pool, arena)); + parameter_types.push_back(parameter_type); + } + + return OpaqueType(arena, type_info.name, parameter_types); + } + + switch (*type_kind) { + case TypeKind::kNull: + return NullType(); + case TypeKind::kBool: + return BoolType(); + case TypeKind::kInt: + return IntType(); + case TypeKind::kUint: + return UintType(); + case TypeKind::kDouble: + return DoubleType(); + case TypeKind::kString: + return StringType(); + case TypeKind::kBytes: + return BytesType(); + case TypeKind::kDuration: + return DurationType(); + case TypeKind::kTimestamp: + return TimestampType(); + case TypeKind::kList: { + Type element_type; + if (!type_info.params.empty()) { + CEL_ASSIGN_OR_RETURN( + element_type, + TypeInfoToType(type_info.params[0], descriptor_pool, arena)); + } else { + element_type = DynType(); + } + return ListType(arena, element_type); + } + case TypeKind::kMap: { + Type key_type = DynType(); + Type value_type = DynType(); + if (!type_info.params.empty()) { + CEL_ASSIGN_OR_RETURN(key_type, TypeInfoToType(type_info.params[0], + descriptor_pool, arena)); + } + if (type_info.params.size() > 1) { + CEL_ASSIGN_OR_RETURN( + value_type, + TypeInfoToType(type_info.params[1], descriptor_pool, arena)); + } + return MapType(arena, key_type, value_type); + } + case TypeKind::kDyn: + return DynType(); + case TypeKind::kAny: + return AnyType(); + case TypeKind::kBoolWrapper: + return BoolWrapperType(); + case TypeKind::kIntWrapper: + return IntWrapperType(); + case TypeKind::kUintWrapper: + return UintWrapperType(); + case TypeKind::kDoubleWrapper: + return DoubleWrapperType(); + case TypeKind::kStringWrapper: + return StringWrapperType(); + case TypeKind::kBytesWrapper: + return BytesWrapperType(); + case TypeKind::kType: { + if (type_info.params.empty()) { + return TypeType(arena, DynType()); + } + CEL_ASSIGN_OR_RETURN(Type type, TypeInfoToType(type_info.params[0], + descriptor_pool, arena)); + return TypeType(arena, type); + } + default: + return DynType(); + } +} + +} // namespace cel diff --git a/env/type_info.h b/env/type_info.h new file mode 100644 index 000000000..bb3cfde43 --- /dev/null +++ b/env/type_info.h @@ -0,0 +1,35 @@ +// Copyright 2026 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_ENV_TYPE_INFO_H_ +#define THIRD_PARTY_CEL_CPP_ENV_TYPE_INFO_H_ + +#include "absl/status/statusor.h" +#include "common/type.h" +#include "env/config.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" + +namespace cel { + +// Converts a Config::TypeInfo to a cel::Type. Returns an error if the type_info +// cannot be converted to a known cel::Type, a list configured with more than +// one parameter. +absl::StatusOr TypeInfoToType( + const Config::TypeInfo& type_info, + const google::protobuf::DescriptorPool* descriptor_pool, google::protobuf::Arena* arena); + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_ENV_TYPE_INFO_H_ diff --git a/env/type_info_test.cc b/env/type_info_test.cc new file mode 100644 index 000000000..ca9d0467c --- /dev/null +++ b/env/type_info_test.cc @@ -0,0 +1,127 @@ +// Copyright 2026 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 "env/type_info.h" + +#include +#include + +#include "common/type.h" +#include "common/type_proto.h" +#include "env/config.h" +#include "internal/proto_matchers.h" +#include "internal/testing.h" +#include "internal/testing_descriptor_pool.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/text_format.h" + +namespace cel { +namespace { + +using absl_testing::IsOk; +using testing::ValuesIn; + +struct TestCase { + Config::TypeInfo type_info; + std::string expected_type_pb; +}; + +using TypeInfoTest = testing::TestWithParam; + +TEST_P(TypeInfoTest, TypeInfo) { + const TestCase& param = GetParam(); + cel::expr::Type expected_type_pb; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(param.expected_type_pb, + &expected_type_pb)); + + google::protobuf::Arena arena; + const google::protobuf::DescriptorPool* descriptor_pool = + cel::internal::GetTestingDescriptorPool(); + ASSERT_OK_AND_ASSIGN( + cel::Type actual_type, + cel::TypeInfoToType(param.type_info, descriptor_pool, &arena)); + + cel::expr::Type actual_type_pb; + ASSERT_THAT(cel::TypeToProto(actual_type, &actual_type_pb), IsOk()); + EXPECT_THAT(actual_type_pb, + cel::internal::test::EqualsProto(expected_type_pb)); +} + +std::vector GetTestCases() { + return { + TestCase{ + .type_info = {.name = "int"}, + .expected_type_pb = "primitive: INT64", + }, + TestCase{ + .type_info = {.name = "list", + .params = {Config::TypeInfo{.name = "int"}}}, + .expected_type_pb = "list_type { elem_type { primitive: INT64 } }", + }, + TestCase{ + .type_info = {.name = "list"}, + .expected_type_pb = "list_type { elem_type { dyn {} }}", + }, + TestCase{ + .type_info = {.name = "map", + .params = {Config::TypeInfo{.name = "string"}, + Config::TypeInfo{.name = "int"}}}, + .expected_type_pb = "map_type { key_type { primitive: STRING } " + "value_type { primitive: INT64 }}", + }, + TestCase{ + .type_info = {.name = "cel.expr.conformance.proto2.TestAllTypes"}, + .expected_type_pb = + "message_type: 'cel.expr.conformance.proto2.TestAllTypes'", + }, + TestCase{ + .type_info = {.name = "A", + .params = {Config::TypeInfo{.name = "B", + .is_type_param = true}}}, + .expected_type_pb = + "abstract_type { name: 'A' parameter_types { type_param: 'B' } }", + }, + TestCase{ + .type_info = {.name = "any"}, + .expected_type_pb = "well_known: ANY", + }, + TestCase{ + .type_info = {.name = "timestamp"}, + .expected_type_pb = "well_known: TIMESTAMP", + }, + TestCase{ + .type_info = {.name = "google.protobuf.DoubleValue"}, + .expected_type_pb = "wrapper: DOUBLE", + }, + TestCase{ + .type_info = {.name = "type", + .params = {Config::TypeInfo{.name = "duration"}}}, + .expected_type_pb = "type: { well_known: DURATION }", + }, + TestCase{ + .type_info = {.name = "parameterized", + .params = {{.name = "A", .is_type_param = true}, + {.name = "double"}}}, + .expected_type_pb = "abstract_type { name: 'parameterized' " + "parameter_types { type_param: 'A' } " + "parameter_types { primitive: DOUBLE } }", + }, + }; +} + +INSTANTIATE_TEST_SUITE_P(TypeInfoTest, TypeInfoTest, ValuesIn(GetTestCases())); + +} // namespace +} // namespace cel