Add Match/Overload abstractions (#26640)

* match/overload abstraction

* update projects

* match should really not accept mutable args

* typo

* tests

* usage comment

* mutable version

* build stuff

* clang-format

* add an escape hatch to avoid needing port_platform.h in files that do not need port_platform.h

* unused args

* Make it possible for a test to not depend on gpr

* add tests

* compile fix

* sepelling
pull/26659/head
Craig Tiller 3 years ago committed by GitHub
parent 994ee5da0c
commit 0bd70a7e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      BUILD
  2. 71
      CMakeLists.txt
  3. 22
      build_autogenerated.yaml
  4. 72
      src/core/lib/gprpp/match.h
  5. 59
      src/core/lib/gprpp/overload.h
  6. 24
      test/core/gprpp/BUILD
  7. 75
      test/core/gprpp/match_test.cc
  8. 36
      test/core/gprpp/overload_test.cc
  9. 15
      test/core/util/BUILD
  10. 48
      tools/run_tests/generated/tests.json
  11. 2
      tools/run_tests/sanity/check_port_platform.py

16
BUILD

@ -745,6 +745,22 @@ grpc_cc_library(
visibility = ["@grpc:debug_location"], visibility = ["@grpc:debug_location"],
) )
grpc_cc_library(
name = "overload",
language = "c++",
public_hdrs = ["src/core/lib/gprpp/overload.h"],
)
grpc_cc_library(
name = "match",
external_deps = [
"absl/types:variant",
],
language = "c++",
public_hdrs = ["src/core/lib/gprpp/match.h"],
deps = ["overload"],
)
grpc_cc_library( grpc_cc_library(
name = "orphanable", name = "orphanable",
language = "c++", language = "c++",

@ -896,6 +896,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx lb_load_data_store_test) add_dependencies(buildtests_cxx lb_load_data_store_test)
add_dependencies(buildtests_cxx linux_system_roots_test) add_dependencies(buildtests_cxx linux_system_roots_test)
add_dependencies(buildtests_cxx log_test) add_dependencies(buildtests_cxx log_test)
add_dependencies(buildtests_cxx match_test)
add_dependencies(buildtests_cxx matchers_test) add_dependencies(buildtests_cxx matchers_test)
add_dependencies(buildtests_cxx message_allocator_end2end_test) add_dependencies(buildtests_cxx message_allocator_end2end_test)
add_dependencies(buildtests_cxx mock_stream_test) add_dependencies(buildtests_cxx mock_stream_test)
@ -904,6 +905,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx noop-benchmark) add_dependencies(buildtests_cxx noop-benchmark)
add_dependencies(buildtests_cxx orphanable_test) add_dependencies(buildtests_cxx orphanable_test)
add_dependencies(buildtests_cxx out_of_bounds_bad_client_test) add_dependencies(buildtests_cxx out_of_bounds_bad_client_test)
add_dependencies(buildtests_cxx overload_test)
add_dependencies(buildtests_cxx pid_controller_test) add_dependencies(buildtests_cxx pid_controller_test)
add_dependencies(buildtests_cxx port_sharing_end2end_test) add_dependencies(buildtests_cxx port_sharing_end2end_test)
add_dependencies(buildtests_cxx proto_server_reflection_test) add_dependencies(buildtests_cxx proto_server_reflection_test)
@ -12246,6 +12248,41 @@ target_link_libraries(log_test
) )
endif()
if(gRPC_BUILD_TESTS)
add_executable(match_test
test/core/gprpp/match_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(match_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(match_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
absl::variant
)
endif() endif()
if(gRPC_BUILD_TESTS) if(gRPC_BUILD_TESTS)
@ -12584,6 +12621,40 @@ target_link_libraries(out_of_bounds_bad_client_test
) )
endif()
if(gRPC_BUILD_TESTS)
add_executable(overload_test
test/core/gprpp/overload_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(overload_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(overload_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
)
endif() endif()
if(gRPC_BUILD_TESTS) if(gRPC_BUILD_TESTS)

@ -5757,6 +5757,18 @@ targets:
deps: deps:
- grpc_test_util - grpc_test_util
uses_polling: false uses_polling: false
- name: match_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/gprpp/match.h
- src/core/lib/gprpp/overload.h
src:
- test/core/gprpp/match_test.cc
deps:
- absl/types:variant
uses_polling: false
- name: matchers_test - name: matchers_test
gtest: true gtest: true
build: test build: test
@ -5880,6 +5892,16 @@ targets:
- test/core/end2end/cq_verifier.cc - test/core/end2end/cq_verifier.cc
deps: deps:
- grpc_test_util - grpc_test_util
- name: overload_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/gprpp/overload.h
src:
- test/core/gprpp/overload_test.cc
deps: []
uses_polling: false
- name: percent_decode_fuzzer - name: percent_decode_fuzzer
build: fuzzer build: fuzzer
language: c++ language: c++

@ -0,0 +1,72 @@
// Copyright 2021 gRPC authors.
//
// 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
//
// http://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 GRPC_CORE_LIB_GPRPP_MATCH_H
#define GRPC_CORE_LIB_GPRPP_MATCH_H
// Portable code. port_platform.h is not required.
#include "absl/types/variant.h"
#include "src/core/lib/gprpp/overload.h"
namespace grpc_core {
namespace detail {
template <typename... Cases>
struct MatchPointerExtractor {
OverloadType<Cases...> cases;
template <typename T>
auto operator()(T& value) -> decltype(cases(&value)) {
return cases(&value);
}
};
} // namespace detail
/// Match on a variant.
/// Given variant \a value, and a set of callables \a fs, call the appropriate
/// callable based on the type contained in \a value.
///
/// Example (prints "hoorah"):
/// variant<int, string> v = 42;
/// Match(v,
/// [](int i) { puts("hoorah"); },
/// [](string s) { puts("boo"); });
template <typename... Fs, typename T0, typename... Ts>
auto Match(const absl::variant<T0, Ts...>& value, Fs... fs)
-> decltype(std::declval<OverloadType<Fs...>>()(std::declval<T0>())) {
return absl::visit(Overload(std::move(fs)...), value);
}
/// A version of Match that takes a mutable pointer to a variant and calls its
/// overload callables with a mutable pointer to the current variant value.
///
/// Example:
/// variant<int, string> v = 42;
/// MatchMutable(&v,
/// [](int* i) { *i = 1; },
/// [](string* s) { *s = "foo"; });
/// // v now contains 1.
template <typename... Fs, typename T0, typename... Ts>
auto MatchMutable(absl::variant<T0, Ts...>* value, Fs... fs)
-> decltype(std::declval<OverloadType<Fs...>>()(std::declval<T0*>())) {
return absl::visit(detail::MatchPointerExtractor<Fs...>{OverloadType<Fs...>(
std::move(fs)...)},
*value);
}
} // namespace grpc_core
#endif // GRPC_CORE_LIB_GPRPP_MATCH_H

@ -0,0 +1,59 @@
// Copyright 2021 gRPC authors.
//
// 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
//
// http://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 GRPC_CORE_LIB_GPRPP_OVERLOAD_H
#define GRPC_CORE_LIB_GPRPP_OVERLOAD_H
// Portable code. port_platform.h is not required.
#include <utility>
namespace grpc_core {
template <typename... Cases>
struct OverloadType;
// Compose one overload with N more -- use inheritance to leverage using and the
// empty base class optimization.
template <typename Case, typename... Cases>
struct OverloadType<Case, Cases...> : public Case,
public OverloadType<Cases...> {
explicit OverloadType(Case&& c, Cases&&... cases)
: Case(std::forward<Case>(c)),
OverloadType<Cases...>(std::forward<Cases>(cases)...) {}
using Case::operator();
using OverloadType<Cases...>::operator();
};
// Overload of a single case is just that case itself
template <typename Case>
struct OverloadType<Case> : public Case {
explicit OverloadType(Case&& c) : Case(std::forward<Case>(c)) {}
using Case::operator();
};
/// Compose callables into a single callable.
/// e.g. given [](int i) { puts("a"); } and [](double d) { puts("b"); },
/// return a callable object like:
/// struct {
/// void operator()(int i) { puts("a"); }
/// void operator()(double i) { puts("b"); }
/// };
/// Preserves all captures.
template <typename... Cases>
OverloadType<Cases...> Overload(Cases... cases) {
return OverloadType<Cases...>(std::move(cases)...);
}
} // namespace grpc_core
#endif // GRPC_CORE_LIB_GPRPP_OVERLOAD_H

@ -89,6 +89,30 @@ grpc_cc_test(
], ],
) )
grpc_cc_test(
name = "match_test",
srcs = ["match_test.cc"],
external_deps = ["gtest"],
language = "C++",
uses_polling = False,
deps = [
"//:match",
"//test/core/util:grpc_suppressions",
],
)
grpc_cc_test(
name = "overload_test",
srcs = ["overload_test.cc"],
external_deps = ["gtest"],
language = "C++",
uses_polling = False,
deps = [
"//:overload",
"//test/core/util:grpc_suppressions",
],
)
grpc_cc_test( grpc_cc_test(
name = "host_port_test", name = "host_port_test",
srcs = ["host_port_test.cc"], srcs = ["host_port_test.cc"],

@ -0,0 +1,75 @@
// Copyright 2021 gRPC authors.
//
// 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
//
// http://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 "src/core/lib/gprpp/match.h"
#include <gtest/gtest.h>
namespace grpc_core {
namespace testing {
TEST(MatchTest, Test) {
EXPECT_EQ(Match(
absl::variant<int, double>(1.9), [](int) -> int { abort(); },
[](double x) -> int {
EXPECT_EQ(x, 1.9);
return 42;
}),
42);
EXPECT_EQ(Match(
absl::variant<int, double>(3),
[](int x) -> int {
EXPECT_EQ(x, 3);
return 42;
},
[](double) -> int { abort(); }),
42);
}
TEST(MatchTest, TestVoidReturn) {
bool triggered = false;
Match(
absl::variant<int, double>(1.9), [](int) { abort(); },
[&triggered](double x) {
EXPECT_EQ(x, 1.9);
triggered = true;
});
EXPECT_TRUE(triggered);
}
TEST(MatchTest, TestMutable) {
absl::variant<int, double> v = 1.9;
MatchMutable(
&v, [](int*) { abort(); }, [](double* x) { *x = 0.0; });
EXPECT_EQ(v, (absl::variant<int, double>(0.0)));
}
TEST(MatchTest, TestMutableWithReturn) {
absl::variant<int, double> v = 1.9;
EXPECT_EQ(MatchMutable(
&v, [](int*) -> int { abort(); },
[](double* x) -> int {
*x = 0.0;
return 1;
}),
1);
EXPECT_EQ(v, (absl::variant<int, double>(0.0)));
}
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -0,0 +1,36 @@
// Copyright 2021 gRPC authors.
//
// 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
//
// http://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 "src/core/lib/gprpp/overload.h"
#include <gtest/gtest.h>
namespace grpc_core {
namespace testing {
TEST(Overload, Test) {
auto a = [](int x) { return x; };
auto b = [](std::string x) -> int { return x.length(); };
auto overload = Overload(a, b);
EXPECT_EQ(overload(1), 1);
EXPECT_EQ(overload("1"), 1);
EXPECT_EQ(overload("abc"), 3);
}
} // namespace testing
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -21,6 +21,15 @@ grpc_package(
visibility = "public", visibility = "public",
) )
grpc_cc_library(
name = "grpc_suppressions",
data = [
"lsan_suppressions.txt",
"tsan_suppressions.txt",
"ubsan_suppressions.txt",
],
)
grpc_cc_library( grpc_cc_library(
name = "grpc_test_util_base", name = "grpc_test_util_base",
srcs = [ srcs = [
@ -67,17 +76,13 @@ grpc_cc_library(
"tracer_util.h", "tracer_util.h",
"trickle_endpoint.h", "trickle_endpoint.h",
], ],
data = [
"lsan_suppressions.txt",
"tsan_suppressions.txt",
"ubsan_suppressions.txt",
],
external_deps = [ external_deps = [
"absl/debugging:failure_signal_handler", "absl/debugging:failure_signal_handler",
"absl/debugging:symbolize", "absl/debugging:symbolize",
], ],
language = "C++", language = "C++",
deps = [ deps = [
":grpc_suppressions",
":stack_tracer", ":stack_tracer",
"//:gpr", "//:gpr",
"//:grpc_base_c", "//:grpc_base_c",

@ -5201,6 +5201,30 @@
], ],
"uses_polling": false "uses_polling": false
}, },
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "match_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{ {
"args": [], "args": [],
"benchmark": false, "benchmark": false,
@ -5393,6 +5417,30 @@
], ],
"uses_polling": true "uses_polling": true
}, },
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "overload_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{ {
"args": [], "args": [],
"benchmark": false, "benchmark": false,

@ -44,6 +44,8 @@ def check_port_platform_inclusion(directory_root):
with open(path) as f: with open(path) as f:
all_lines_in_file = f.readlines() all_lines_in_file = f.readlines()
for index, l in enumerate(all_lines_in_file): for index, l in enumerate(all_lines_in_file):
if l == '// Portable code. port_platform.h is not required.\n':
break
if '#include' in l: if '#include' in l:
if l not in [ if l not in [
'#include <grpc/support/port_platform.h>\n', '#include <grpc/support/port_platform.h>\n',

Loading…
Cancel
Save