Skip to content

Commit 44ec5fd

Browse files
jnthntatumcopybara-github
authored andcommitted
Add homogeneous literal validator.
Ported from the go implementation. PiperOrigin-RevId: 895404316
1 parent e9ba71a commit 44ec5fd

File tree

4 files changed

+408
-0
lines changed

4 files changed

+408
-0
lines changed

validator/BUILD

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,41 @@ cc_library(
9696
],
9797
)
9898

99+
cc_library(
100+
name = "homogeneous_literal_validator",
101+
srcs = ["homogeneous_literal_validator.cc"],
102+
hdrs = ["homogeneous_literal_validator.h"],
103+
deps = [
104+
":validator",
105+
"//common:ast",
106+
"//common:expr",
107+
"//common:navigable_ast",
108+
"@com_google_absl//absl/strings",
109+
],
110+
)
111+
112+
cc_test(
113+
name = "homogeneous_literal_validator_test",
114+
srcs = ["homogeneous_literal_validator_test.cc"],
115+
deps = [
116+
":homogeneous_literal_validator",
117+
":validator",
118+
"//checker:validation_result",
119+
"//common:decl",
120+
"//common:type",
121+
"//compiler",
122+
"//compiler:compiler_factory",
123+
"//compiler:optional",
124+
"//compiler:standard_library",
125+
"//extensions:strings",
126+
"//internal:status_macros",
127+
"//internal:testing",
128+
"//internal:testing_descriptor_pool",
129+
"@com_google_absl//absl/status:statusor",
130+
"@com_google_absl//absl/strings:string_view",
131+
],
132+
)
133+
99134
cc_test(
100135
name = "ast_depth_validator_test",
101136
srcs = ["ast_depth_validator_test.cc"],
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "validator/homogeneous_literal_validator.h"
16+
17+
#include <cstdint>
18+
#include <string>
19+
#include <utility>
20+
#include <vector>
21+
22+
#include "absl/strings/str_cat.h"
23+
#include "absl/strings/string_view.h"
24+
#include "common/ast.h"
25+
#include "common/expr.h"
26+
#include "common/navigable_ast.h"
27+
#include "validator/validator.h"
28+
29+
namespace cel {
30+
31+
namespace {
32+
33+
bool InExemptFunction(const NavigableAstNode& node,
34+
const std::vector<std::string>& exempt_functions) {
35+
const NavigableAstNode* parent = node.parent();
36+
while (parent != nullptr) {
37+
if (parent->node_kind() == NodeKind::kCall) {
38+
absl::string_view fn_name = parent->expr()->call_expr().function();
39+
for (const auto& exempt : exempt_functions) {
40+
if (exempt == fn_name) {
41+
return true;
42+
}
43+
}
44+
}
45+
parent = parent->parent();
46+
}
47+
return false;
48+
}
49+
50+
bool IsOptional(const TypeSpec& t) {
51+
return t.has_abstract_type() && t.abstract_type().name() == "optional_type";
52+
}
53+
54+
const TypeSpec& GetOptionalParameter(const TypeSpec& t) {
55+
return t.abstract_type().parameter_types()[0];
56+
}
57+
58+
void TypeMismatch(ValidationContext& context, int64_t id,
59+
const TypeSpec& expected, const TypeSpec& actual) {
60+
context.ReportErrorAt(
61+
id, absl::StrCat("expected type '", FormatTypeSpec(expected),
62+
"' but found '", FormatTypeSpec(actual), "'"));
63+
}
64+
65+
bool TypeEquiv(const TypeSpec& a, const TypeSpec& b) {
66+
if (a == b) {
67+
return true;
68+
}
69+
70+
if (a.has_error() || b.has_error()) {
71+
// Don't report mismatch if there's an error (type checking failed for the
72+
// expression).
73+
return true;
74+
}
75+
76+
if (a.has_wrapper() && b.has_primitive()) {
77+
return a.wrapper() == b.primitive();
78+
} else if (a.has_primitive() && b.has_wrapper()) {
79+
return a.primitive() == b.wrapper();
80+
}
81+
82+
if (a.has_list_type() && b.has_list_type()) {
83+
return TypeEquiv(a.list_type().elem_type(), b.list_type().elem_type());
84+
}
85+
86+
if (a.has_map_type() && b.has_map_type()) {
87+
return TypeEquiv(a.map_type().key_type(), b.map_type().key_type()) &&
88+
TypeEquiv(a.map_type().value_type(), b.map_type().value_type());
89+
}
90+
91+
if (a.has_abstract_type() && b.has_abstract_type() &&
92+
a.abstract_type().name() == b.abstract_type().name() &&
93+
a.abstract_type().parameter_types().size() ==
94+
b.abstract_type().parameter_types().size()) {
95+
for (int i = 0; i < a.abstract_type().parameter_types().size(); ++i) {
96+
if (!TypeEquiv(a.abstract_type().parameter_types()[i],
97+
b.abstract_type().parameter_types()[i])) {
98+
return false;
99+
}
100+
}
101+
return true;
102+
}
103+
104+
return false;
105+
}
106+
107+
} // namespace
108+
109+
Validation HomogeneousLiteralValidator(
110+
std::vector<std::string> exempt_functions) {
111+
return Validation([exempt_functions = std::move(exempt_functions)](
112+
ValidationContext& context) -> bool {
113+
bool valid = true;
114+
for (const auto& node :
115+
context.navigable_ast().Root().DescendantsPostorder()) {
116+
if (node.node_kind() == NodeKind::kList) {
117+
if (InExemptFunction(node, exempt_functions)) {
118+
continue;
119+
}
120+
const auto& list_expr = node.expr()->list_expr();
121+
const auto& elements = list_expr.elements();
122+
const TypeSpec* expected_type = nullptr;
123+
124+
for (const auto& element : elements) {
125+
int64_t id = element.expr().id();
126+
const TypeSpec& actual_type = context.ast().GetTypeOrDyn(id);
127+
const TypeSpec* type_to_check = &actual_type;
128+
129+
if (element.optional() && IsOptional(actual_type)) {
130+
type_to_check = &GetOptionalParameter(actual_type);
131+
}
132+
133+
if (expected_type == nullptr) {
134+
expected_type = type_to_check;
135+
continue;
136+
}
137+
138+
if (!(TypeEquiv(*expected_type, *type_to_check))) {
139+
TypeMismatch(context, id, *expected_type, *type_to_check);
140+
valid = false;
141+
break;
142+
}
143+
}
144+
} else if (node.node_kind() == NodeKind::kMap) {
145+
if (InExemptFunction(node, exempt_functions)) {
146+
continue;
147+
}
148+
const auto& map_expr = node.expr()->map_expr();
149+
const auto& entries = map_expr.entries();
150+
const TypeSpec* expected_key_type = nullptr;
151+
const TypeSpec* expected_value_type = nullptr;
152+
153+
for (const auto& entry : entries) {
154+
int64_t key_id = entry.key().id();
155+
int64_t val_id = entry.value().id();
156+
const TypeSpec& actual_key_type = context.ast().GetTypeOrDyn(key_id);
157+
const TypeSpec& actual_val_type = context.ast().GetTypeOrDyn(val_id);
158+
const TypeSpec* key_type_to_check = &actual_key_type;
159+
const TypeSpec* val_type_to_check = &actual_val_type;
160+
161+
if (entry.optional() && IsOptional(actual_val_type)) {
162+
val_type_to_check = &GetOptionalParameter(actual_val_type);
163+
}
164+
165+
if (expected_key_type == nullptr) {
166+
expected_key_type = key_type_to_check;
167+
expected_value_type = val_type_to_check;
168+
continue;
169+
}
170+
171+
if (!(TypeEquiv(*expected_key_type, *key_type_to_check))) {
172+
TypeMismatch(context, key_id, *expected_key_type,
173+
*key_type_to_check);
174+
valid = false;
175+
break;
176+
}
177+
if (!(TypeEquiv(*expected_value_type, *val_type_to_check))) {
178+
TypeMismatch(context, val_id, *expected_value_type,
179+
*val_type_to_check);
180+
valid = false;
181+
break;
182+
}
183+
}
184+
}
185+
}
186+
return valid;
187+
});
188+
}
189+
190+
} // namespace cel
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef THIRD_PARTY_CEL_CPP_VALIDATOR_HOMOGENEOUS_LITERAL_VALIDATOR_H_
16+
#define THIRD_PARTY_CEL_CPP_VALIDATOR_HOMOGENEOUS_LITERAL_VALIDATOR_H_
17+
18+
#include <string>
19+
#include <vector>
20+
21+
#include "validator/validator.h"
22+
23+
namespace cel {
24+
25+
// Returns a `Validation` that checks that all literals in map or list literals
26+
// are the same type. If the list or map is part of an argument to an exempted
27+
// function, it is not checked.
28+
Validation HomogeneousLiteralValidator(
29+
std::vector<std::string> exempt_functions);
30+
31+
inline Validation HomogeneousLiteralValidator() {
32+
// Default to exempting the strings extension "format" function.
33+
return HomogeneousLiteralValidator({"format"});
34+
}
35+
36+
} // namespace cel
37+
38+
#endif // THIRD_PARTY_CEL_CPP_VALIDATOR_HOMOGENEOUS_LITERAL_VALIDATOR_H_

0 commit comments

Comments
 (0)