diff options
author | Igor Murashkin <iam@google.com> | 2018-07-26 14:14:44 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2018-08-10 11:59:21 -0700 |
commit | 7dfecaa23cfb56b360d683a301e6dd0144b15214 (patch) | |
tree | 2bf5660768e2e48adc2c105778f9d9508a6748e5 | |
parent | 67a7fe692ecc0da7ea6e10b10bf8b1ca5662eeab (diff) | |
parent | 4f1aacf4aaeec2565b2945bba311d2e0efbbb579 (diff) | |
download | google-fruit-7dfecaa23cfb56b360d683a301e6dd0144b15214.tar.gz |
android: Import fruit from upstream
Add required metadata files for AOSP usage.
Merge remote-tracking branch 'aosp/upstream-master' into master
Change-Id: I7d36b446f210c6d1a71810b5954881a988825455
340 files changed, 40522 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ad65d48 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: LLVM +Language: Cpp +ColumnLimit: 120 +AllowShortFunctionsOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: true +PointerAlignment: Left diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5da26d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/*.kdev4 +/.kdev_include_paths +/coverage +bench_results +/extras/bazel_root/bazel-* +.idea +*.pyc +/build* +/cmake-build-debug +/cmake-build-release +/.vs +/Debug +/*.sln +/*.vcxproj +/*.vcxproj.filters +/CMakeSettings.json +/*.vcxproj.user diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6560579 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,219 @@ +# +# This file was auto-generated from extras/scripts/travis_yml_generator.py, DO NOT EDIT +# +branches: + only: + - master +dist: trusty +language: cpp +matrix: + fast_finish: true + include: + - compiler: gcc + env: COMPILER=gcc-7 UBUNTU=17.10 TEST=ReleasePlain + install: export OS=linux; export COMPILER='gcc-7'; export UBUNTU='17.10'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='gcc-7'; export UBUNTU='17.10'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: gcc + env: COMPILER=gcc-7 UBUNTU=17.10 TEST=DebugPlain + install: export OS=linux; export COMPILER='gcc-7'; export UBUNTU='17.10'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='gcc-7'; export UBUNTU='17.10'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: clang + env: COMPILER=clang-4.0 STL=libstdc++ UBUNTU=17.10 TEST=ReleasePlain + install: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-4.0 STL=libstdc++ UBUNTU=17.10 TEST=DebugAsanUbsan + install: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/postsubmit.sh DebugAsanUbsan + - compiler: clang + env: COMPILER=clang-4.0 STL=libstdc++ UBUNTU=17.10 TEST=DebugPlain + install: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-4.0'; export STL='libstdc++'; + export UBUNTU='17.10'; extras/scripts/postsubmit.sh DebugPlain + - compiler: gcc + env: COMPILER=bazel UBUNTU=16.04 + install: export OS=linux; export COMPILER='bazel'; export UBUNTU='16.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='bazel'; export UBUNTU='16.04'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: gcc + env: COMPILER=gcc-6 TEST=DebugPlain + install: export OS=osx; export COMPILER='gcc-6'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='gcc-6'; extras/scripts/postsubmit.sh DebugPlain + - compiler: clang + env: COMPILER=clang-4.0 STL=libc++ TEST=DebugPlain + install: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: clang + env: COMPILER=clang-default STL=libc++ TEST=DebugPlain + install: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; + extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8.2 + script: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: gcc + env: COMPILER=gcc-5 UBUNTU=14.04 TEST=ReleasePlain + install: export OS=linux; export COMPILER='gcc-5'; export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='gcc-5'; export UBUNTU='14.04'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: gcc + env: COMPILER=gcc-5 UBUNTU=14.04 TEST=DebugPlain + install: export OS=linux; export COMPILER='gcc-5'; export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='gcc-5'; export UBUNTU='14.04'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: clang + env: COMPILER=clang-3.5 STL=libstdc++ UBUNTU=14.04 TEST=ReleasePlain + install: export OS=linux; export COMPILER='clang-3.5'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.5'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-3.5 STL=libstdc++ UBUNTU=14.04 TEST=DebugPlain + install: export OS=linux; export COMPILER='clang-3.5'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.5'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/postsubmit.sh DebugPlain + - compiler: clang + env: COMPILER=clang-3.9 STL=libstdc++ UBUNTU=14.04 TEST=ReleasePlain + install: export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-3.9 STL=libstdc++ UBUNTU=14.04 TEST=DebugPlain + install: export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; + export UBUNTU='14.04'; extras/scripts/postsubmit.sh DebugPlain + - compiler: clang + env: COMPILER=clang-3.5 STL=libc++ UBUNTU=14.04 TEST=ReleasePlain + install: export OS=linux; export COMPILER='clang-3.5'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.5'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-3.5 STL=libc++ UBUNTU=14.04 TEST=DebugPlain + install: export OS=linux; export COMPILER='clang-3.5'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.5'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/postsubmit.sh DebugPlain + - compiler: clang + env: COMPILER=clang-3.9 STL=libc++ UBUNTU=14.04 TEST=ReleasePlain + install: export OS=linux; export COMPILER='clang-3.9'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.9'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-3.9 STL=libc++ UBUNTU=14.04 TEST=DebugPlain + install: export OS=linux; export COMPILER='clang-3.9'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/travis_ci_install_linux.sh + os: linux + script: export OS=linux; export COMPILER='clang-3.9'; export STL='libc++'; export + UBUNTU='14.04'; extras/scripts/postsubmit.sh DebugPlain + - compiler: gcc + env: COMPILER=gcc-5 TEST=ReleasePlain + install: export OS=osx; export COMPILER='gcc-5'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='gcc-5'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: gcc + env: COMPILER=gcc-5 TEST=DebugPlain + install: export OS=osx; export COMPILER='gcc-5'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='gcc-5'; extras/scripts/postsubmit.sh DebugPlain + - compiler: gcc + env: COMPILER=gcc-6 TEST=ReleasePlain + install: export OS=osx; export COMPILER='gcc-6'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='gcc-6'; extras/scripts/postsubmit.sh ReleasePlain + - compiler: clang + env: COMPILER=clang-3.7 STL=libc++ TEST=ReleasePlain + install: export OS=osx; export COMPILER='clang-3.7'; export STL='libc++'; extras/scripts/travis_ci_install_osx.sh + os: osx + script: export OS=osx; export COMPILER='clang-3.7'; export STL='libc++'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: clang + env: COMPILER=clang-3.7 STL=libc++ TEST=DebugPlain + install: export OS=osx; export COMPILER='clang-3.7'; export STL='libc++'; extras/scripts/travis_ci_install_osx.sh + os: osx + script: export OS=osx; export COMPILER='clang-3.7'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugPlain + - compiler: clang + env: COMPILER=clang-4.0 STL=libc++ TEST=ReleasePlain + install: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: clang + env: COMPILER=clang-4.0 STL=libc++ TEST=DebugAsanUbsan + install: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8 + script: export OS=osx; export COMPILER='clang-4.0'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugAsanUbsan + - compiler: clang + env: COMPILER=clang-default STL=libc++ TEST=ReleasePlain + install: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; + extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode7.3 + script: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: clang + env: COMPILER=clang-default STL=libc++ TEST=DebugAsan + install: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; + extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode7.3 + script: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugAsan + - compiler: clang + env: COMPILER=clang-default STL=libc++ TEST=ReleasePlain + install: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; + extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8.2 + script: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; extras/scripts/postsubmit.sh + ReleasePlain + - compiler: clang + env: COMPILER=clang-default STL=libc++ TEST=DebugAsan + install: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; + extras/scripts/travis_ci_install_osx.sh + os: osx + osx_image: xcode8.2 + script: export OS=osx; export COMPILER='clang-default'; export STL='libc++'; extras/scripts/postsubmit.sh + DebugAsan +services: +- docker +sudo: required + @@ -0,0 +1,23 @@ + +package(default_visibility = ["//visibility:public"]) +licenses(["notice"]) + +filegroup( + name = "fruit_headers", + srcs = glob([ + "include/**/*.h", + "configuration/bazel/**/*.h", + ]), +) + +cc_library( + name = "fruit", + srcs = glob([ + "src/*.cpp", + "include/fruit/impl/**/*.h", + "configuration/bazel/**/*.h"]), + hdrs = glob(["include/fruit/*.h"]), + includes = ["include", "configuration/bazel"], + deps = [], + linkopts = ["-lm"], +) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9ac08da --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,125 @@ +project(Fruit) +cmake_minimum_required(VERSION 2.8) + +if (POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif() + +# CMake on OSX likes to see this set explicitly, otherwise it outputs a warning. +set(CMAKE_MACOSX_RPATH 1) + +if(NOT "${CMAKE_BUILD_TYPE}" MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)$") + message(FATAL_ERROR "Please re-run CMake, specifying -DCMAKE_BUILD_TYPE=Debug , -DCMAKE_BUILD_TYPE=Release , -DCMAKE_BUILD_TYPE=RelWithDebInfo or -DCMAKE_BUILD_TYPE=MinSizeRel .") +endif() + +option(BUILD_SHARED_LIBS "Build shared library" ON) + +# The Visual Studio CMake generators default to multiple configurations, but Fruit doesn't support multi-configuration build directories. +set(CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}") + +if(NOT "${RUNTIME_OUTPUT_DIRECTORY}") + set(RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") +endif() + +set(FRUIT_ADDITIONAL_CXX_FLAGS "" CACHE STRING "Additional CXX compiler flags." FORCE) + +set(FRUIT_ADDITIONAL_COMPILE_FLAGS "${FRUIT_ADDITIONAL_CXX_FLAGS}") + +if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "^(GNU|Clang|AppleClang|MSVC)$") + message(WARNING "Compiler not officially supported: ${CMAKE_CXX_COMPILER_ID}") + # Full list of possible values at https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html . + # Major compilers not currently supported: + # * "Intel": not supported ATM due to compiler bugs: + # - https://software.intel.com/en-us/forums/intel-c-compiler/topic/606048 + # - https://software.intel.com/en-us/forums/intel-c-compiler/topic/606049 +endif() + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(GNU|Clang|Intel|AppleClang)$") + set(FRUIT_ADDITIONAL_COMPILE_FLAGS "${FRUIT_ADDITIONAL_COMPILE_FLAGS} -std=c++11 -W -Wall -Wno-unknown-warning-option -Wno-missing-braces") +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(MSVC)$") + # TODO: we currently disable the warning C4709 because MSVC emits it even when there is no reason to. Re-enable it when possible. + # TODO: the warning C4141 is disabled, because MSVC emits it ("'inline': used more than once") when a function/method is marked with both __forceinline and inline. + # TODO: the warning C4714 is disabled, MSVC emits it when it decides not to inline a __forceinline function/method. + # The warning C4577 is disabled because we don't need a termination guarantee on exceptions for functions marked with + # 'noexcept'. + # The warning C4530 is disabled because it's triggered by MSVC's STL. + set(FRUIT_ADDITIONAL_COMPILE_FLAGS "${FRUIT_ADDITIONAL_COMPILE_FLAGS} /nologo /FS /W4 /wd4324 /wd4709 /wd4459 /wd4141 /wd4714 /wd4577 /wd4530 /D_SCL_SECURE_NO_WARNINGS") +endif() + +option(FRUIT_ENABLE_COVERAGE "Enable collection of test coverage. This is meant to be used by Fruit developers. It's only supported when using GCC on Linux." OFF) +if("${FRUIT_ENABLE_COVERAGE}") + # We also disable exceptions because otherwise GCC considers every function/method call that could throw as an + # additional branch. + set(FRUIT_ADDITIONAL_COMPILE_FLAGS "${FRUIT_ADDITIONAL_COMPILE_FLAGS} -fprofile-arcs -ftest-coverage -fno-exceptions -fno-inline -O0") + set(FRUIT_ADDITIONAL_LINKER_FLAGS "${FRUIT_ADDITIONAL_LINKER_FLAGS} -fprofile-arcs -ftest-coverage -fno-exceptions -fno-inline -O0") +endif() + +set(FRUIT_USES_BOOST TRUE CACHE BOOL + "Whether to use Boost (specifically, boost::unordered_set and boost::unordered_map). + If this is false, Fruit will use std::unordered_set and std::unordered_map instead (however this causes injection to be a bit slower).") + +if("${WIN32}" AND "${FRUIT_USES_BOOST}") + set(BOOST_DIR "" CACHE PATH "The directory where the boost library is installed, e.g. C:\\boost\\boost_1_62_0.") + if("${BOOST_DIR}" STREQUAL "") + message(FATAL_ERROR "Please re-run CMake, specifying the boost library path as BOOST_DIR, e.g. -DBOOST_DIR=C:\\boost\\boost_1_62_0.") + endif() + include_directories("${BOOST_DIR}") +endif() + +set(RUN_TESTS_UNDER_VALGRIND FALSE CACHE BOOL "Whether to run Fruit tests under valgrind") +if ("${RUN_TESTS_UNDER_VALGRIND}") + set(RUN_TESTS_UNDER_VALGRIND_FLAG "1") +endif() + +# Unsafe, only for debugging/benchmarking. +#set(FRUIT_ADDITIONAL_COMPILE_FLAGS "${FRUIT_ADDITIONAL_COMPILE_FLAGS} -DFRUIT_NO_LOOP_CHECK") + +add_definitions(${FRUIT_ADDITIONAL_COMPILE_FLAGS}) +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FRUIT_ADDITIONAL_LINKER_FLAGS}") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FRUIT_ADDITIONAL_LINKER_FLAGS}") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FRUIT_ADDITIONAL_LINKER_FLAGS}") + +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") + set(FRUIT_COMPILE_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE} ${FRUIT_ADDITIONAL_COMPILE_FLAGS}") +else() + set(FRUIT_COMPILE_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG} ${FRUIT_ADDITIONAL_COMPILE_FLAGS}") +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +# (debug-only) compile switch to get deep template instantiation stacktraces for errors (instead +# of the user-friendly default that hides Fruit internals). +#add_definitions(-DFRUIT_DEEP_TEMPLATE_INSTANTIATION_STACKTRACES_FOR_ERRORS) + +set(INSTALL_INCLUDE_DIR include/fruit CACHE PATH "Installation directory for headers") +set(INSTALL_LIBRARY_DIR lib CACHE PATH "Installation directory for libraries") + +set(FRUIT_VERSION "3.2.0") + +add_subdirectory(configuration) +add_subdirectory(src) + +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + # Do not exclude these from "make all" in debug mode, they must build. + add_subdirectory(examples) + add_subdirectory(tests) +else() + add_subdirectory(examples EXCLUDE_FROM_ALL) + add_subdirectory(tests) +endif() + +add_subdirectory(extras EXCLUDE_FROM_ALL) + +install(DIRECTORY include/fruit/ + DESTINATION "${INSTALL_INCLUDE_DIR}" + FILES_MATCHING PATTERN "*.h") + +set(CPACK_PACKAGE_NAME "Fruit") +set(CPACK_PACKAGE_VENDOR "Marco Poletti") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Fruit - Dependency Injection Framework For C++") +string(REGEX REPLACE "([^.]*)\\.([^.]*)\\.([^.]*)" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${FRUIT_VERSION}") +string(REGEX REPLACE "([^.]*)\\.([^.]*)\\.([^.]*)" "\\2" CPACK_PACKAGE_VERSION_MINOR "${FRUIT_VERSION}") +string(REGEX REPLACE "([^.]*)\\.([^.]*)\\.([^.]*)" "\\3" CPACK_PACKAGE_VERSION_PATCH "${FRUIT_VERSION}") +set(CPACK_PACKAGE_VERSION "${FRUIT_VERSION}") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "Fruit") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf461be --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,240 @@ + +# Contributing to Fruit + +This file contains various information and documentation for Fruit contributors. +If you only want to use Fruit, see the [wiki](https://github.com/google/fruit/wiki); +you can find instructions for building Fruit manually +[here](https://github.com/google/fruit/wiki/install#building-fruit-manually). + +If you actually want to change Fruit itself, that's great! Read on. + +### Basics + +#### Build systems + +Fruit supports two build systems: CMake (configured in `CMakeLists.txt` files) and +[Bazel](https://www.bazel.io) (configured in `BUILD` files). + +This means that when you build/test Fruit code you have a choice of what build system you want to use, +but also that for larger changes (typically, if you add new files) you might need changes in both +`CMakeLists.txt` and `BUILD` files, to make sure that Fruit keeps building (and passing its tests) under both build +systems. +Both build systems are tested in Travis CI (see below). + +Example commands to build a development version of Fruit using CMake (with all assertions enabled) and run the tests: + +```bash +cd $PATH_TO_FRUIT +mkdir build-debug +cd build-debug +cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG" +make -j 16 +cd tests +py.test-3 -n auto +``` + +### Continuous Integration (CI) + +Fruit uses Travis CI for continuous integration. You can see the latest CI runs in Travis CI +[here](https://travis-ci.org/google/fruit/builds). The CI configuration is defined in +`extras/scripts/travis_yml_generator.py`, that generates a `.travis.yml` file (which must also be checked in, due to the +way Travis CI is configured). + +When editing the `travis_yml_generator.py` script you should also update the `.travis.yml` file (in the same commit) +by running: + +```bash +cd $PATH_TO_FRUIT +extras/scripts/travis_yml_generator.py >.travis.yml +``` + +Fruit tests run in Travis CI in various configurations/environments, notably: + +* In Linux or OS X +* In various Ubuntu versions +* Using GCC or Clang +* Optionally running under Valgrind +* Optionally running with ASan/UBSan +* Using CMake or Bazel + +These tests run after every commit in master and for every pull request (as soon as the pull request is sent). + +Linux tests run in Docker, using a set of images built for this purpose +([list of images](https://hub.docker.com/r/polettimarco/fruit-basesystem/tags/)). + +If a test fails in Travis CI in some configuration, look at the beginning of the Travis CI Job log for a line such as: + +```bash +export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; export UBUNTU='16.04'; extras/scripts/postsubmit.sh DebugValgrind +``` + +You can then run the same command locally (from your fruit directory) to reproduce the issue. Running this +`postsubmit.sh` script will run the tests under Docker to ensure repeatability of the results. + +For example, even if the failure only happens with an old Ubuntu/GCC version you don't have installed, it will download +a Docker image containing that old Ubuntu/GCC and then run the tests inside a VM started from that image. + +Once `postsubmit.sh` completes, if you want you can attach to the stopped VM used to run the tests by running: + +```bash +docker attach fruit +``` + +This is often very useful to e.g. re-run a compilation manually with additional debug flags. + +When running `postsubmit.sh` manually in this way, it will run using the latest changes in your fruit directory, even if +they aren't staged/committed yet. This allows to do a quicker edit/test cycle. + +To speed up the execution of `postsubmit.sh` you can also set the `NJOBS` variable, e.g.: + +```bash +export NJOBS=16; export OS=linux; export COMPILER='clang-3.9'; export STL='libstdc++'; export UBUNTU='16.04'; extras/scripts/postsubmit.sh DebugValgrind +``` + +The default number of jobs (used in Travis CI) is 2. + +### How to run Fruit tests on Windows + +You can import Fruit in Visual Studio (2017 and later) as a CMake project. You need to set the relevant CMake flags in +the `CMakeSettings.json` file that Visual Studio will create. +For example, if you installed Boost in `C:\boost\boost_1_62_0`, you can put this configuration in your +`CMakeSettings.json`: + + { + // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. + "configurations": [ + { + "name": "x86-Debug", + "generator": "Visual Studio 15 2017", + "configurationType": "Debug", + "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", + "cmakeCommandArgs": "-DBOOST_DIR=C:\\boost\\boost_1_62_0 -DCMAKE_BUILD_TYPE=Debug -DFRUIT_ADDITIONAL_CXX_FLAGS=/Z7", + "buildCommandArgs": "-m -v:minimal" + }, + { + "name": "x86-Release", + "generator": "Visual Studio 15 2017", + "configurationType": "Release", + "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", + "cmakeCommandArgs": "-DBOOST_DIR=C:\\boost\\boost_1_62_0 -DCMAKE_BUILD_TYPE=Release", + "buildCommandArgs": "-m -v:minimal" + }, + { + "name": "x64-Debug", + "generator": "Visual Studio 15 2017 Win64", + "configurationType": "Debug", + "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", + "cmakeCommandArgs": "-DBOOST_DIR=C:\\boost\\boost_1_62_0 -DCMAKE_BUILD_TYPE=Debug -DFRUIT_ADDITIONAL_CXX_FLAGS=/Z7", + "buildCommandArgs": "-m -v:minimal" + }, + { + "name": "x64-Release", + "generator": "Visual Studio 15 2017 Win64", + "configurationType": "Release", + "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}", + "cmakeCommandArgs": "-DBOOST_DIR=C:\\boost\\boost_1_62_0 -DCMAKE_BUILD_TYPE=Release", + "buildCommandArgs": "-m -v:minimal" + } + ] + } + +The `/Z7` flag instructs Visual Studio to use the C7 format for debugging information, which allows Fruit's tests to run in parallel without interfering with each other. + +You can now run CMake within Visual Studio (from the menu: CMake -> Cache -> Generate -> CMakeLists.txt) and build Fruit (from the menu: CMake -> Build All). + +You can also run tests, but *only* from the command-line (after building Fruit from Visual Studio), running tests from Visual Studio doesn't work. +To do so: + +* Open the Start menu +* From there, open the "Native Tools Command Prompt for VS 2017" shell for the chosen architecture. For example, "x64 Native Tools Command Prompt for VS 2017". +* In Visual Studio, open the Output view (from the menu: View -> Output) and select "CMake" in the "Show output from:" dropdown menu. +* Scroll to the beginning of that view. You should see two lines starting with "Command line" and "Working directory" respectively. +* Cd to that working directory in the shell. For example, if the path in the "Working directory" line is `C:\Users\Marco\AppData\Local\CMakeBuild\fa17dda0-4eec-6438-a358-e1253b7e86ff\build\x64-Debug`, you can run `cd "C:\Users\Marco\AppData\Local\CMakeBuild\fa17dda0-4eec-6438-a358-e1253b7e86ff\build\x64-Debug"`. +* Cd to the "tests" subdirectory ("cd tests"). +* Then run pytest, e.g. `py.test -n auto`. + +### Sending pull requests + +If you send a pull request, you should make sure that these CI tests are passing. They will run automatically on your +pull request as soon as you send it. + +As an exception, if the current master also failed the last CI run feel free to send the pull request anyway (you can go +[here](https://travis-ci.org/google/fruit) to check if that's the case). + +If a test fails, see the CI section above for informations on how to reproduce. + +You should also make sure that your code: + +* Is formatted correctly ([more details here](#code-style)) +* Has appropriate tests (if your change is user-visible, or if you're introducing new branches that should be tested) + +### What to install in order to develop Fruit code + +In addition to +[the compiler you need to install to build Fruit](https://github.com/google/fruit/wiki/install#dependencies), +when developing Fruit code you might need some of the following software. Note that depending on your change you may or +may not need all of these; you might want to go ahead without these and then only install additional things if you get +an error about a missing tool. + +* CMake +* Bazel ([installation instructions](https://www.bazel.io/docs/install.html)) +* Valgrind +* Docker + +## Useful command for fast edit/rebuild/retest cycles + +This command uses Bazel to run the tests (so you need to have it installed in order to use this). +Bazel has a much more fine-grained picture of what tests depend on what source files, so it will often avoid running +tests that have passed before when it knows that they will pass (unlike py.test that runs the entire test suite every +time). This is especially relevant for incremental builds when only test sources have changed (e.g. after adjusting an +expectation in a test or fixing a bug in the test); there is little difference when changing `src/` or `include/` +because all tests will be re-run anyway. + +```bash +cd $PATH_TO_FRUIT/extras/bazel_root +bazel test --python_path=/usr/bin/python3 \ + --test_output=errors \ + --test_summary=terse \ + //third_party/fruit/... +``` + +## Checking test coverage + +Fruit's test suite supports collecting test coverage (only when building with GCC on Linux using CMake). +Example commands: + +```bash +cd $PATH_TO_FRUIT +mkdir build-coverage +cd build-coverage +CXX=g++-6 cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DFRUIT_ENABLE_COVERAGE=ON +make -j 10 +(cd tests; py.test-3 -n auto) +lcov --rc lcov_branch_coverage=1 --capture --directory . --output-file coverage.info +lcov --rc lcov_branch_coverage=1 --remove coverage.info '/usr/include/*' '/tmp/*' -o coverage-filtered.info +genhtml --no-function-coverage --rc lcov_branch_coverage=1 --rc genhtml_hi_limit=100 coverage-filtered.info --output-directory html +google-chrome html/index.html +``` + +The important figures for each file are: +* Percentage of lines covered +* Percentage of branches covered + +Ideally, they should both be 100%. The `LCOV_EXCL_LINE` and `LCOV_EXCL_BR_LINE` markers can be used to mark lines and +branches (respectively) that can't be covered and therefore should be excluded. + +Note that the "percentage of **functions** covered" metric is not meaningful for Fruit, since it considers each +instantiation of a template function/method as separate (even if they share the same source lines). + +## Code style + +C++ code in Fruit should be indented using clang-format (a `.clang-format` file is provided in the Fruit root +directory). You can re-indent all code using this command: + +```bash +$ clang-format -i $(git ls-files | egrep '\.cpp|\.h' ) +``` + +## Reporting vulnerabilities + +In case of a security vulnerability in Fruit, please contact [poletti.marco@gmail.com](mailto:poletti.marco@gmail.com) directly instead of using the public issue tracker. @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. All rights reserved. + + 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. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..084579a --- /dev/null +++ b/METADATA @@ -0,0 +1,23 @@ +name: "google-fruit" +description: + "Fruit is a dependency injection framework for C++, loosely inspired by the " + "Guice framework for Java. It uses C++ metaprogramming together with some " + "new C++11 features to detect most injection problems at compile-time. It " + "allows to split the implementation code in \"components\" (aka modules) that " + "can be assembled to form other components. From a component with no " + "requirements it's then possible to create an injector, that provides an " + "instance of the interfaces exposed by the component." + +third_party { + url { + type: HOMEPAGE + value: "https://github.com/google/fruit" + } + url { + type: GIT + value: "https://github.com/google/fruit.git" + } + version: "4f1aacf4aaeec2565b2945bba311d2e0efbbb579" + last_upgrade_date { year: 2018 month: 7 day: 26 } + license_type: NOTICE +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. All rights reserved. + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b87cae3 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ + +[![Build Status](https://img.shields.io/travis/google/fruit/master.svg?label=Linux/OSX%20build/tests)](https://travis-ci.org/google/fruit) +[![Build status](https://img.shields.io/appveyor/ci/poletti-marco/fruit/master.svg?label=Windows%20build/tests)](https://ci.appveyor.com/project/poletti-marco/fruit) +[![Coverity Scan Status](https://img.shields.io/coverity/scan/8486.svg?label=Coverity%20scan)](https://scan.coverity.com/projects/google-fruit) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1040/badge)](https://bestpractices.coreinfrastructure.org/projects/1040) + +Fruit is a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) framework for C++, loosely inspired by the Guice framework for Java. It uses C++ metaprogramming together with some new C++11 features to detect most injection problems at compile-time. +It allows to split the implementation code in "components" (aka modules) that can be assembled to form other components. +From a component with no requirements it's then possible to create an injector, that provides an instance of the interfaces exposed by the component. + +See the [wiki](https://github.com/google/fruit/wiki) for more information, including installation instructions, tutorials and reference documentation. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..1c3a847 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,62 @@ +version: 1.0.{build} +clone_folder: C:\Fruit +environment: + PYTHON3_PATH: C:\Python36 + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: 'Visual Studio 15 2017 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="/WX /DFRUIT_DEBUG /DFRUIT_EXTRA_DEBUG /D_ITERATOR_DEBUG_LEVEL=2" -T host=x64' + CONFIGURATION: Debug + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: 'Visual Studio 14 2015 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="/WX /DFRUIT_DEBUG /DFRUIT_EXTRA_DEBUG /D_ITERATOR_DEBUG_LEVEL=2" -T host=x64' + CONFIGURATION: Debug + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + MINGW_PATH: 'C:\MinGW\bin' + CMAKE_GENERATOR: 'MinGW Makefiles' + VCVARSALL_DIR: '' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="-Werror -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG"' + CONFIGURATION: Debug + + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + MINGW_PATH: 'C:\MinGW\bin' + CMAKE_GENERATOR: 'MinGW Makefiles' + VCVARSALL_DIR: '' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="-Werror"' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + MINGW_PATH: 'C:\MinGW\bin' + CMAKE_GENERATOR: 'MinGW Makefiles' + VCVARSALL_DIR: '' + ADDITIONAL_CMAKE_ARGS: '-DBOOST_DIR=C:\Libraries\boost_1_63_0 -DCMAKE_CXX_FLAGS="-Werror"' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: 'Visual Studio 15 2017 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="/WX" -T host=x64' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: 'Visual Studio 14 2015 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DCMAKE_CXX_FLAGS="/WX" -T host=x64' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + MINGW_PATH: 'C:\MinGW\bin' + CMAKE_GENERATOR: 'MinGW Makefiles' + VCVARSALL_DIR: '' + ADDITIONAL_CMAKE_ARGS: '-DBOOST_DIR=C:\Libraries\boost_1_63_0 -DBUILD_SHARED_LIBS=False -DCMAKE_CXX_FLAGS="-Werror"' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: 'Visual Studio 15 2017 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DBUILD_SHARED_LIBS=False -DCMAKE_CXX_FLAGS="/WX" -T host=x64' + CONFIGURATION: Release + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: 'Visual Studio 14 2015 Win64' + VCVARSALL_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC' + ADDITIONAL_CMAKE_ARGS: '-DFRUIT_USES_BOOST=False -DBUILD_SHARED_LIBS=False -DCMAKE_CXX_FLAGS="/WX" -T host=x64' + CONFIGURATION: Release +build_script: +- cmd: cmd /c C:\Fruit\extras\scripts\postsubmit.bat diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..02e208e --- /dev/null +++ b/conanfile.py @@ -0,0 +1,37 @@ +from conans import ConanFile, CMake, tools + + +class FruitConan(ConanFile): + name = "fruit" + version = "3.2.0" + license = "Apache" + url = "https://github.com/google/fruit" + description = "C++ dependency injection framework" + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False]} + default_options = "shared=False" + generators = "cmake" + + def source(self): + self.run("git clone https://github.com/google/fruit") + self.run("cd fruit && git checkout v3.2.0") + + def build(self): + cmake = CMake(self) + if self.options.shared: + cmake.definitions["BUILD_SHARED_LIBS"] = "YES" + else: + cmake.definitions["BUILD_SHARED_LIBS"] = "NO" + cmake.definitions["FRUIT_USES_BOOST"] = "NO" + cmake.definitions["RUN_TESTS_UNDER_VALGRIND"] = "NO" + cmake.definitions["FRUIT_TESTS_USE_PRECOMPILED_HEADERS"] = "NO" + cmake.definitions["FRUIT_ENABLE_COVERAGE"] = "NO" + cmake.configure(source_folder="fruit") + cmake.build() + cmake.install() + + def package(self): + pass + + def package_info(self): + self.cpp_info.libs = ["fruit"] diff --git a/configuration/CMakeLists.txt b/configuration/CMakeLists.txt new file mode 100644 index 0000000..b18a463 --- /dev/null +++ b/configuration/CMakeLists.txt @@ -0,0 +1,221 @@ + +include(CheckCXXSourceCompiles) + +set(CMAKE_REQUIRED_FLAGS "${FRUIT_COMPILE_FLAGS}") + +CHECK_CXX_SOURCE_COMPILES(" +int main() {} +" +FRUIT_TRIVIAL_SOURCE_COMPILES) + +if (NOT "${FRUIT_TRIVIAL_SOURCE_COMPILES}") + message(FATAL_ERROR "A trivial program with an empty main doesn't compile, something is wrong with your compiler and/or with your compiler flags.") +endif() + +CHECK_CXX_SOURCE_COMPILES(" +template <typename T, typename U> +struct Pair {}; + +struct Map : public Pair<int, float>, Pair<int, char> {}; + +template <typename Value> +Value f(Pair<int, Value>*) { return Value(); } + +int main() { + f((Map*)0); +} +" +FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG) + +CHECK_CXX_SOURCE_COMPILES(" +int main() { + bool b = __has_trivial_copy(int); + (void) b; + return 0; +} +" +FRUIT_HAS_HAS_TRIVIAL_COPY) + +CHECK_CXX_SOURCE_COMPILES(" +int main() { + bool b = __is_trivially_copyable(int); + (void) b; + return 0; +} +" +FRUIT_HAS_IS_TRIVIALLY_COPYABLE) + +CHECK_CXX_SOURCE_COMPILES(" +#include <cstddef> +using X = max_align_t; +int main() { + return 0; +} +" +FRUIT_HAS_MAX_ALIGN_T) + +CHECK_CXX_SOURCE_COMPILES(" +#include <type_traits> +int main() { + bool b = std::is_trivially_copyable<int>::value; + (void) b; + return 0; +} +" +FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE) + +CHECK_CXX_SOURCE_COMPILES(" +#include <type_traits> +int main() { + bool b = std::is_trivially_copy_constructible<int>::value; + (void) b; + return 0; +} +" +FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE) + +CHECK_CXX_SOURCE_COMPILES(" +#include <cstddef> +using X = std::max_align_t; +int main() { + return 0; +} +" +FRUIT_HAS_STD_MAX_ALIGN_T) + +CHECK_CXX_SOURCE_COMPILES(" +#include <typeinfo> +int main() { + (void) typeid(int); + return 0; +} +" +FRUIT_HAS_TYPEID) + +CHECK_CXX_SOURCE_COMPILES(" +#include <typeinfo> +int main() { + constexpr static const std::type_info& x = typeid(int); + (void) x; + return 0; +} +" +FRUIT_HAS_CONSTEXPR_TYPEID) + +CHECK_CXX_SOURCE_COMPILES(" +#include <cxxabi.h> +int main() { + auto* p = abi::__cxa_demangle; + (void) p; + return 0; +} +" +FRUIT_HAS_CXA_DEMANGLE) + +if("${FRUIT_ENABLE_COVERAGE}") + set(FRUIT_HAS_ALWAYS_INLINE_ATTRIBUTE OFF) + set(FRUIT_HAS_FORCEINLINE OFF) +else() +CHECK_CXX_SOURCE_COMPILES(" +__attribute__((always_inline)) +void f() { +} + +int main() { + return 0; +} +" +FRUIT_HAS_ALWAYS_INLINE_ATTRIBUTE) + +CHECK_CXX_SOURCE_COMPILES(" +__forceinline +void f() { +} + +int main() { + return 0; +} +" +FRUIT_HAS_FORCEINLINE) + +endif() + +CHECK_CXX_SOURCE_COMPILES(" +[[deprecated]] void f(); + +int main() { + return 0; +} +" +FRUIT_HAS_ATTRIBUTE_DEPRECATED) + +CHECK_CXX_SOURCE_COMPILES(" +void f() __attribute__((deprecated)); + +int main() { + return 0; +} +" +FRUIT_HAS_GCC_ATTRIBUTE_DEPRECATED) + +CHECK_CXX_SOURCE_COMPILES(" +__declspec(deprecated) void f(); + +int main() { + return 0; +} +" +FRUIT_HAS_DECLSPEC_DEPRECATED) + +CHECK_CXX_SOURCE_COMPILES(" +int f() { + static int x = 1; + if (x == 1) { + return 0; + } else { + __assume(0); + // Note: the lack of return here is intentional + } +} + +int main() { + return f(); +} +" +FRUIT_HAS_MSVC_ASSUME) + +CHECK_CXX_SOURCE_COMPILES(" +int f() { + static int x = 1; + if (x == 1) { + return 0; + } else { + __builtin_unreachable(); + // Note: the lack of return here is intentional + } +} + +int main() { + return f(); +} +" +FRUIT_HAS_BUILTIN_UNREACHABLE) + + +if (NOT "${FRUIT_HAS_STD_MAX_ALIGN_T}" AND NOT "${FRUIT_HAS_MAX_ALIGN_T}") + message(WARNING "The current C++ standard library doesn't support std::max_align_t nor ::max_align_t. Attempting to use std::max_align_t anyway, but it most likely won't work.") +endif() + +if(NOT "${FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE}" AND NOT "${FRUIT_HAS_IS_TRIVIALLY_COPYABLE}" + AND NOT "${FRUIT_HAS_HAS_TRIVIAL_COPY}") + message(WARNING "The current standard library doesn't support std::is_trivially_copyable<T>, and the current compiler doesn't support __is_trivially_copyable(T) nor __has_trivial_copy(T). Attemping to use std::is_trivially_copyable<T> anyway, but it most likely won't work.") +endif() + +if (NOT "${FRUIT_HAS_ATTRIBUTE_DEPRECATED}" AND NOT "${FRUIT_HAS_GCC_ATTRIBUTE_DEPRECATED}" AND NOT "${FRUIT_HAS_DECLSPEC_DEPRECATED}") + message(WARNING "No supported way to mark functions as deprecated was found. Continuing anyway, without the 'deprecated' markers.") +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fruit-config-base.h.in ${CMAKE_CURRENT_BINARY_DIR}/../include/fruit/impl/fruit-config-base.h) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/../include/fruit/impl/fruit-config-base.h + DESTINATION ${INSTALL_INCLUDE_DIR}/impl) diff --git a/configuration/bazel/fruit/impl/fruit-config-base.h b/configuration/bazel/fruit/impl/fruit-config-base.h new file mode 100644 index 0000000..8e7af1d --- /dev/null +++ b/configuration/bazel/fruit/impl/fruit-config-base.h @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_CONFIG_BASE_H +#define FRUIT_CONFIG_BASE_H + +// Needed for all Clang versions (as of January 2016), not needed for GCC. +// This can also be defined for GCC, but it slightly slows down compile time of code using Fruit. +#define FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG 1 + +// Whether the compiler defines std::max_align_t. +#define FRUIT_HAS_STD_MAX_ALIGN_T 1 + +// Whether the compiler defines ::max_align_t. +// Ignored if FRUIT_HAS_STD_MAX_ALIGN_T is set. +#define FRUIT_HAS_MAX_ALIGN_T 1 + +// Whether the compiler defines std::is_trivially_copyable. +#define FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE 1 + +// Whether the compiler defines __has_trivial_copy. +// Ignored if FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE is set. +#define FRUIT_HAS_HAS_TRIVIAL_COPY 1 + +// Whether the compiler defines __is_trivially_copyable. +// Ignored if FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE is set. +#define FRUIT_HAS_IS_TRIVIALLY_COPYABLE 1 + +// Whether the compiler defines std::is_trivially_copy_constructible. +#define FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE 1 + +// Whether typeid() is available. Typically, it is unless RTTI is disabled. +#define FRUIT_HAS_TYPEID 1 + +// Whether typeid() is constexpr. Typically, it is except in MSVC. +#define FRUIT_HAS_CONSTEXPR_TYPEID 1 + +// Whether abi::__cxa_demangle() is available after including cxxabi.h. +#define FRUIT_HAS_CXA_DEMANGLE 1 + +#define FRUIT_USES_BOOST 1 + +#define FRUIT_HAS_ALWAYS_INLINE_ATTRIBUTE 1 + +#define FRUIT_HAS_FORCEINLINE 0 + +#define FRUIT_HAS_ATTRIBUTE_DEPRECATED 0 + +#define FRUIT_HAS_GCC_ATTRIBUTE_DEPRECATED 1 + +#define FRUIT_HAS_DECLSPEC_DEPRECATED 0 + +#define FRUIT_HAS_MSVC_ASSUME 0 + +#define FRUIT_HAS_BUILTIN_UNREACHABLE 1 + +#endif // FRUIT_CONFIG_BASE_H diff --git a/configuration/fruit-config-base.h.in b/configuration/fruit-config-base.h.in new file mode 100644 index 0000000..5554eea --- /dev/null +++ b/configuration/fruit-config-base.h.in @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_CONFIG_BASE_H +#define FRUIT_CONFIG_BASE_H + +#cmakedefine FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG 1 +#cmakedefine FRUIT_HAS_HAS_TRIVIAL_COPY 1 +#cmakedefine FRUIT_HAS_IS_TRIVIALLY_COPYABLE 1 +#cmakedefine FRUIT_HAS_MAX_ALIGN_T 1 +#cmakedefine FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE 1 +#cmakedefine FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE 1 +#cmakedefine FRUIT_HAS_STD_MAX_ALIGN_T 1 +#cmakedefine FRUIT_HAS_TYPEID 1 +#cmakedefine FRUIT_HAS_CONSTEXPR_TYPEID 1 +#cmakedefine FRUIT_HAS_CXA_DEMANGLE 1 +#cmakedefine FRUIT_USES_BOOST 1 +#cmakedefine FRUIT_HAS_ALWAYS_INLINE_ATTRIBUTE 1 +#cmakedefine FRUIT_HAS_FORCEINLINE 1 +#cmakedefine FRUIT_HAS_ATTRIBUTE_DEPRECATED 1 +#cmakedefine FRUIT_HAS_GCC_ATTRIBUTE_DEPRECATED 1 +#cmakedefine FRUIT_HAS_DECLSPEC_DEPRECATED 1 +#cmakedefine FRUIT_HAS_MSVC_ASSUME 1 +#cmakedefine FRUIT_HAS_BUILTIN_UNREACHABLE 1 + +#endif // FRUIT_CONFIG_BASE_H diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..1e33fad --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_subdirectory(simple_injection) +add_subdirectory(hello_world) + +# This uses threads and doesn't work on Windows (at least when using MinGW's GCC). +if(NOT "${WIN32}") + add_subdirectory(server) +endif() + +add_subdirectory(multibindings) +add_subdirectory(scaling_doubles) +add_subdirectory(annotated_injection) diff --git a/examples/annotated_injection/BUILD b/examples/annotated_injection/BUILD new file mode 100644 index 0000000..a659487 --- /dev/null +++ b/examples/annotated_injection/BUILD @@ -0,0 +1,11 @@ + +licenses(["notice"]) + +cc_binary( + name = "annotated_injection", + srcs = glob([ + "*.cpp", + "*.h", + ]), + deps = ["//third_party/fruit"], +) diff --git a/examples/annotated_injection/CMakeLists.txt b/examples/annotated_injection/CMakeLists.txt new file mode 100644 index 0000000..5fbf754 --- /dev/null +++ b/examples/annotated_injection/CMakeLists.txt @@ -0,0 +1,10 @@ + +set(ANNOTATED_INJECTION_SOURCES +main.cpp +car.cpp +main_brake.cpp +emergency_brake.cpp +) + +add_executable(annotated_injection ${ANNOTATED_INJECTION_SOURCES}) +target_link_libraries(annotated_injection fruit) diff --git a/examples/annotated_injection/brake.h b/examples/annotated_injection/brake.h new file mode 100644 index 0000000..3cfaf52 --- /dev/null +++ b/examples/annotated_injection/brake.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 BRAKE_H +#define BRAKE_H + +class Brake { +public: + // Activates the brake. Throws an exception if braking failed. + virtual void activate() = 0; +}; + +#endif // BRAKE_H diff --git a/examples/annotated_injection/car.cpp b/examples/annotated_injection/car.cpp new file mode 100644 index 0000000..605a4de --- /dev/null +++ b/examples/annotated_injection/car.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "car.h" +#include "emergency_brake.h" +#include "main_brake.h" + +class CarImpl : public Car { +private: + Brake* mainBrake; + Brake* emergencyBrake; + +public: + INJECT(CarImpl(ANNOTATED(MainBrake, Brake*) mainBrake, ANNOTATED(EmergencyBrake, Brake*) emergencyBrake)) + : mainBrake(mainBrake), emergencyBrake(emergencyBrake) {} + + void brake() override { + try { + mainBrake->activate(); + } catch (...) { + // The main brake failed! + emergencyBrake->activate(); + } + } +}; + +fruit::Component<Car> getCarComponent() { + return fruit::createComponent() + .bind<Car, CarImpl>() + .install(getMainBrakeComponent) + .install(getEmergencyBrakeComponent); +} diff --git a/examples/annotated_injection/car.h b/examples/annotated_injection/car.h new file mode 100644 index 0000000..4b25043 --- /dev/null +++ b/examples/annotated_injection/car.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 CAR_H +#define CAR_H + +#include <fruit/fruit.h> + +class Car { +public: + virtual void brake() = 0; +}; + +fruit::Component<Car> getCarComponent(); + +#endif // CAR_H diff --git a/examples/annotated_injection/emergency_brake.cpp b/examples/annotated_injection/emergency_brake.cpp new file mode 100644 index 0000000..f78a67f --- /dev/null +++ b/examples/annotated_injection/emergency_brake.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "emergency_brake.h" + +class EmergencyBrakeImpl : public Brake { +public: + INJECT(EmergencyBrakeImpl()) = default; + + void activate() override { + // ... + } +}; + +fruit::Component<fruit::Annotated<EmergencyBrake, Brake>> getEmergencyBrakeComponent() { + return fruit::createComponent().bind<fruit::Annotated<EmergencyBrake, Brake>, EmergencyBrakeImpl>(); +} diff --git a/examples/annotated_injection/emergency_brake.h b/examples/annotated_injection/emergency_brake.h new file mode 100644 index 0000000..c5650b4 --- /dev/null +++ b/examples/annotated_injection/emergency_brake.h @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 EMERGENCY_BRAKE_H +#define EMERGENCY_BRAKE_H + +#include "brake.h" + +#include <fruit/fruit.h> + +// This type is not meaningful by itself, it's only used for annotated injection. +// This marks a the Brake instance that represents the main brake. +struct EmergencyBrake {}; + +fruit::Component<fruit::Annotated<EmergencyBrake, Brake>> getEmergencyBrakeComponent(); + +#endif // EMERGENCY_BRAKE_H diff --git a/examples/annotated_injection/main.cpp b/examples/annotated_injection/main.cpp new file mode 100644 index 0000000..f054717 --- /dev/null +++ b/examples/annotated_injection/main.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "car.h" + +using fruit::Injector; + +int main() { + Injector<Car> injector(getCarComponent); + Car* car(injector); + + car->brake(); + + return 0; +} diff --git a/examples/annotated_injection/main_brake.cpp b/examples/annotated_injection/main_brake.cpp new file mode 100644 index 0000000..722368c --- /dev/null +++ b/examples/annotated_injection/main_brake.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "main_brake.h" + +class MainBrakeImpl : public Brake { +public: + INJECT(MainBrakeImpl()) = default; + + void activate() override { + // ... + } +}; + +fruit::Component<fruit::Annotated<MainBrake, Brake>> getMainBrakeComponent() { + return fruit::createComponent().bind<fruit::Annotated<MainBrake, Brake>, MainBrakeImpl>(); +} diff --git a/examples/annotated_injection/main_brake.h b/examples/annotated_injection/main_brake.h new file mode 100644 index 0000000..34730e9 --- /dev/null +++ b/examples/annotated_injection/main_brake.h @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 MAIN_BRAKE_H +#define MAIN_BRAKE_H + +#include "brake.h" + +#include <fruit/fruit.h> + +// This type is not meaningful by itself, it's only used for annotated injection. +// This marks a the Brake instance that represents the main brake. +struct MainBrake {}; + +fruit::Component<fruit::Annotated<MainBrake, Brake>> getMainBrakeComponent(); + +#endif // MAIN_BRAKE_H diff --git a/examples/hello_world/BUILD b/examples/hello_world/BUILD new file mode 100644 index 0000000..c11a74d --- /dev/null +++ b/examples/hello_world/BUILD @@ -0,0 +1,8 @@ + +licenses(["notice"]) + +cc_binary( + name = "hello_world", + srcs = ["main.cpp"], + deps = ["//third_party/fruit"], +) diff --git a/examples/hello_world/CMakeLists.txt b/examples/hello_world/CMakeLists.txt new file mode 100644 index 0000000..5a154ad --- /dev/null +++ b/examples/hello_world/CMakeLists.txt @@ -0,0 +1,7 @@ + +set(HELLO_WORLD_SOURCES +main.cpp +) + +add_executable(hello ${HELLO_WORLD_SOURCES}) +target_link_libraries(hello fruit) diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp new file mode 100644 index 0000000..4364993 --- /dev/null +++ b/examples/hello_world/main.cpp @@ -0,0 +1,55 @@ + +#include <fruit/fruit.h> +#include <iostream> + +using fruit::Component; +using fruit::Injector; + +class Writer { +public: + virtual void write(std::string s) = 0; +}; + +class StdoutWriter : public Writer { +public: + // Like "StdoutWriter() = default;" but also marks this constructor as the + // one to use for injection. + INJECT(StdoutWriter()) = default; + + virtual void write(std::string s) override { + std::cout << s; + } +}; + +class Greeter { +public: + virtual void greet() = 0; +}; + +class GreeterImpl : public Greeter { +private: + Writer* writer; + +public: + // Like "GreeterImpl(Writer* writer) {...}" but also marks this constructor + // as the one to use for injection. + INJECT(GreeterImpl(Writer* writer)) : writer(writer) {} + + virtual void greet() override { + writer->write("Hello world!\n"); + } +}; + +Component<Greeter> getGreeterComponent() { + return fruit::createComponent().bind<Writer, StdoutWriter>().bind<Greeter, GreeterImpl>(); +} + +int main() { + + Injector<Greeter> injector(getGreeterComponent); + Greeter* greeter = injector.get<Greeter*>(); + + greeter->greet(); + + return 0; +} diff --git a/examples/multibindings/BUILD b/examples/multibindings/BUILD new file mode 100644 index 0000000..1750e1d --- /dev/null +++ b/examples/multibindings/BUILD @@ -0,0 +1,8 @@ + +licenses(["notice"]) + +cc_binary( + name = "multibindings", + srcs = ["main.cpp"], + deps = ["//third_party/fruit"], +) diff --git a/examples/multibindings/CMakeLists.txt b/examples/multibindings/CMakeLists.txt new file mode 100644 index 0000000..fd0c06d --- /dev/null +++ b/examples/multibindings/CMakeLists.txt @@ -0,0 +1,7 @@ + +set(MULTIBINDINGS_SOURCES +main.cpp +) + +add_executable(multibindings ${MULTIBINDINGS_SOURCES}) +target_link_libraries(multibindings fruit) diff --git a/examples/multibindings/main.cpp b/examples/multibindings/main.cpp new file mode 100644 index 0000000..bc34240 --- /dev/null +++ b/examples/multibindings/main.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 <fruit/fruit.h> +#include <iostream> + +using fruit::Component; +using fruit::Injector; + +class Listener { +public: + virtual void notify() = 0; +}; + +class Listener1 : public Listener { +public: + INJECT(Listener1()) = default; + + void notify() override { + std::cout << "Listener 1 notified" << std::endl; + } +}; + +class Writer { +public: + virtual void write(std::string s) = 0; +}; + +// To show that we can inject parameters of multibindings +class StdoutWriter : public Writer { +public: + INJECT(StdoutWriter()) = default; + + void write(std::string s) override { + std::cout << s << std::endl; + } +}; + +class Listener2 : public Listener { +private: + Writer* writer; + +public: + INJECT(Listener2(Writer* writer)) : writer(writer) {} + + void notify() override { + writer->write("Listener 2 notified"); + } +}; + +Component<> getListenersComponent() { + // Here they are in the same component to keep it simple, but Fruit collects all multibindings in installed + // components. + return fruit::createComponent() + .bind<Writer, StdoutWriter>() + .addMultibinding<Listener, Listener1>() + .addMultibinding<Listener, Listener2>(); +} + +int main() { + Injector<> injector(getListenersComponent); + std::vector<Listener*> listeners = injector.getMultibindings<Listener>(); + + // The order of the returned listeners is unspecified, so the lines in output may have any order. + for (Listener* listener : listeners) { + listener->notify(); + } + + return 0; +} diff --git a/examples/scaling_doubles/BUILD b/examples/scaling_doubles/BUILD new file mode 100644 index 0000000..a9d6e85 --- /dev/null +++ b/examples/scaling_doubles/BUILD @@ -0,0 +1,11 @@ + +licenses(["notice"]) + +cc_binary( + name = "scaling_doubles", + srcs = glob([ + "*.cpp", + "*.h", + ]), + deps = ["//third_party/fruit"], +) diff --git a/examples/scaling_doubles/CMakeLists.txt b/examples/scaling_doubles/CMakeLists.txt new file mode 100644 index 0000000..4a0472e --- /dev/null +++ b/examples/scaling_doubles/CMakeLists.txt @@ -0,0 +1,9 @@ + +set(SCALING_SOURCES +main.cpp +scaler.cpp +multiplier.cpp +) + +add_executable(scaling ${SCALING_SOURCES}) +target_link_libraries(scaling fruit) diff --git a/examples/scaling_doubles/main.cpp b/examples/scaling_doubles/main.cpp new file mode 100644 index 0000000..d4d733a --- /dev/null +++ b/examples/scaling_doubles/main.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 <iostream> + +#include "scaler.h" + +using fruit::Injector; + +int main() { + Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + + return 0; +} diff --git a/examples/scaling_doubles/multiplier.cpp b/examples/scaling_doubles/multiplier.cpp new file mode 100644 index 0000000..be0d349 --- /dev/null +++ b/examples/scaling_doubles/multiplier.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "multiplier.h" + +class MultiplierImpl : public Multiplier { +public: + double multiply(double x, double y) override { + return x * y; + } +}; + +fruit::Component<Multiplier> getMultiplierComponent() { + return fruit::createComponent().bind<Multiplier, MultiplierImpl>().registerConstructor<MultiplierImpl()>(); +} diff --git a/examples/scaling_doubles/multiplier.h b/examples/scaling_doubles/multiplier.h new file mode 100644 index 0000000..ae22c3d --- /dev/null +++ b/examples/scaling_doubles/multiplier.h @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 MULTIPLIER_H +#define MULTIPLIER_H + +#include <fruit/fruit.h> + +class Multiplier { +public: + // Returns the product of x and y. + virtual double multiply(double x, double y) = 0; +}; + +fruit::Component<Multiplier> getMultiplierComponent(); + +#endif // MULTIPLIER_H diff --git a/examples/scaling_doubles/scaler.cpp b/examples/scaling_doubles/scaler.cpp new file mode 100644 index 0000000..32838bd --- /dev/null +++ b/examples/scaling_doubles/scaler.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "scaler.h" +#include "multiplier.h" + +using fruit::Component; +using fruit::Injector; +using fruit::createComponent; + +class ScalerImpl : public Scaler { +private: + Multiplier* multiplier; + double factor; + +public: + INJECT(ScalerImpl(ASSISTED(double) factor, Multiplier* multiplier)) : multiplier(multiplier), factor(factor) {} + + double scale(double x) override { + return multiplier->multiply(x, factor); + } +}; + +Component<ScalerFactory> getScalerComponent() { + return createComponent().bind<Scaler, ScalerImpl>().install(getMultiplierComponent); +} diff --git a/examples/scaling_doubles/scaler.h b/examples/scaling_doubles/scaler.h new file mode 100644 index 0000000..9011e81 --- /dev/null +++ b/examples/scaling_doubles/scaler.h @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SCALER_H +#define SCALER_H + +#include <fruit/fruit.h> + +class Scaler { +public: + virtual double scale(double x) = 0; + + virtual ~Scaler() = default; +}; + +using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + +fruit::Component<ScalerFactory> getScalerComponent(); + +#endif // SCALER_H diff --git a/examples/server/BUILD b/examples/server/BUILD new file mode 100644 index 0000000..1888614 --- /dev/null +++ b/examples/server/BUILD @@ -0,0 +1,12 @@ + +licenses(["notice"]) + +cc_binary( + name = "server", + srcs = glob([ + "*.cpp", + "*.h", + ]), + linkopts = ["-pthread"], + deps = ["//third_party/fruit"], +) diff --git a/examples/server/CMakeLists.txt b/examples/server/CMakeLists.txt new file mode 100644 index 0000000..9e61837 --- /dev/null +++ b/examples/server/CMakeLists.txt @@ -0,0 +1,13 @@ + +set(SERVER_SOURCES +main.cpp +foo_handler.cpp +bar_handler.cpp +request_dispatcher.cpp +server.cpp +) + +add_definitions("-pthread") + +add_executable(server ${SERVER_SOURCES}) +target_link_libraries(server fruit pthread) diff --git a/examples/server/bar_handler.cpp b/examples/server/bar_handler.cpp new file mode 100644 index 0000000..c70b9cc --- /dev/null +++ b/examples/server/bar_handler.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "bar_handler.h" + +#include <iostream> + +using namespace std; +using namespace fruit; + +class BarHandlerImpl : public BarHandler { +private: + const Request& request; + const ServerContext& serverContext; + +public: + INJECT(BarHandlerImpl(const Request& request, const ServerContext& serverContext)) + : request(request), serverContext(serverContext) {} + + void handleRequest() override { + cout << "BarHandler handling request on server started at " << serverContext.startupTime + << " for path: " << request.path << endl; + } +}; + +Component<Required<Request, ServerContext>, BarHandler> getBarHandlerComponent() { + return fruit::createComponent().bind<BarHandler, BarHandlerImpl>(); +} diff --git a/examples/server/bar_handler.h b/examples/server/bar_handler.h new file mode 100644 index 0000000..475d8f5 --- /dev/null +++ b/examples/server/bar_handler.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 BAR_HANDLER_H +#define BAR_HANDLER_H + +#include "request.h" +#include "server_context.h" + +#include <fruit/fruit.h> + +class BarHandler { +public: + // Handles a request for a subpath of "/bar/". + // The request is injected, no need to pass it directly here. + virtual void handleRequest() = 0; +}; + +fruit::Component<fruit::Required<Request, ServerContext>, BarHandler> getBarHandlerComponent(); + +#endif // BAR_HANDLER_H diff --git a/examples/server/foo_handler.cpp b/examples/server/foo_handler.cpp new file mode 100644 index 0000000..db1261c --- /dev/null +++ b/examples/server/foo_handler.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "foo_handler.h" + +#include <iostream> + +using namespace std; +using namespace fruit; + +class FooHandlerImpl : public FooHandler { +private: + const Request& request; + const ServerContext& serverContext; + +public: + INJECT(FooHandlerImpl(const Request& request, const ServerContext& serverContext)) + : request(request), serverContext(serverContext) {} + + void handleRequest() override { + cout << "FooHandler handling request on server started at " << serverContext.startupTime + << " for path: " << request.path << endl; + } +}; + +Component<Required<Request, ServerContext>, FooHandler> getFooHandlerComponent() { + return fruit::createComponent().bind<FooHandler, FooHandlerImpl>(); +} diff --git a/examples/server/foo_handler.h b/examples/server/foo_handler.h new file mode 100644 index 0000000..02d21ee --- /dev/null +++ b/examples/server/foo_handler.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FOO_HANDLER_H +#define FOO_HANDLER_H + +#include "request.h" +#include "server_context.h" + +#include <fruit/fruit.h> + +class FooHandler { +public: + // Handles a request for a subpath of "/foo/". + // The request is injected, no need to pass it directly here. + virtual void handleRequest() = 0; +}; + +fruit::Component<fruit::Required<Request, ServerContext>, FooHandler> getFooHandlerComponent(); + +#endif // FOO_HANDLER_H diff --git a/examples/server/main.cpp b/examples/server/main.cpp new file mode 100644 index 0000000..a1804e6 --- /dev/null +++ b/examples/server/main.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 <fruit/fruit.h> + +#include "request_dispatcher.h" +#include "server.h" + +using namespace fruit; + +int main() { + Injector<Server> injector(getServerComponent); + + Server* server(injector); + server->run(getRequestDispatcherComponent); + + return 0; +} diff --git a/examples/server/request.h b/examples/server/request.h new file mode 100644 index 0000000..b09cf93 --- /dev/null +++ b/examples/server/request.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 REQUEST_H +#define REQUEST_H + +#include <string> + +struct Request { + std::string path; +}; + +#endif // REQUEST_H diff --git a/examples/server/request_dispatcher.cpp b/examples/server/request_dispatcher.cpp new file mode 100644 index 0000000..f63ca0b --- /dev/null +++ b/examples/server/request_dispatcher.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "request_dispatcher.h" + +#include "bar_handler.h" +#include "foo_handler.h" + +#include <iostream> + +using namespace std; +using namespace fruit; + +class RequestDispatcherImpl : public RequestDispatcher { +private: + const Request& request; + // We hold providers here for lazy injection; we only want to inject the handler that is actually used for the + // request. + // In a large system, there will be many handlers, and many will have lots of dependencies that also have to be + // injected. + Provider<FooHandler> fooHandler; + Provider<BarHandler> barHandler; + +public: + INJECT(RequestDispatcherImpl(const Request& request, Provider<FooHandler> fooHandler, + Provider<BarHandler> barHandler)) + : request(request), fooHandler(fooHandler), barHandler(barHandler) {} + + void handleRequest() override { + if (stringStartsWith(request.path, "/foo/")) { + fooHandler.get()->handleRequest(); + } else if (stringStartsWith(request.path, "/bar/")) { + barHandler.get()->handleRequest(); + } else { + cerr << "Error: no handler found for request path: '" << request.path << "' , ignoring request." << endl; + } + } + +private: + static bool stringStartsWith(const string& s, const string& candidatePrefix) { + return s.compare(0, candidatePrefix.size(), candidatePrefix) == 0; + } +}; + +Component<Required<Request, ServerContext>, RequestDispatcher> getRequestDispatcherComponent() { + return createComponent() + .bind<RequestDispatcher, RequestDispatcherImpl>() + .install(getFooHandlerComponent) + .install(getBarHandlerComponent); +} diff --git a/examples/server/request_dispatcher.h b/examples/server/request_dispatcher.h new file mode 100644 index 0000000..5580ce1 --- /dev/null +++ b/examples/server/request_dispatcher.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 REQUEST_DISPATCHER_H +#define REQUEST_DISPATCHER_H + +#include "request.h" +#include "server_context.h" + +#include <fruit/fruit.h> + +class RequestDispatcher { +public: + // Handles the current request. + // The request is injected, no need to pass it directly here. + virtual void handleRequest() = 0; +}; + +fruit::Component<fruit::Required<Request, ServerContext>, RequestDispatcher> getRequestDispatcherComponent(); + +#endif // REQUEST_DISPATCHER_H diff --git a/examples/server/server.cpp b/examples/server/server.cpp new file mode 100644 index 0000000..60e6853 --- /dev/null +++ b/examples/server/server.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "server.h" + +#include <ctime> +#include <iostream> +#include <thread> + +using namespace std; +using namespace fruit; + +class ServerImpl : public Server { +private: + std::vector<std::thread> threads; + +public: + INJECT(ServerImpl()) {} + + ~ServerImpl() { + for (std::thread& t : threads) { + t.join(); + } + } + + void run(Component<Required<Request, ServerContext>, RequestDispatcher> (*getRequestDispatcherComponent)()) override { + ServerContext serverContext; + serverContext.startupTime = getTime(); + + const NormalizedComponent<Required<Request>, RequestDispatcher> requestDispatcherNormalizedComponent( + getRequestDispatcherComponentWithContext, getRequestDispatcherComponent, &serverContext); + + cerr << "Server started." << endl; + + while (1) { + cerr << endl; + cerr << "Enter the request (absolute path starting with \"/foo/\" or \"/bar/\"), or an empty line to exit." + << endl; + Request request; + getline(cin, request.path); + cerr << "Server received request: " + request.path << endl; + if (request.path.empty()) { + cerr << "Server received empty line, shutting down." << endl; + break; + } + + // In production code we would use a thread pool. + // Here we spawn a new thread each time to keep it simple. + threads.push_back(std::thread(worker_thread_main, std::ref(requestDispatcherNormalizedComponent), request)); + } + } + +private: + static void worker_thread_main( + const NormalizedComponent<Required<Request>, RequestDispatcher>& requestDispatcherNormalizedComponent, + Request request) { + Injector<RequestDispatcher> injector(requestDispatcherNormalizedComponent, getRequestComponent, &request); + + RequestDispatcher* requestDispatcher(injector); + requestDispatcher->handleRequest(); + } + + static string getTime() { + time_t now = time(nullptr); + tm* localTime = localtime(&now); + string result = asctime(localTime); + if (result.size() != 0 && result.back() == '\n') { + result.pop_back(); + } + return result; + } + + static Component<Request> getRequestComponent(Request* request) { + return createComponent().bindInstance(*request); + } + + static Component<Required<Request>, RequestDispatcher> getRequestDispatcherComponentWithContext( + Component<Required<Request, ServerContext>, RequestDispatcher> (*getRequestDispatcherComponent)(), + ServerContext* serverContext) { + return createComponent().install(getRequestDispatcherComponent).bindInstance(*serverContext); + } +}; + +fruit::Component<Server> getServerComponent() { + return fruit::createComponent().bind<Server, ServerImpl>(); +} diff --git a/examples/server/server.h b/examples/server/server.h new file mode 100644 index 0000000..b97c55c --- /dev/null +++ b/examples/server/server.h @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SERVER_H +#define SERVER_H + +#include "request.h" +#include "request_dispatcher.h" +#include "server_context.h" + +#include <fruit/fruit.h> +#include <string> + +class Server { +public: + virtual void run(fruit::Component<fruit::Required<Request, ServerContext>, RequestDispatcher> ( + *getrequestDispatcherComponent)()) = 0; +}; + +fruit::Component<Server> getServerComponent(); + +#endif // SERVER_H diff --git a/examples/server/server_context.h b/examples/server/server_context.h new file mode 100644 index 0000000..3da38dc --- /dev/null +++ b/examples/server/server_context.h @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SERVER_CONTEXT_H +#define SERVER_CONTEXT_H + +#include <fruit/fruit.h> +#include <string> + +struct ServerContext { + // This is to show that the requests can get non-request-specific information. + std::string startupTime; +}; + +#endif // SERVER_CONTEXT_H diff --git a/examples/simple_injection/BUILD b/examples/simple_injection/BUILD new file mode 100644 index 0000000..4c2cf42 --- /dev/null +++ b/examples/simple_injection/BUILD @@ -0,0 +1,27 @@ + +licenses(["notice"]) + +cc_library( + name = "simple_injection_lib", + srcs = glob(["*.cpp"], exclude = ["main*.cpp"]), + hdrs = glob(["*.h"]), + deps = ["//third_party/fruit"], +) + +cc_binary( + name = "simple_injection", + srcs = ["main.cpp"], + deps = [ + ":simple_injection_lib", + "//third_party/fruit", + ], +) + +cc_binary( + name = "simple_injection_v1", + srcs = ["main_v1.cpp"], + deps = [ + ":simple_injection_lib", + "//third_party/fruit", + ], +) diff --git a/examples/simple_injection/CMakeLists.txt b/examples/simple_injection/CMakeLists.txt new file mode 100644 index 0000000..738dd3b --- /dev/null +++ b/examples/simple_injection/CMakeLists.txt @@ -0,0 +1,14 @@ + +set(INCREMENTER_SOURCES +checked_adder.cpp +checked_incrementer.cpp +incrementer_impl.cpp +incrementer_component.cpp +simple_adder.cpp +simple_incrementer.cpp +) + +add_executable(incrementer_v1 ${INCREMENTER_SOURCES} main_v1.cpp) +add_executable(incrementer ${INCREMENTER_SOURCES} main.cpp) +target_link_libraries(incrementer_v1 fruit) +target_link_libraries(incrementer fruit) diff --git a/examples/simple_injection/adder.h b/examples/simple_injection/adder.h new file mode 100644 index 0000000..111c5da --- /dev/null +++ b/examples/simple_injection/adder.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 ADDER_H +#define ADDER_H + +class Adder { +public: + // Returns the sum of x and y. + virtual int add(int x, int y) = 0; +}; + +#endif // ADDER_H diff --git a/examples/simple_injection/checked_adder.cpp b/examples/simple_injection/checked_adder.cpp new file mode 100644 index 0000000..7ca6754 --- /dev/null +++ b/examples/simple_injection/checked_adder.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "checked_adder.h" + +#include <climits> +#include <iostream> + +class CheckedAdder : public Adder { +private: + bool add_overflows(int x, int y) { + if (y > x) + std::swap(x, y); + // Now y <= x. + const int half_max = INT_MAX / 2; + const int half_min = INT_MIN / 2; + if (x > half_max) { + // We can't have negative overflow, but might have positive overflow. + if (y > half_max) + return true; + if (y <= 0) + return false; + // x <= INT_MAX && y <= half_max, + // so: x + y <= INT_MAX + half_max + // so: x - half_max + y <= INT_MAX + // so: (x - half_max + y) doesn't overflow. + // (x + y) > INT_MAX iff (x - half_max + y) > (INT_MAX - half_max) + return (x - half_max + y) > (INT_MAX - half_max); + } + // y <= x <= half_max, can't have positive overflow. + if (y < half_min) { + // We can't have positive overflow, but might have negative overflow. + if (x < half_min) + return true; + if (x >= 0) + return false; + // y >= INT_MIN && x >= half_min, + // so: y + x >= INT_MIN + half_min + // so: y - half_min + x >= INT_MAX + // so: (y - half_min + x) doesn't overflow. + // (y + x) < INT_MIN iff (y - half_min + x) < (INT_MIN - half_min) + return (y - half_min + x) < (INT_MIN - half_min); + } + // Neither negative nor positive overflow. + return false; + } + +public: + INJECT(CheckedAdder()) = default; + + virtual int add(int x, int y) override { + if (add_overflows(x, y)) { + std::cerr << "CheckedAdder: detected overflow during addition of " << x << " and " << y << std::endl; + abort(); + } + return x + y; + } +}; + +fruit::Component<Adder> getCheckedAdderComponent() { + return fruit::createComponent().bind<Adder, CheckedAdder>(); +} diff --git a/examples/simple_injection/checked_adder.h b/examples/simple_injection/checked_adder.h new file mode 100644 index 0000000..e3d9cc3 --- /dev/null +++ b/examples/simple_injection/checked_adder.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 CHECKED_ADDER_H +#define CHECKED_ADDER_H + +#include "adder.h" +#include <fruit/fruit.h> + +fruit::Component<Adder> getCheckedAdderComponent(); + +#endif // CHECKED_ADDER_H diff --git a/examples/simple_injection/checked_incrementer.cpp b/examples/simple_injection/checked_incrementer.cpp new file mode 100644 index 0000000..2ba166f --- /dev/null +++ b/examples/simple_injection/checked_incrementer.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "checked_incrementer.h" + +#include "checked_adder.h" +#include "incrementer_impl.h" + +fruit::Component<Incrementer> getCheckedIncrementerComponent() { + return fruit::createComponent().install(getIncrementerImplComponent).install(getCheckedAdderComponent); +} diff --git a/examples/simple_injection/checked_incrementer.h b/examples/simple_injection/checked_incrementer.h new file mode 100644 index 0000000..0394a1f --- /dev/null +++ b/examples/simple_injection/checked_incrementer.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 CHECKED_INCREMENTER_H +#define CHECKED_INCREMENTER_H + +#include "incrementer.h" +#include <fruit/fruit.h> + +fruit::Component<Incrementer> getCheckedIncrementerComponent(); + +#endif // CHECKED_INCREMENTER_H diff --git a/examples/simple_injection/incrementer.h b/examples/simple_injection/incrementer.h new file mode 100644 index 0000000..373d28c --- /dev/null +++ b/examples/simple_injection/incrementer.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 INCREMENTER_H +#define INCREMENTER_H + +class Incrementer { +public: + // Returns x + 1. + virtual int increment(int x) = 0; +}; + +#endif // INCREMENTER_H diff --git a/examples/simple_injection/incrementer_component.cpp b/examples/simple_injection/incrementer_component.cpp new file mode 100644 index 0000000..534ee81 --- /dev/null +++ b/examples/simple_injection/incrementer_component.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "incrementer_component.h" + +#include "checked_incrementer.h" +#include "simple_incrementer.h" + +fruit::Component<Incrementer> getIncrementerComponent(bool checked) { + if (checked) + return getCheckedIncrementerComponent(); + else + return getSimpleIncrementerComponent(); +} diff --git a/examples/simple_injection/incrementer_component.h b/examples/simple_injection/incrementer_component.h new file mode 100644 index 0000000..8d25212 --- /dev/null +++ b/examples/simple_injection/incrementer_component.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 INCREMENTER_COMPONENT_H +#define INCREMENTER_COMPONENT_H + +#include "incrementer.h" + +#include <fruit/fruit.h> + +fruit::Component<Incrementer> getIncrementerComponent(bool checked); + +#endif // INCREMENTER_COMPONENT_H diff --git a/examples/simple_injection/incrementer_impl.cpp b/examples/simple_injection/incrementer_impl.cpp new file mode 100644 index 0000000..fb2bf2b --- /dev/null +++ b/examples/simple_injection/incrementer_impl.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "incrementer_impl.h" + +class IncrementerImpl : public Incrementer { +private: + Adder* adder; + +public: + INJECT(IncrementerImpl(Adder* adder)) : adder(adder) {} + + virtual int increment(int x) override { + return adder->add(x, 1); + } +}; + +fruit::Component<fruit::Required<Adder>, Incrementer> getIncrementerImplComponent() { + return fruit::createComponent().bind<Incrementer, IncrementerImpl>(); +} diff --git a/examples/simple_injection/incrementer_impl.h b/examples/simple_injection/incrementer_impl.h new file mode 100644 index 0000000..1e4df49 --- /dev/null +++ b/examples/simple_injection/incrementer_impl.h @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 INCREMENTER_IMPL_H +#define INCREMENTER_IMPL_H + +#include "adder.h" +#include "incrementer.h" +#include <fruit/fruit.h> + +fruit::Component<fruit::Required<Adder>, Incrementer> getIncrementerImplComponent(); + +#endif // INCREMENTER_IMPL_H diff --git a/examples/simple_injection/main.cpp b/examples/simple_injection/main.cpp new file mode 100644 index 0000000..f97793e --- /dev/null +++ b/examples/simple_injection/main.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "incrementer_component.h" +#include <iostream> + +using fruit::Component; +using fruit::Injector; + +// Try e.g.: +// echo 5 | ./incrementer +// echo 2147483647 | ./incrementer +// echo 2147483647 | ./incrementer --checked +int main(int argc, const char* argv[]) { + + bool checked = false; + + if (argc == 2 && std::string(argv[1]) == "--checked") + checked = true; + + Injector<Incrementer> injector(getIncrementerComponent, checked); + Incrementer* incrementer(injector); + + int x; + std::cin >> x; + std::cout << incrementer->increment(x) << std::endl; + + return 0; +} diff --git a/examples/simple_injection/main_v1.cpp b/examples/simple_injection/main_v1.cpp new file mode 100644 index 0000000..ddcd5a0 --- /dev/null +++ b/examples/simple_injection/main_v1.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "simple_incrementer.h" +#include <iostream> + +using fruit::Component; +using fruit::Injector; + +// Try e.g.: +// echo 5 | ./incrementer +// echo 2147483647 | ./incrementer +int main() { + + Injector<Incrementer> injector(getSimpleIncrementerComponent); + Incrementer* incrementer = injector.get<Incrementer*>(); + + int x; + std::cin >> x; + std::cout << incrementer->increment(x) << std::endl; + + return 0; +} diff --git a/examples/simple_injection/simple_adder.cpp b/examples/simple_injection/simple_adder.cpp new file mode 100644 index 0000000..8277d23 --- /dev/null +++ b/examples/simple_injection/simple_adder.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "simple_adder.h" + +class SimpleAdder : public Adder { +public: + INJECT(SimpleAdder()) = default; + + virtual int add(int x, int y) override { + return x + y; + } +}; + +fruit::Component<Adder> getSimpleAdderComponent() { + return fruit::createComponent().bind<Adder, SimpleAdder>(); +} diff --git a/examples/simple_injection/simple_adder.h b/examples/simple_injection/simple_adder.h new file mode 100644 index 0000000..9b75a79 --- /dev/null +++ b/examples/simple_injection/simple_adder.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SIMPLE_ADDER_H +#define SIMPLE_ADDER_H + +#include "adder.h" +#include <fruit/fruit.h> + +fruit::Component<Adder> getSimpleAdderComponent(); + +#endif // SIMPLE_ADDER_H diff --git a/examples/simple_injection/simple_incrementer.cpp b/examples/simple_injection/simple_incrementer.cpp new file mode 100644 index 0000000..2ecf4a2 --- /dev/null +++ b/examples/simple_injection/simple_incrementer.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 "simple_incrementer.h" + +#include "incrementer_impl.h" +#include "simple_adder.h" + +fruit::Component<Incrementer> getSimpleIncrementerComponent() { + return fruit::createComponent().install(getIncrementerImplComponent).install(getSimpleAdderComponent); +} diff --git a/examples/simple_injection/simple_incrementer.h b/examples/simple_injection/simple_incrementer.h new file mode 100644 index 0000000..ac6e3a7 --- /dev/null +++ b/examples/simple_injection/simple_incrementer.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SIMPLE_INCREMENTER_H +#define SIMPLE_INCREMENTER_H + +#include "incrementer.h" +#include <fruit/fruit.h> + +fruit::Component<Incrementer> getSimpleIncrementerComponent(); + +#endif // SIMPLE_INCREMENTER_H diff --git a/extras/CMakeLists.txt b/extras/CMakeLists.txt new file mode 100644 index 0000000..49af9ab --- /dev/null +++ b/extras/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_subdirectory(doc EXCLUDE_FROM_ALL) +add_subdirectory(packaging EXCLUDE_FROM_ALL) +add_subdirectory(benchmark) diff --git a/extras/bazel_root/WORKSPACE b/extras/bazel_root/WORKSPACE new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/extras/bazel_root/WORKSPACE diff --git a/extras/bazel_root/third_party/fruit/BUILD b/extras/bazel_root/third_party/fruit/BUILD new file mode 120000 index 0000000..388dc92 --- /dev/null +++ b/extras/bazel_root/third_party/fruit/BUILD @@ -0,0 +1 @@ +../../../../BUILD
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/build b/extras/bazel_root/third_party/fruit/build new file mode 120000 index 0000000..388dc92 --- /dev/null +++ b/extras/bazel_root/third_party/fruit/build @@ -0,0 +1 @@ +../../../../BUILD
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/configuration b/extras/bazel_root/third_party/fruit/configuration new file mode 120000 index 0000000..2e678a5 --- /dev/null +++ b/extras/bazel_root/third_party/fruit/configuration @@ -0,0 +1 @@ +../../../../configuration
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/examples b/extras/bazel_root/third_party/fruit/examples new file mode 120000 index 0000000..1929330 --- /dev/null +++ b/extras/bazel_root/third_party/fruit/examples @@ -0,0 +1 @@ +../../../../examples
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/include b/extras/bazel_root/third_party/fruit/include new file mode 120000 index 0000000..0f0436a --- /dev/null +++ b/extras/bazel_root/third_party/fruit/include @@ -0,0 +1 @@ +../../../../include
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/src b/extras/bazel_root/third_party/fruit/src new file mode 120000 index 0000000..b3e266f --- /dev/null +++ b/extras/bazel_root/third_party/fruit/src @@ -0,0 +1 @@ +../../../../src
\ No newline at end of file diff --git a/extras/bazel_root/third_party/fruit/tests b/extras/bazel_root/third_party/fruit/tests new file mode 120000 index 0000000..70ef7f3 --- /dev/null +++ b/extras/bazel_root/third_party/fruit/tests @@ -0,0 +1 @@ +../../../../tests
\ No newline at end of file diff --git a/extras/benchmark/CMakeLists.txt b/extras/benchmark/CMakeLists.txt new file mode 100644 index 0000000..fba9f91 --- /dev/null +++ b/extras/benchmark/CMakeLists.txt @@ -0,0 +1,8 @@ + +# This is just to help IDEs (e.g. CLion) figure out how new_delete_benchmark.cpp is supposed to be built. +add_executable(new_delete_benchmark-dummy-exec EXCLUDE_FROM_ALL new_delete_benchmark.cpp) +target_link_libraries(new_delete_benchmark-dummy-exec fruit) + +# This is just to help IDEs (e.g. CLion) figure out how compile_time_benchmark.cpp is supposed to be built. +add_executable(compile_time_benchmark_executable EXCLUDE_FROM_ALL compile_time_benchmark.cpp) +target_link_libraries(compile_time_benchmark_executable fruit) diff --git a/extras/benchmark/README.md b/extras/benchmark/README.md new file mode 100644 index 0000000..775ba21 --- /dev/null +++ b/extras/benchmark/README.md @@ -0,0 +1,109 @@ + +## Fruit benchmarks + +Fruit contains some code to benchmark various metrics (e.g. performance, compile time, executable size) in an automated +way. + +### Benchmark suites + +The `suites` folder contains suites of Fruit (or Fruit-related) benchmarks that can be run using `run_benchmarks.py`. +For example: + +```bash +$ ~/projects/fruit/extras/benchmark/run_benchmarks.py \ + --continue-benchmark=true \ + --benchmark-definition ~/projects/fruit/extras/benchmark/suites/fruit_full.yml + --output-file ~/fruit_bench_results.txt \ + --fruit-sources-dir ~/projects/fruit \ + --fruit-benchmark-sources-dir ~/projects/fruit \ + --boost-di-sources-dir ~/projects/boost-di +``` + +Once the benchmark run completes, you can format the results using some pre-defined tables, see the section below. + +The following benchmark suites are defined: + +* `fruit_full.yml`: full set of Fruit benchmarks (using the Fruit 3.x API). +* `fruit_mostly_full.yml`: a subset of the tests in `fruit_full.yml`. +* `fruit_quick.yml`: this is an even smaller subset, and the number of runs is capped at 10 so + the confidence intervals might be wider. It's useful as a quicker (around 10-15min) way to get a rough idea of the + performance (e.g. to evaluate the performance impact of a commit, during development). +* `fruit_single.yml`: runs the Fruit runtime benchs under a single compiler and with just 1 combination of flags. This + also caps the number of runs at 8, so the resulting confidence intervals might be wider than they would be with + `fruit_full.yml`. This is a quick benchmark that can used during development of performance optimizations. +* `fruit_debug.yml`: a suite used to debug Fruit's benchmarking code. This is very quick, but the actual results are + not meaningful. Run this after changing any benchmarking code, to check that it still works. +* `fruit_full_old_style.yml`: a variant of `fruit_full.yml` that uses the Fruit 2.x API. +* `fruit_quick_old_style.yml`: a variant of `fruit_quick.yml` that uses the Fruit 2.x API. +* `fruit_single_old_style.yml`: a variant of `fruit_single.yml` that uses the Fruit 2.x API. +* `boost_di`: unlike the others, this benchmark suite exercises the Boost.DI library (still in boost-experimental at the + time of writing) instead of Fruit. + +### Benchmark tables + +The `tables` folder contains some table definitions that can be used to get a human-readable representations of +benchmark results generated using `run_benchmarks.py`. + +Note that there *isn't* a 1:1 mapping between benchmark suites and benchmark tables; the same table definition can be +used with multiple benchmark suites (for example, a full suite and a quick variant that only has a subset of the +dimensions) and multiple table definitions might be appropriate to display the results of a single suite (for example, +there could be a table definition that displays only metrics meaningful to Fruit users and one that also displays +more fine-grained metrics that are only meaningful to Fruit developers). + +Example usage of `format_bench_results.py` with one of these table definitions: + +```bash +$ ~/projects/fruit/extras/benchmark/format_bench_results.py \ + --benchmark-results ~/fruit_bench_results.txt \ + --benchmark-tables-definition ~/projects/fruit/extras/benchmark/tables/fruit_wiki.yml +``` + +It's also possible to compare two benchmark results (for example, when running the same benchmarks before and after +a Fruit commit): + +```bash +$ ~/projects/fruit/extras/benchmark/format_bench_results.py \ + --benchmark-results ~/fruit_bench_results_after.txt \ + --benchmark-tables-definition ~/projects/fruit/extras/benchmark/tables/fruit_wiki.yml \ + --baseline-benchmark-results ~/fruit_bench_results_before.txt +``` + +The following tables are defined: + +* `fruit_wiki.yml`: the "main" table definition, with the tables that are in Fruit's wiki. +* `fruit_wiki_old_style.yml`: a variant of `fruit_wiki.yml` that uses the Fruit 2.x APIs, whereas `fruit_wiki.yml` uses + the new one. This is useful to visualize benchmarks of old Fruit versions that don't support the 3.x new-style API. +* `fruit_internal.yml`: a more detailed version of `fruit_wiki.yml`, also displaying metrics that are only meaningful + to Fruit developers (e.g. splitting the setup time into component creation time and normalization time). +* `fruit_internal_old_vs_new_style.yml`: used to compare the performance of the Fruit 3.x and 2.x APIs. + +### Manual benchmarks + +In some cases, you might want to run the benchmarks manually (e.g. if you want to use `callgrind` to profile the +benchmark run). This is how you can do that: + +```bash +$ cd ~/projects/fruit +$ mkdir build +$ cd build +$ CXX=g++-6 cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo +$ make -j 10 +$ cd .. +$ mkdir generated-benchs +$ extras/benchmark/generate_benchmark.py \ + --compiler g++-6 \ + --fruit-sources-dir ~/projects/fruit \ + --fruit-build-dir ~/projects/fruit/build \ + --num-components-with-no-deps 10 \ + --num-components-with-deps 90 \ + --num-deps 10 \ + --output-dir generated-benchs \ + --generate-debuginfo=true +$ cd generated-benchs +$ make -j 10 +$ valgrind \ + --tool=callgrind \ + --simulate-cache=yes \ + --dump-instr=yes \ + ./main 10000 +``` diff --git a/extras/benchmark/analyze_symbol_size.sh b/extras/benchmark/analyze_symbol_size.sh new file mode 100755 index 0000000..5ca2ea5 --- /dev/null +++ b/extras/benchmark/analyze_symbol_size.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +# This is a quick-and-dirty script to analyze the ELF symbol sizes in a binary generated by generate_benchmark.py. +# It can be used to decide which functions should no longer be inlined, to save space. + +if [ "$#" != "1" ] +then + echo "Usage: $0 <executable_file>" + exit 1 +fi + +FILE1=$(mktemp) +nm --print-size --size-sort --radix=d "$1" | sort -k 2 | c++filt \ +| sed 's/[^ ]* //;s/fruit::impl:://g;s/InvokeConstructorWithInjectedArgVector<.*>::operator()/InvokeConstructorWithInjectedArgVector<...>::operator()/' \ +| sed 's/getComponent[0-9]\+/getComponent$N/' \ +| sed 's/X[0-9]\+/X$N/g' \ +| sed 's/Interface[0-9]\+/Interface$N/g' \ +| sed 's/GetBindingDepsHelper<.*>::operator()()/GetBindingDepsHelper<...>::operator()()/' \ +| sed 's/\(std::shared_ptr<Interface$N>, \)\+std::shared_ptr<Interface$N>/.../' \ +>"$FILE1" + +FILE2=$(mktemp) +python3 -c " +lines = open('$FILE1', 'r').readlines() +total_size = {} +for line in lines: + splits = line.split(' ', maxsplit=1) + total_size[splits[1]] = total_size.get(splits[1], 0) + int(splits[0]) +for key, value in total_size.items(): + print('%s %s' % (value, key)) +" >"$FILE2" + +sort -n "$FILE2" diff --git a/extras/benchmark/boost_di_source_generator.py b/extras/benchmark/boost_di_source_generator.py new file mode 100644 index 0000000..0f96015 --- /dev/null +++ b/extras/benchmark/boost_di_source_generator.py @@ -0,0 +1,91 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + + +class BoostDiSourceGenerator: + def generate_component_header(self, component_index): + template = """ +#ifndef COMPONENT{component_index}_H +#define COMPONENT{component_index}_H + +#include <boost/di.hpp> +#include <memory> + +namespace di = boost::di; + +struct Interface{component_index} {{ + virtual ~Interface{component_index}() = default; +}}; +di::injector<std::shared_ptr<Interface{component_index}>> getComponent{component_index}(); + +#endif // COMPONENT{component_index}_H +""" + return template.format(**locals()) + + def generate_component_source(self, component_index, deps): + include_directives = ''.join(['#include "component%s.h"\n' % index for index in deps + [component_index]]) + + component_deps = ''.join([', std::shared_ptr<Interface%s>' % dep for dep in deps]) + + make_injector_params = ','.join(['\n getComponent%s()' % dep for dep in deps] + + ['\n di::bind<Interface%s>().in(di::singleton).to<X%s>()' % (component_index, component_index)]) + + template = """ +{include_directives} + +struct X{component_index} : public Interface{component_index} {{ + BOOST_DI_INJECT(X{component_index}{component_deps}) {{}} + + virtual ~X{component_index}() = default; +}}; + +di::injector<std::shared_ptr<Interface{component_index}>> getComponent{component_index}() {{ + return di::make_injector({make_injector_params}); +}} +""" + return template.format(**locals()) + + def generate_main(self, toplevel_component): + template = """ +#include "component{toplevel_component}.h" +#include <ctime> +#include <iostream> +#include <cstdlib> +#include <iomanip> +#include <chrono> + +using namespace std; + +int main(int argc, char* argv[]) {{ + if (argc != 2) {{ + std::cout << "Need to specify num_loops as argument." << std::endl; + exit(1); + }} + size_t num_loops = std::atoi(argv[1]); + double perRequestTime = 0; + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < num_loops; i++) {{ + di::injector<std::shared_ptr<Interface{toplevel_component}>> injector(getComponent{toplevel_component}()); + injector.create<std::shared_ptr<Interface{toplevel_component}>>(); + }} + perRequestTime += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + std::cout << std::fixed; + std::cout << std::setprecision(15); + std::cout << "Total for setup = " << 0 << std::endl; + std::cout << "Full injection time = " << perRequestTime / num_loops << std::endl; + std::cout << "Total per request = " << perRequestTime / num_loops << std::endl; + return 0; +}} +""" + return template.format(**locals()) diff --git a/extras/benchmark/compile_time_benchmark.cpp b/extras/benchmark/compile_time_benchmark.cpp new file mode 100644 index 0000000..ddc6fd3 --- /dev/null +++ b/extras/benchmark/compile_time_benchmark.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 <fruit/fruit.h> + +#if MULTIPLIER == 1 +#define REPEAT(X) REPEAT_1(X, _) + +#elif MULTIPLIER == 2 +#define REPEAT(X) REPEAT_2(X, _) + +#elif MULTIPLIER == 4 +#define REPEAT(X) REPEAT_4(X, _) + +#elif MULTIPLIER == 8 +#define REPEAT(X) REPEAT_8(X, _) + +#elif MULTIPLIER == 16 +#define REPEAT(X) REPEAT_16(X, _) + +#elif MULTIPLIER == 32 +#define REPEAT(X) REPEAT_32(X, _) + +#elif MULTIPLIER == 64 +#define REPEAT(X) REPEAT_64(X, _) + +#elif MULTIPLIER == 128 +#define REPEAT(X) REPEAT_128(X, _) + +#elif MULTIPLIER == 256 +#define REPEAT(X) REPEAT_256(X, _) + +#elif MULTIPLIER == 512 +#define REPEAT(X) REPEAT_512(X, _) + +#elif MULTIPLIER == 1024 +#define REPEAT(X) REPEAT_1024(X, _) + +#else +#error Multiplier not supported. +#endif + +#define PLACEHOLDER + +#define EVAL0(...) __VA_ARGS__ +#define EVAL1(...) EVAL0(EVAL0(EVAL0(EVAL0(__VA_ARGS__)))) +#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))) +#define EVAL(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))) + +#define META_REPEAT_2(R, X, I) R PLACEHOLDER(X, I##0) R PLACEHOLDER(X, I##1) + +#define REPEAT_1(X, I) X(I) + +#define REPEAT_2(X, I) META_REPEAT_2(REPEAT_1, X, I) + +#define REPEAT_4(X, I) META_REPEAT_2(REPEAT_2, X, I) + +#define REPEAT_8(X, I) META_REPEAT_2(REPEAT_4, X, I) + +#define REPEAT_16(X, I) META_REPEAT_2(REPEAT_8, X, I) + +#define REPEAT_32(X, I) META_REPEAT_2(REPEAT_16, X, I) + +#define REPEAT_64(X, I) META_REPEAT_2(REPEAT_32, X, I) + +#define REPEAT_128(X, I) META_REPEAT_2(REPEAT_64, X, I) + +#define REPEAT_256(X, I) META_REPEAT_2(REPEAT_128, X, I) + +#define REPEAT_512(X, I) META_REPEAT_2(REPEAT_256, X, I) + +#define REPEAT_1024(X, I) META_REPEAT_2(REPEAT_512, X, I) + +using namespace fruit; + +#define DEFINITIONS(N) \ + struct A##N { \ + INJECT(A##N()) = default; \ + }; \ + \ + struct B##N {}; \ + \ + struct C##N {}; \ + \ + struct I##N { \ + virtual void f() = 0; \ + }; \ + \ + struct X##N : public I##N { \ + INJECT(X##N(A##N, B##N*, const C##N&)){}; \ + \ + virtual void f(); \ + }; \ + \ + struct Y##N {}; \ + \ + struct Z##N {}; \ + \ + Component<Required<Y##N>, Z##N> getZ##N##Component(); + +#define REQUIREMENTS(N) C##N, + +#define PARAMETERS(N) B##N &b##N, + +#ifdef USE_FRUIT_2_X_SYNTAX +#define BINDINGS(N) \ + .bind<I##N, X##N>().bindInstance(b##N).install(getZ##N##Component()).registerProvider([]() { return Y##N(); }) +#else + +#define BINDINGS(N) \ + .bind<I##N, X##N>().bindInstance(b##N).install(getZ##N##Component).registerProvider([]() { return Y##N(); }) +#endif + +EVAL(REPEAT(DEFINITIONS)) + +Component<Required<EVAL(REPEAT(REQUIREMENTS)) int>> getComponent(EVAL(REPEAT(PARAMETERS)) int) { + return createComponent() EVAL(REPEAT(BINDINGS)); +} diff --git a/extras/benchmark/format_bench_results.py b/extras/benchmark/format_bench_results.py new file mode 100755 index 0000000..f95a483 --- /dev/null +++ b/extras/benchmark/format_bench_results.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import argparse +import json +import yaml +from collections import defaultdict + +def extract_results(bench_results, fixed_benchmark_params, column_dimension, row_dimension, result_dimension): + table_data = defaultdict(lambda: dict()) + remaining_dimensions_by_row_column = dict() + for bench_result in bench_results: + try: + params = {dimension_name: make_immutable(dimension_value) + for dimension_name, dimension_value in bench_result['benchmark'].items()} + results = bench_result['results'] + for param_name, param_value in fixed_benchmark_params.items(): + if params.get(param_name) != param_value: + # fixed_benchmark_params not satisfied by this result, skip + break + if result_dimension not in results: + # result_dimension not found in this result, skip + break + params.pop(param_name) + else: + # fixed_benchmark_params were satisfied by these params (and were removed) + assert row_dimension in params.keys(), '%s not in %s' % (row_dimension, params.keys()) + assert column_dimension in params.keys(), '%s not in %s' % (column_dimension, params.keys()) + assert result_dimension in results, '%s not in %s' % (result_dimension, results) + row_value = params[row_dimension] + column_value = params[column_dimension] + remaining_dimensions = params.copy() + remaining_dimensions.pop(row_dimension) + remaining_dimensions.pop(column_dimension) + if column_value in table_data[row_value]: + previous_remaining_dimensions = remaining_dimensions_by_row_column[(row_value, column_value)] + raise Exception( + 'Found multiple benchmark results with the same fixed benchmark params, benchmark param for row and benchmark param for column, so a result can\'t be uniquely determined. ' + + 'Consider adding additional values in fixed_benchmark_params. Remaining dimensions: %s vs %s' % ( + remaining_dimensions, previous_remaining_dimensions)) + table_data[row_value][column_value] = results[result_dimension] + remaining_dimensions_by_row_column[(row_value, column_value)] = remaining_dimensions + except Exception as e: + raise Exception('While processing %s' % bench_result) from e + return table_data + + +def identity(x): + return x + + +# Takes a 2-dimensional array (list of lists) and prints a markdown table with that content. +def print_markdown_table(table_data): + max_content_length_by_column = [max([len(str(row[column_index])) for row in table_data]) + for column_index in range(len(table_data[0]))] + for row_index in range(len(table_data)): + row = table_data[row_index] + cell_strings = [] + for column_index in range(len(row)): + value = str(row[column_index]) + # E.g. if max_content_length_by_column=20, table_cell_format='%20s' + table_cell_format = '%%%ss' % max_content_length_by_column[column_index] + cell_strings += [table_cell_format % value] + print('| ' + ' | '.join(cell_strings) + ' |') + if row_index == 0: + # Print the separator line, e.g. |---|-----|---| + print('|-' + + '-|-'.join(['-' * max_content_length_by_column[column_index] + for column_index in range(len(row))]) + + '-|') + +def compute_min_max(table_data, row_headers, column_headers): + values_by_row = {row_header: [table_data[row_header][column_header] + for column_header in column_headers + if column_header in table_data[row_header]] + for row_header in row_headers} + # We compute min and max and pass it to the value pretty-printer, so that it can determine a unit that works well for all values in the table. + min_in_table = min([min([min(interval[0][0], interval[1][0]) for interval in values_by_row[row_header]]) + for row_header in row_headers]) + max_in_table = max([max([max(interval[0][1], interval[1][1]) for interval in values_by_row[row_header]]) + for row_header in row_headers]) + return (min_in_table, max_in_table) + + +def pretty_print_percentage_difference(baseline_value, current_value): + baseline_min = baseline_value[0] + baseline_max = baseline_value[1] + current_min = current_value[0] + current_max = current_value[1] + percentage_min = (current_min / baseline_max - 1) * 100 + percentage_max = (current_max / baseline_min - 1) * 100 + percentage_min_s = "%+.1f%%" % percentage_min + percentage_max_s = "%+.1f%%" % percentage_max + if percentage_min_s == percentage_max_s: + return percentage_min_s + else: + return "%s - %s" % (percentage_min_s, percentage_max_s) + + +# Takes a table as a dict of dicts (where each table_data[row_key][column_key] is a confidence interval) and prints it as a markdown table using +# the specified pretty print functions for column keys, row keys and values respectively. +# column_header_pretty_printer and row_header_pretty_printer must be functions taking a single value and returning the pretty-printed version. +# value_pretty_printer must be a function taking (value_confidence_interval, min_in_table, max_in_table). +# baseline_table_data is an optional table (similar to table_data) that contains the "before" state. If present, the values in two tables will be compared. +def print_confidence_intervals_table(table_name, + table_data, + baseline_table_data, + column_header_pretty_printer=identity, + row_header_pretty_printer=identity, + value_pretty_printer=identity): + if table_data == {}: + print('%s: (no data)' % table_name) + return + + row_headers = sorted(list(table_data.keys())) + # We need to compute the union of the headers of all rows; some rows might be missing values for certain columns. + column_headers = sorted(set().union(*[list(row_values.keys()) for row_values in table_data.values()])) + if baseline_table_data: + baseline_row_headers = sorted(list(baseline_table_data.keys())) + baseline_column_headers = sorted(set().union(*[list(row_values.keys()) for row_values in baseline_table_data.values()])) + unmached_baseline_column_headers = set(baseline_row_headers) - set(row_headers) + if unmached_baseline_column_headers: + print('Found baseline column headers with no match in new results (they will be ignored): ', unmached_baseline_column_headers) + unmached_baseline_row_headers = set(baseline_row_headers) - set(row_headers) + if unmached_baseline_row_headers: + print('Found baseline row headers with no match in new results (they will be ignored): ', unmached_baseline_row_headers) + + min_in_table, max_in_table = compute_min_max(table_data, row_headers, column_headers) + if baseline_table_data: + min_in_baseline_table, max_in_baseline_table = compute_min_max(table_data, row_headers, column_headers) + min_in_table = min(min_in_table, min_in_baseline_table) + max_in_table = max(max_in_table, max_in_baseline_table) + + table_content = [] + table_content.append([table_name] + [column_header_pretty_printer(column_header) for column_header in column_headers]) + for row_header in row_headers: + row_content = [row_header_pretty_printer(row_header)] + for column_header in column_headers: + if column_header in table_data[row_header]: + value = table_data[row_header][column_header] + raw_confidence_interval, rounded_confidence_interval = value + pretty_printed_value = value_pretty_printer(rounded_confidence_interval, min_in_table, max_in_table) + if baseline_table_data and row_header in baseline_table_data and column_header in baseline_table_data[row_header]: + baseline_value = baseline_table_data[row_header][column_header] + raw_baseline_confidence_interval, rounded_baseline_confidence_interval = baseline_value + pretty_printed_baseline_value = value_pretty_printer(rounded_baseline_confidence_interval, min_in_table, max_in_table) + pretty_printed_percentage_difference = pretty_print_percentage_difference(raw_baseline_confidence_interval, raw_confidence_interval) + row_content.append("%s -> %s (%s)" % (pretty_printed_baseline_value, pretty_printed_value, pretty_printed_percentage_difference)) + else: + row_content.append(pretty_printed_value) + else: + row_content.append("N/A") + table_content.append(row_content) + print_markdown_table(table_content) + + +def format_string_pretty_printer(format_string): + def pretty_print(s): + return format_string % s + + return pretty_print + + +def interval_pretty_printer(interval, unit, multiplier): + interval = interval.copy() + interval[0] *= multiplier + interval[1] *= multiplier + + # This prevents the format strings below from printing '.0' for numbers that already have 2 digits: + # 23.0 -> 23 + # 2.0 -> 2.0 (here we don't remove the '.0' because printing just '2' might suggest a lower precision) + if int(interval[0]) == interval[0] and interval[0] >= 10: + interval[0] = int(interval[0]) + else: + interval[0] = '%.3g' % interval[0] + if int(interval[1]) == interval[1] and interval[1] >= 10: + interval[1] = int(interval[1]) + else: + interval[1] = '%.3g' % interval[1] + + if interval[0] == interval[1]: + return '%s %s' % (interval[0], unit) + else: + return '%s-%s %s' % (interval[0], interval[1], unit) + + +# Finds the best unit to represent values in the range [min_value, max_value]. +# The units must be specified as an ordered list [multiplier1, ..., multiplierN] +def find_best_unit(units, min_value, max_value): + assert min_value <= max_value + if max_value <= units[0]: + return units[0] + for i in range(len(units) - 1): + if min_value > units[i] and max_value < units[i + 1]: + return units[i] + if min_value > units[-1]: + return units[-1] + # There is no unit that works very well for all values, first let's try relaxing the min constraint + for i in range(len(units) - 1): + if min_value > units[i] * 0.2 and max_value < units[i + 1]: + return units[i] + if min_value > units[-1] * 0.2: + return units[-1] + # That didn't work either, just use a unit that works well for the min values then + for i in reversed(range(len(units))): + if min_value > units[i]: + return units[i] + assert min_value <= min(units) + # Pick the smallest unit + return units[0] + + +def time_interval_pretty_printer(time_interval, min_in_table, max_in_table): + sec = 1 + milli = 0.001 + micro = milli * milli + units = [micro, milli, sec] + unit_name_by_unit = {micro: 'μs', milli: 'ms', sec: 's'} + + unit = find_best_unit(units, min_in_table, max_in_table) + unit_name = unit_name_by_unit[unit] + + return interval_pretty_printer(time_interval, unit=unit_name, multiplier=1 / unit) + + +def file_size_interval_pretty_printer(file_size_interval, min_in_table, max_in_table): + byte = 1 + kb = 1024 + mb = kb * kb + units = [byte, kb, mb] + unit_name_by_unit = {byte: 'bytes', kb: 'KB', mb: 'MB'} + + unit = find_best_unit(units, min_in_table, max_in_table) + unit_name = unit_name_by_unit[unit] + + return interval_pretty_printer(file_size_interval, unit=unit_name, multiplier=1 / unit) + + +def make_immutable(x): + if isinstance(x, list): + return tuple(make_immutable(elem) for elem in x) + return x + + +def dict_pretty_printer(dict_data): + if isinstance(dict_data, list): + dict_data = {make_immutable(mapping['from']): mapping['to'] for mapping in dict_data} + def pretty_print(s): + if s in dict_data: + return dict_data[s] + else: + raise Exception('dict_pretty_printer(%s) can\'t handle the value %s' % (dict_data, s)) + + return pretty_print + + +def determine_column_pretty_printer(pretty_printer_definition): + if 'format_string' in pretty_printer_definition: + return format_string_pretty_printer(pretty_printer_definition['format_string']) + + if 'fixed_map' in pretty_printer_definition: + return dict_pretty_printer(pretty_printer_definition['fixed_map']) + + raise Exception("Unrecognized pretty printer description: %s" % pretty_printer_definition) + + +def determine_row_pretty_printer(pretty_printer_definition): + return determine_column_pretty_printer(pretty_printer_definition) + + +def determine_value_pretty_printer(unit): + if unit == "seconds": + return time_interval_pretty_printer + if unit == "bytes": + return file_size_interval_pretty_printer + raise Exception("Unrecognized unit: %s" % unit) + + +def main(): + parser = argparse.ArgumentParser(description='Runs all the benchmarks whose results are on the Fruit website.') + parser.add_argument('--benchmark-results', + help='The input file where benchmark results will be read from (1 per line, with each line in JSON format). You can use the run_benchmarks.py to run a benchmark and generate results in this format.') + parser.add_argument('--baseline-benchmark-results', + help='Optional. If specified, compares this file (considered the "before" state) with the one specified in --benchmark-results.') + parser.add_argument('--benchmark-tables-definition', help='The YAML file that defines the benchmark tables (e.g. fruit_wiki_bench_tables.yaml).') + args = parser.parse_args() + + if args.benchmark_results is None: + raise Exception("You must specify a benchmark results file using --benchmark-results.") + + if args.benchmark_tables_definition is None: + raise Exception("You must specify a benchmark tables definition file using --benchmark-tables-definition.") + + with open(args.benchmark_results, 'r') as f: + bench_results = [json.loads(line) for line in f.readlines()] + + if args.baseline_benchmark_results: + with open(args.baseline_benchmark_results, 'r') as f: + baseline_bench_results = [json.loads(line) for line in f.readlines()] + else: + baseline_bench_results = None + + + with open(args.benchmark_tables_definition, 'r') as f: + for table_definition in yaml.load(f)["tables"]: + try: + fixed_benchmark_params = {dimension_name: make_immutable(dimension_value) for dimension_name, dimension_value in table_definition['benchmark_filter'].items()} + table_data = extract_results( + bench_results, + fixed_benchmark_params=fixed_benchmark_params, + column_dimension=table_definition['columns']['dimension'], + row_dimension=table_definition['rows']['dimension'], + result_dimension=table_definition['results']['dimension']) + if baseline_bench_results: + baseline_table_data = extract_results( + baseline_bench_results, + fixed_benchmark_params=fixed_benchmark_params, + column_dimension=table_definition['columns']['dimension'], + row_dimension=table_definition['rows']['dimension'], + result_dimension=table_definition['results']['dimension']) + else: + baseline_table_data = None + rows_pretty_printer_definition = table_definition['rows']['pretty_printer'] + columns_pretty_printer_definition = table_definition['columns']['pretty_printer'] + results_unit = table_definition['results']['unit'] + print_confidence_intervals_table(table_definition['name'], + table_data, + baseline_table_data, + column_header_pretty_printer=determine_column_pretty_printer(columns_pretty_printer_definition), + row_header_pretty_printer=determine_row_pretty_printer(rows_pretty_printer_definition), + value_pretty_printer=determine_value_pretty_printer(results_unit)) + print() + print() + except Exception as e: + print('While processing table:\n' + table_definition) + print() + raise e + + +if __name__ == "__main__": + main() diff --git a/extras/benchmark/fruit_source_generator.py b/extras/benchmark/fruit_source_generator.py new file mode 100644 index 0000000..8b2a9a6 --- /dev/null +++ b/extras/benchmark/fruit_source_generator.py @@ -0,0 +1,206 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + + +class FruitSourceGenerator: + def __init__(self, use_fruit_2_x_syntax=False): + self.use_fruit_2_x_syntax = use_fruit_2_x_syntax + + def _get_component_type(self, component_index): + if self.use_fruit_2_x_syntax: + return 'const fruit::Component<Interface{component_index}>&'.format(**locals()) + else: + return 'fruit::Component<Interface{component_index}>'.format(**locals()) + + def generate_component_header(self, component_index): + component_type = self._get_component_type(component_index) + template = """ +#ifndef COMPONENT{component_index}_H +#define COMPONENT{component_index}_H + +#include <fruit/fruit.h> + +struct Interface{component_index} {{ + virtual ~Interface{component_index}() = default; +}}; + +{component_type} getComponent{component_index}(); + +#endif // COMPONENT{component_index}_H +""" + return template.format(**locals()) + + def generate_component_source(self, component_index, deps): + include_directives = ''.join(['#include "component%s.h"\n' % index for index in deps + [component_index]]) + + component_deps = ', '.join(['std::shared_ptr<Interface%s>' % dep for dep in deps]) + + if self.use_fruit_2_x_syntax: + install_expressions = ''.join([' .install(getComponent%s())\n' % dep for dep in deps]) + else: + install_expressions = ''.join([' .install(getComponent%s)\n' % dep for dep in deps]) + + component_type = self._get_component_type(component_index) + + template = """ +{include_directives} + +struct X{component_index} : public Interface{component_index} {{ +INJECT(X{component_index}({component_deps})) {{}} + +virtual ~X{component_index}() = default; +}}; + +""" + + if self.use_fruit_2_x_syntax: + template += """ +{component_type} getComponent{component_index}() {{ + static {component_type} comp = fruit::createComponent(){install_expressions} + .bind<Interface{component_index}, X{component_index}>(); + return comp; +}} +""" + else: + template += """ +{component_type} getComponent{component_index}() {{ + return fruit::createComponent(){install_expressions} + .bind<Interface{component_index}, X{component_index}>(); +}} +""" + + return template.format(**locals()) + + def generate_main(self, toplevel_component): + if self.use_fruit_2_x_syntax: + return self.generate_main_with_fruit_2_x_syntax(toplevel_component) + else: + return self.generate_main_with_fruit_3_x_syntax(toplevel_component) + + def generate_main_with_fruit_2_x_syntax(self, toplevel_component): + template = """ +#include "component{toplevel_component}.h" + +#include <ctime> +#include <iostream> +#include <cstdlib> +#include <iomanip> +#include <chrono> + +using namespace std; + +int main(int argc, char* argv[]) {{ + if (argc != 2) {{ + std::cout << "Need to specify num_loops as argument." << std::endl; + exit(1); + }} + size_t num_loops = std::atoi(argv[1]); + double componentCreationTime = 0; + double componentNormalizationTime = 0; + std::chrono::high_resolution_clock::time_point start_time; + + for (size_t i = 0; i < 1 + num_loops/100; i++) {{ + start_time = std::chrono::high_resolution_clock::now(); + fruit::Component<Interface{toplevel_component}> component(getComponent{toplevel_component}()); + componentCreationTime += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + start_time = std::chrono::high_resolution_clock::now(); + fruit::NormalizedComponent<Interface{toplevel_component}> normalizedComponent(std::move(component)); + componentNormalizationTime += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + }} + + start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < 1 + num_loops/100; i++) {{ + fruit::Injector<Interface{toplevel_component}> injector(getComponent{toplevel_component}()); + injector.get<std::shared_ptr<Interface{toplevel_component}>>(); + }} + double fullInjectionTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + + // The cast to Component<Interface{toplevel_component}> is needed for Fruit<2.1.0, where the constructor of + // NormalizedComponent only accepted a Component&&. + fruit::NormalizedComponent<Interface{toplevel_component}> normalizedComponent{{fruit::Component<Interface{toplevel_component}>{{getComponent{toplevel_component}()}}}}; + + start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < num_loops; i++) {{ + fruit::Injector<Interface{toplevel_component}> injector(normalizedComponent, fruit::Component<>(fruit::createComponent())); + injector.get<std::shared_ptr<Interface{toplevel_component}>>(); + }} + double perRequestTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + + std::cout << std::fixed; + std::cout << std::setprecision(15); + std::cout << "componentNormalizationTime = " << componentNormalizationTime * 100 / num_loops << std::endl; + std::cout << "Total for setup = " << (componentCreationTime + componentNormalizationTime) * 100 / num_loops << std::endl; + std::cout << "Full injection time = " << fullInjectionTime * 100 / num_loops << std::endl; + std::cout << "Total per request = " << perRequestTime / num_loops << std::endl; + return 0; +}} + """ + return template.format(**locals()) + + def generate_main_with_fruit_3_x_syntax(self, toplevel_component): + template = """ +#include "component{toplevel_component}.h" + +#include <ctime> +#include <iostream> +#include <cstdlib> +#include <iomanip> +#include <chrono> + +using namespace std; + +fruit::Component<> getEmptyComponent() {{ + return fruit::createComponent(); +}} + +int main(int argc, char* argv[]) {{ + if (argc != 2) {{ + std::cout << "Need to specify num_loops as argument." << std::endl; + exit(1); + }} + size_t num_loops = std::atoi(argv[1]); + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < 1 + num_loops/100; i++) {{ + fruit::NormalizedComponent<Interface{toplevel_component}> normalizedComponent(getComponent{toplevel_component}); + (void)normalizedComponent; + }} + double componentNormalizationTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + + start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < 1 + num_loops/100; i++) {{ + fruit::Injector<Interface{toplevel_component}> injector(getComponent{toplevel_component}); + injector.get<std::shared_ptr<Interface{toplevel_component}>>(); + }} + double fullInjectionTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + + fruit::NormalizedComponent<Interface{toplevel_component}> normalizedComponent(getComponent{toplevel_component}); + + start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < num_loops; i++) {{ + fruit::Injector<Interface{toplevel_component}> injector(normalizedComponent, getEmptyComponent); + injector.get<std::shared_ptr<Interface{toplevel_component}>>(); + }} + double perRequestTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time).count(); + + std::cout << std::fixed; + std::cout << std::setprecision(15); + std::cout << "componentNormalizationTime = " << componentNormalizationTime * 100 / num_loops << std::endl; + std::cout << "Total for setup = " << componentNormalizationTime * 100 / num_loops << std::endl; + std::cout << "Full injection time = " << fullInjectionTime * 100 / num_loops << std::endl; + std::cout << "Total per request = " << perRequestTime / num_loops << std::endl; + return 0; +}} + """ + return template.format(**locals()) diff --git a/extras/benchmark/generate_benchmark.py b/extras/benchmark/generate_benchmark.py new file mode 100755 index 0000000..7a3d361 --- /dev/null +++ b/extras/benchmark/generate_benchmark.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import random +import os + +from fruit_source_generator import FruitSourceGenerator +from boost_di_source_generator import BoostDiSourceGenerator +from makefile_generator import generate_makefile +import argparse + + +def add_node(n, deps, source_generator, output_dir): + with open('%s/component%s.h' % (output_dir, n), 'w') as headerFile: + headerFile.write(source_generator.generate_component_header(n)) + with open('%s/component%s.cpp' % (output_dir, n), 'w') as sourceFile: + sourceFile.write(source_generator.generate_component_source(n, deps)) + + +def generate_benchmark( + di_library, + compiler, + cxx_std, + fruit_build_dir, + fruit_sources_dir, + output_dir, + num_components_with_no_deps, + num_components_with_deps, + num_deps, + boost_di_sources_dir=None, + use_fruit_2_x_syntax=False, + generate_debuginfo=False): + """Generates a sample codebase using the specified DI library, meant for benchmarking. + + :param boost_di_sources_dir: this is only used if di_library=='boost_di', it can be None otherwise. + """ + + if num_components_with_no_deps < num_deps: + raise Exception( + "Too few components with no deps. num_components_with_no_deps=%s but num_deps=%s." % (num_components_with_no_deps, num_deps)) + if num_deps < 2: + raise Exception("num_deps should be at least 2.") + + # This is a constant so that we always generate the same file (=> benchmark more repeatable). + random.seed(42) + + if di_library == 'fruit': + source_generator = FruitSourceGenerator( + use_fruit_2_x_syntax = use_fruit_2_x_syntax) + include_dirs = [fruit_build_dir + '/include', fruit_sources_dir + '/include'] + library_dirs = [fruit_build_dir + '/src'] + link_libraries = ['fruit'] + elif di_library == 'boost_di': + source_generator = BoostDiSourceGenerator() + include_dirs = [boost_di_sources_dir + '/include'] + library_dirs = [] + link_libraries = [] + else: + raise Exception('Unrecognized di_library: %s' % di_library) + + os.makedirs(output_dir, exist_ok=True) + + num_used_ids = 0 + is_toplevel = [True for i in range(0, num_components_with_no_deps + num_components_with_deps)] + toplevel_components = set() + for i in range(0, num_components_with_no_deps): + id = num_used_ids + num_used_ids += 1 + add_node(id, [], source_generator=source_generator, output_dir=output_dir) + toplevel_components |= {id} + + # Then the rest have num_deps deps, chosen (pseudo-)randomly from the previous components with no + # deps, plus the previous component with deps (if any). + # However, the last few components depend on multiple components with >1 deps, so that the last + # component transitively depends on everything. + for i in range(0, num_components_with_deps): + deps = set() + + if len(toplevel_components) > (num_components_with_deps - 1 - i) * (num_deps - 1): + # We need at least 1 dep with deps, otherwise the last few components will not be enough + # to tie together all components. + num_deps_with_deps = len(toplevel_components) - (num_components_with_deps - 1 - i) * (num_deps - 1) + deps |= set(random.sample(toplevel_components, num_deps_with_deps)) + + if i != 0 and len(deps) < num_deps: + # Pick one random component with deps. + # If we picked num_deps random components here, the computation of the n-th component (during + # the benchmark) would take time super-linear in n, and we don't want that (if most time was + # spent constructing the component rather than constructing the injector and injecting objects, + # the benchmark would be slow and not very meaningful). + deps |= {num_components_with_no_deps + random.randint(0, i - 1)} + + # Add other deps with no deps to get to the desired num_deps. + deps |= set(random.sample(range(0, num_components_with_no_deps), num_deps - len(deps))) + + toplevel_components -= deps + for dep in deps: + is_toplevel[dep] = False + + component_id = num_used_ids + toplevel_components |= {component_id} + num_used_ids += 1 + deps_list = list(deps) + random.shuffle(deps_list) + add_node(component_id, deps_list, source_generator, output_dir=output_dir) + + assert len(toplevel_components) == 1, toplevel_components + toplevel_component = num_used_ids - 1 + assert is_toplevel[toplevel_component] + + with open("%s/main.cpp" % output_dir, 'w') as mainFile: + mainFile.write(source_generator.generate_main(toplevel_component)) + + include_flags = ' '.join(['-I%s' % include_dir for include_dir in include_dirs]) + library_dirs_flags = ' '.join(['-L%s' % library_dir for library_dir in library_dirs]) + rpath_flags = ' '.join(['-Wl,-rpath,%s' % library_dir for library_dir in library_dirs]) + link_libraries_flags = ' '.join(['-l%s' % library for library in link_libraries]) + other_compile_flags = [] + if use_fruit_2_x_syntax: + other_compile_flags.append('-Wno-deprecated-declarations') + if generate_debuginfo: + other_compile_flags.append('-g') + compile_command = '%s -std=%s -O2 -W -Wall -Werror -DNDEBUG -ftemplate-depth=1000 %s %s' % (compiler, cxx_std, include_flags, ' '.join(other_compile_flags)) + link_command = '%s -std=%s -O2 -W -Wall -Werror %s %s' % (compiler, cxx_std, rpath_flags, library_dirs_flags) + # GCC requires passing the -lfruit flag *after* all object files to be linked for some reason. + link_command_suffix = link_libraries_flags + + sources = ['component%s' % i for i in range(0, num_used_ids)] + sources += ['main'] + + with open("%s/Makefile" % output_dir, 'w') as makefile: + makefile.write(generate_makefile(sources, 'main', compile_command, link_command, link_command_suffix)) + + +def main(): + parser = argparse.ArgumentParser(description='Generates source files and a build script for benchmarks.') + parser.add_argument('--di-library', default='fruit', help='DI library to use. One of {fruit, boost_di}. (default: fruit)') + parser.add_argument('--compiler', help='Compiler to use') + parser.add_argument('--fruit-sources-dir', help='Path to the fruit sources (only used when di_library==\'fruit\')') + parser.add_argument('--fruit-build-dir', help='Path to the fruit build dir (only used with --di_library=\'fruit\')') + parser.add_argument('--boost-di-sources-dir', help='Path to the Boost.DI sources (only used with --di-library==\'boost_di\')') + parser.add_argument('--num-components-with-no-deps', default=10, help='Number of components with no deps that will be generated') + parser.add_argument('--num-components-with-deps', default=90, help='Number of components with deps that will be generated') + parser.add_argument('--num-deps', default=10, help='Number of deps in each component with deps that will be generated') + parser.add_argument('--output-dir', help='Output directory for generated files') + parser.add_argument('--cxx-std', default='c++11', + help='Version of the C++ standard to use. Typically one of \'c++11\' and \'c++14\'. (default: \'c++11\')') + parser.add_argument('--use-fruit-2-x-syntax', default=False, help='Set this to \'true\' to generate source files compatible with Fruit 2.x (instead of 3.x).') + parser.add_argument('--generate-debuginfo', default=False, help='Set this to \'true\' to generate debugging information (-g).') + + args = parser.parse_args() + + if args.compiler is None: + raise Exception('--compiler is required.') + + if args.di_library == 'fruit': + if args.fruit_sources_dir is None: + raise Exception('--fruit-sources-dir is required with --di-library=\'fruit\'.') + if args.fruit_build_dir is None: + raise Exception('--fruit-build-dir is required with --di-library=\'fruit\'.') + elif args.di_library == 'boost_di': + if args.boost_di_sources_dir is None: + raise Exception('--boost-di-sources-dir is required with --di-library=\'boost_di\'.') + else: + raise Exception('Unrecognized --di-library: \'%s\'. Allowed values are %s' % (args.di_library, {'fruit', 'boost_di'})) + + num_components_with_deps = int(args.num_components_with_deps) + num_components_with_no_deps = int(args.num_components_with_no_deps) + num_deps = int(args.num_deps) + + if args.output_dir is None: + raise Exception("output_dir must be specified.") + + generate_benchmark( + di_library=args.di_library, + fruit_sources_dir=args.fruit_sources_dir, + boost_di_sources_dir=args.boost_di_sources_dir, + output_dir=args.output_dir, + compiler=args.compiler, + cxx_std=args.cxx_std, + num_components_with_deps=num_components_with_deps, + num_components_with_no_deps=num_components_with_no_deps, + fruit_build_dir=args.fruit_build_dir, + num_deps=num_deps, + use_fruit_2_x_syntax=(args.use_fruit_2_x_syntax == 'true'), + generate_debuginfo=(args.generate_debuginfo == 'true')) + + +if __name__ == "__main__": + main() diff --git a/extras/benchmark/makefile_generator.py b/extras/benchmark/makefile_generator.py new file mode 100644 index 0000000..2a5b2c4 --- /dev/null +++ b/extras/benchmark/makefile_generator.py @@ -0,0 +1,51 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + + +def generate_makefile(sources, executable_name, compile_command, link_command, link_command_suffix): + link_rule_template = """ +{executable_name}: {object_files} +\t{link_command} {object_files} -o {executable_name} {link_command_suffix} +""" + compile_rule_template = """ +{name}.o: {name}.cpp +\t{compile_command} -c {name}.cpp -o {name}.o +""" + + clean_rule_template = """ +clean: +\trm -f {object_files} {executable_name} +""" + + compile_rules = [] + object_files = [] + for source in sources: + compile_rule = compile_rule_template.format( + name=source, + compile_command=compile_command) + compile_rules += [compile_rule] + object_files += ['%s.o' % source] + + link_rule = link_rule_template.format( + object_files=' '.join(object_files), + link_command=link_command, + link_command_suffix=link_command_suffix, + executable_name=executable_name) + + clean_rule = clean_rule_template.format( + object_files=' '.join(object_files), + executable_name=executable_name) + + # We put the link rule first so that it's the default Make target. + return link_rule + ''.join(compile_rules) + clean_rule diff --git a/extras/benchmark/new_delete_benchmark.cpp b/extras/benchmark/new_delete_benchmark.cpp new file mode 100644 index 0000000..8131eac --- /dev/null +++ b/extras/benchmark/new_delete_benchmark.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 <chrono> +#include <ctime> +#include <iomanip> +#include <iostream> +#include <vector> + +#if MULTIPLIER == 1 +#define REPEAT(X) REPEAT_1(X, _) + +#elif MULTIPLIER == 10 +#define REPEAT(X) REPEAT_10(X, _) + +#elif MULTIPLIER == 100 +#define REPEAT(X) REPEAT_100(X, _) + +#elif MULTIPLIER == 1000 +#define REPEAT(X) REPEAT_1000(X, _) + +#else +#error Multiplier not supported. +#endif + +#define PLACEHOLDER + +#define EVAL0(...) __VA_ARGS__ +#define EVAL1(...) EVAL0(EVAL0(EVAL0(EVAL0(__VA_ARGS__)))) +#define EVAL2(...) EVAL1(EVAL1(EVAL1(EVAL1(__VA_ARGS__)))) +#define EVAL(...) EVAL2(EVAL2(EVAL2(EVAL2(__VA_ARGS__)))) + +#define META_REPEAT_10(R, X, I) \ + R PLACEHOLDER(X, I##0) R PLACEHOLDER(X, I##1) R PLACEHOLDER(X, I##2) R PLACEHOLDER(X, I##3) R PLACEHOLDER(X, I##4) \ + R PLACEHOLDER(X, I##5) R PLACEHOLDER(X, I##6) R PLACEHOLDER(X, I##7) R PLACEHOLDER(X, I##8) \ + R PLACEHOLDER(X, I##9) + +#define REPEAT_1(X, I) X(I) + +#define REPEAT_10(X, I) META_REPEAT_10(REPEAT_1, X, I) + +#define REPEAT_100(X, I) META_REPEAT_10(REPEAT_10, X, I) + +#define REPEAT_1000(X, I) META_REPEAT_10(REPEAT_100, X, I) + +using namespace std; + +#define DEFINITIONS(N) \ + struct I##N { \ + virtual ~I##N() = default; \ + }; \ + \ + struct C##N : public I##N { \ + virtual ~C##N() = default; \ + }; + +#define ALLOCATE(N) C##N* c##N = new C##N(); + +#define DEALLOCATE(N) delete c##N; + +EVAL(REPEAT(DEFINITIONS)) + +int main(int argc, const char* argv[]) { + if (argc != 2) { + std::cout << "Error: you need to specify the number of loops as argument." << std::endl; + return 1; + } + size_t num_loops = std::atoi(argv[1]); + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + + for (size_t i = 0; i < num_loops; i++) { + EVAL(REPEAT(ALLOCATE)) + EVAL(REPEAT(DEALLOCATE)) + } + double totalTime = + std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - start_time) + .count(); + + std::cout << std::fixed; + std::cout << std::setprecision(15); + std::cout << "Total = " << totalTime * 1.0 / num_loops << std::endl; + + return 0; +} diff --git a/extras/benchmark/run_benchmarks.py b/extras/benchmark/run_benchmarks.py new file mode 100755 index 0000000..cbbea78 --- /dev/null +++ b/extras/benchmark/run_benchmarks.py @@ -0,0 +1,633 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import argparse +import re +import textwrap +from collections import defaultdict +from timeit import default_timer as timer +import tempfile +import os +import shutil +import itertools +import numpy +import subprocess +import yaml +from numpy import floor, log10 +import scipy +import multiprocessing +import sh +import json +import statsmodels.stats.api as stats +from generate_benchmark import generate_benchmark +import git +from functools import lru_cache as memoize + +class CommandFailedException(Exception): + def __init__(self, command, stdout, stderr, error_code): + self.command = command + self.stdout = stdout + self.stderr = stderr + self.error_code = error_code + + def __str__(self): + return textwrap.dedent('''\ + Ran command: {command} + Exit code {error_code} + Stdout: + {stdout} + + Stderr: + {stderr} + ''').format(command=self.command, error_code=self.error_code, stdout=self.stdout, stderr=self.stderr) + +def run_command(executable, args=[], cwd=None, env=None): + args = [str(arg) for arg in args] + command = [executable] + args + try: + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, cwd=cwd, + env=env) + (stdout, stderr) = p.communicate() + except Exception as e: + raise Exception("While executing: %s" % command) + if p.returncode != 0: + raise CommandFailedException(command, stdout, stderr, p.returncode) + return (stdout, stderr) + +compile_flags = ['-O2', '-DNDEBUG'] + +make_args = ['-j', multiprocessing.cpu_count() + 1] + +def parse_results(result_lines): + """ + Parses results from the format: + ['Dimension name1 = 123', + 'Long dimension name2 = 23.45'] + + Into a dict {'Dimension name1': 123.0, 'Dimension name2': 23.45} + """ + result_dict = dict() + for line in result_lines: + line_splits = line.split('=') + metric = line_splits[0].strip() + value = float(line_splits[1].strip()) + result_dict[metric] = value + return result_dict + + +# We memoize the result since this might be called repeatedly and it's somewhat expensive. +@memoize(maxsize=None) +def determine_compiler_name(compiler_executable_name): + tmpdir = tempfile.gettempdir() + '/fruit-determine-compiler-version-dir' + ensure_empty_dir(tmpdir) + with open(tmpdir + '/CMakeLists.txt', 'w') as file: + file.write('message("@@@${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}@@@")\n') + modified_env = os.environ.copy() + modified_env['CXX'] = compiler_executable_name + # By converting to a list, we force all output to be read (so the command execution is guaranteed to be complete after this line). + # Otherwise, subsequent calls to determine_compiler_name might have trouble deleting the temporary directory because the cmake + # process is still writing files in there. + _, stderr = run_command('cmake', args=['.'], cwd=tmpdir, env=modified_env) + cmake_output = stderr.splitlines() + for line in cmake_output: + re_result = re.search('@@@(.*)@@@', line) + if re_result: + pretty_name = re_result.group(1) + # CMake calls GCC 'GNU', change it into 'GCC'. + return pretty_name.replace('GNU ', 'GCC ') + raise Exception('Unable to determine compiler. CMake output was: \n', cmake_output) + + +# Returns a pair (sha256_hash, version_name), where version_name will be None if no version tag was found at HEAD. +@memoize(maxsize=None) +def git_repo_info(repo_path): + repo = git.Repo(repo_path) + head_tags = [tag.name for tag in repo.tags if tag.commit == repo.head.commit and re.match('v[0-9].*', tag.name)] + if head_tags == []: + head_tag = None + else: + # There should be only 1 version at any given commit. + [head_tag] = head_tags + # Remove the 'v' prefix. + head_tag = head_tag[1:] + return (repo.head.commit.hexsha, head_tag) + + +# Some benchmark parameters, e.g. 'compiler_name' are synthesized automatically from other dimensions (e.g. 'compiler' dimension) or from the environment. +# We put the compiler name/version in the results because the same 'compiler' value might refer to different compiler versions +# (e.g. if GCC 6.0.0 is installed when benchmarks are run, then it's updated to GCC 6.0.1 and finally the results are formatted, we +# want the formatted results to say "GCC 6.0.0" instead of "GCC 6.0.1"). +def add_synthetic_benchmark_parameters(original_benchmark_parameters, path_to_code_under_test): + benchmark_params = original_benchmark_parameters.copy() + benchmark_params['compiler_name'] = determine_compiler_name(original_benchmark_parameters['compiler']) + if path_to_code_under_test is not None: + sha256_hash, version_name = git_repo_info(path_to_code_under_test) + benchmark_params['di_library_git_commit_hash'] = sha256_hash + if version_name is not None: + benchmark_params['di_library_version_name'] = version_name + return benchmark_params + + +class NewDeleteRunTimeBenchmark: + def __init__(self, benchmark_definition, fruit_benchmark_sources_dir): + self.benchmark_definition = add_synthetic_benchmark_parameters(benchmark_definition, path_to_code_under_test=None) + self.fruit_benchmark_sources_dir = fruit_benchmark_sources_dir + + def prepare(self): + cxx_std = self.benchmark_definition['cxx_std'] + num_classes = self.benchmark_definition['num_classes'] + compiler_executable_name = self.benchmark_definition['compiler'] + + self.tmpdir = tempfile.gettempdir() + '/fruit-benchmark-dir' + ensure_empty_dir(self.tmpdir) + run_command(compiler_executable_name, + args=compile_flags + [ + '-std=%s' % cxx_std, + '-DMULTIPLIER=%s' % num_classes, + self.fruit_benchmark_sources_dir + '/extras/benchmark/new_delete_benchmark.cpp', + '-o', + self.tmpdir + '/main', + ]) + + def run(self): + loop_factor = self.benchmark_definition['loop_factor'] + stdout, _ = run_command(self.tmpdir + '/main', args = [int(5000000 * loop_factor)]) + return parse_results(stdout.splitlines()) + + def describe(self): + return self.benchmark_definition + + +class FruitSingleFileCompileTimeBenchmark: + def __init__(self, benchmark_definition, fruit_sources_dir, fruit_build_tmpdir, fruit_benchmark_sources_dir): + self.benchmark_definition = add_synthetic_benchmark_parameters(benchmark_definition, path_to_code_under_test=fruit_sources_dir) + self.fruit_sources_dir = fruit_sources_dir + self.fruit_build_tmpdir = fruit_build_tmpdir + self.fruit_benchmark_sources_dir = fruit_benchmark_sources_dir + num_bindings = self.benchmark_definition['num_bindings'] + assert (num_bindings % 5) == 0, num_bindings + + def prepare(self): + pass + + def run(self): + start = timer() + cxx_std = self.benchmark_definition['cxx_std'] + num_bindings = self.benchmark_definition['num_bindings'] + compiler_executable_name = self.benchmark_definition['compiler'] + benchmark_generation_flags = self.benchmark_definition['benchmark_generation_flags'] + + other_compile_flags = [] + if 'use_fruit_2_x_syntax' in benchmark_generation_flags: + other_compile_flags.append('-DUSE_FRUIT_2_X_SYNTAX') + other_compile_flags.append('-Wno-deprecated-declarations') + + run_command(compiler_executable_name, + args = compile_flags + other_compile_flags + [ + '-std=%s' % cxx_std, + '-DMULTIPLIER=%s' % (num_bindings // 5), + '-I', self.fruit_sources_dir + '/include', + '-I', self.fruit_build_tmpdir + '/include', + '-ftemplate-depth=1000', + '-c', + self.fruit_benchmark_sources_dir + '/extras/benchmark/compile_time_benchmark.cpp', + '-o', + '/dev/null', + ]) + end = timer() + return {"compile_time": end - start} + + def describe(self): + return self.benchmark_definition + + +def ensure_empty_dir(dirname): + # We start by creating the directory instead of just calling rmtree with ignore_errors=True because that would ignore + # all errors, so we might otherwise go ahead even if the directory wasn't properly deleted. + os.makedirs(dirname, exist_ok=True) + shutil.rmtree(dirname) + os.makedirs(dirname) + + +class GenericGeneratedSourcesBenchmark: + def __init__(self, di_library, benchmark_definition, fruit_sources_dir, fruit_build_tmpdir, path_to_code_under_test, **other_args): + self.di_library = di_library + self.benchmark_definition = add_synthetic_benchmark_parameters(benchmark_definition, path_to_code_under_test=path_to_code_under_test) + self.fruit_sources_dir = fruit_sources_dir + self.fruit_build_tmpdir = fruit_build_tmpdir + self.other_args = other_args + + def prepare_compile_benchmark(self): + num_classes = self.benchmark_definition['num_classes'] + cxx_std = self.benchmark_definition['cxx_std'] + compiler_executable_name = self.benchmark_definition['compiler'] + benchmark_generation_flags = {flag_name: True for flag_name in self.benchmark_definition['benchmark_generation_flags']} + + self.tmpdir = tempfile.gettempdir() + '/fruit-benchmark-dir' + ensure_empty_dir(self.tmpdir) + num_classes_with_no_deps = int(num_classes * 0.1) + generate_benchmark( + compiler=compiler_executable_name, + fruit_sources_dir=self.fruit_sources_dir, + fruit_build_dir=self.fruit_build_tmpdir, + num_components_with_no_deps=num_classes_with_no_deps, + num_components_with_deps=num_classes - num_classes_with_no_deps, + num_deps=10, + output_dir=self.tmpdir, + cxx_std=cxx_std, + di_library=self.di_library, + **benchmark_generation_flags, + **self.other_args) + + def prepare_runtime_benchmark(self): + self.prepare_compile_benchmark() + run_command('make', args=make_args, cwd=self.tmpdir) + + def prepare_executable_size_benchmark(self): + self.prepare_runtime_benchmark() + run_command('strip', args=[self.tmpdir + '/main']) + + def run_compile_benchmark(self): + run_command('make', + args=make_args + ['clean'], + cwd=self.tmpdir) + start = timer() + run_command('make', + args=make_args, + cwd=self.tmpdir) + end = timer() + result = {'compile_time': end - start} + return result + + def run_runtime_benchmark(self): + num_classes = self.benchmark_definition['num_classes'] + loop_factor = self.benchmark_definition['loop_factor'] + + results, _ = run_command(self.tmpdir + '/main', + args = [ + # 40M loops with 100 classes, 40M with 1000 + int(4 * 1000 * 1000 * 1000 * loop_factor / num_classes), + ]) + return parse_results(results.splitlines()) + + def run_executable_size_benchmark(self): + wc_result, _ = run_command('wc', args=['-c', self.tmpdir + '/main']) + num_bytes = wc_result.splitlines()[0].split(' ')[0] + return {'num_bytes': float(num_bytes)} + + def describe(self): + return self.benchmark_definition + + +class FruitCompileTimeBenchmark: + def __init__(self, benchmark_definition, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='fruit', + benchmark_definition=benchmark_definition, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=fruit_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_compile_benchmark() + + def run(self): + return self.generic_benchmark.run_compile_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +class FruitRunTimeBenchmark: + def __init__(self, benchmark_definition, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='fruit', + benchmark_definition=benchmark_definition, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=fruit_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_runtime_benchmark() + + def run(self): + return self.generic_benchmark.run_runtime_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +# This is not really a 'benchmark', but we consider it as such to reuse the benchmark infrastructure. +class FruitExecutableSizeBenchmark: + def __init__(self, benchmark_definition, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='fruit', + benchmark_definition=benchmark_definition, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=fruit_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_executable_size_benchmark() + + def run(self): + return self.generic_benchmark.run_executable_size_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +class BoostDiCompileTimeBenchmark: + def __init__(self, benchmark_definition, boost_di_sources_dir, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='boost_di', + benchmark_definition=benchmark_definition, + boost_di_sources_dir=boost_di_sources_dir, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=boost_di_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_compile_benchmark() + + def run(self): + return self.generic_benchmark.run_compile_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +class BoostDiRunTimeBenchmark: + def __init__(self, benchmark_definition, boost_di_sources_dir, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='boost_di', + benchmark_definition=benchmark_definition, + boost_di_sources_dir=boost_di_sources_dir, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=boost_di_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_runtime_benchmark() + + def run(self): + return self.generic_benchmark.run_runtime_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +# This is not really a 'benchmark', but we consider it as such to reuse the benchmark infrastructure. +class BoostDiExecutableSizeBenchmark: + def __init__(self, benchmark_definition, boost_di_sources_dir, fruit_sources_dir, fruit_build_tmpdir): + self.generic_benchmark = GenericGeneratedSourcesBenchmark( + di_library='boost_di', + benchmark_definition=benchmark_definition, + boost_di_sources_dir=boost_di_sources_dir, + fruit_sources_dir=fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + path_to_code_under_test=boost_di_sources_dir) + + def prepare(self): + self.generic_benchmark.prepare_executable_size_benchmark() + + def run(self): + return self.generic_benchmark.run_executable_size_benchmark() + + def describe(self): + return self.generic_benchmark.describe() + + +def round_to_significant_digits(n, num_significant_digits): + if n <= 0: + # We special-case this, otherwise the log10 below will fail. + return 0 + return round(n, num_significant_digits - int(floor(log10(n))) - 1) + +def run_benchmark(benchmark, max_runs, output_file, min_runs=3): + def run_benchmark_once(): + print('Running benchmark... ', end='', flush=True) + result = benchmark.run() + print(result) + for dimension, value in result.items(): + results_by_dimension[dimension] += [value] + + results_by_dimension = defaultdict(lambda: []) + print('Preparing for benchmark... ', end='', flush=True) + benchmark.prepare() + print('Done.') + + # Run at least min_runs times + for i in range(min_runs): + run_benchmark_once() + + # Then consider running a few more times to get the desired precision. + while True: + for dimension, results in results_by_dimension.items(): + if all(result == results[0] for result in results): + # If all results are exactly the same the code below misbehaves. We don't need to run again in this case. + continue + confidence_interval = stats.DescrStatsW(results).tconfint_mean(0.05) + confidence_interval_2dig = (round_to_significant_digits(confidence_interval[0], 2), + round_to_significant_digits(confidence_interval[1], 2)) + if abs(confidence_interval_2dig[0] - confidence_interval_2dig[1]) > numpy.finfo(float).eps * 10: + if len(results) < max_runs: + print("Running again to get more precision on the metric %s. Current confidence interval: [%.3g, %.3g]" % ( + dimension, confidence_interval[0], confidence_interval[1])) + break + else: + print("Warning: couldn't determine a precise result for the metric %s. Confidence interval: [%.3g, %.3g]" % ( + dimension, confidence_interval[0], confidence_interval[1])) + else: + # We've reached sufficient precision in all metrics, or we've reached the max number of runs. + break + + run_benchmark_once() + + # We've reached the desired precision in all dimensions or reached the maximum number of runs. Record the results. + rounded_confidence_intervals_by_dimension = {} + confidence_intervals_by_dimension = {} + for dimension, results in results_by_dimension.items(): + confidence_interval = stats.DescrStatsW(results).tconfint_mean(0.05) + confidence_interval_2dig = (round_to_significant_digits(confidence_interval[0], 2), + round_to_significant_digits(confidence_interval[1], 2)) + rounded_confidence_intervals_by_dimension[dimension] = confidence_interval_2dig + confidence_intervals_by_dimension[dimension] = (confidence_interval, confidence_interval_2dig) + with open(output_file, 'a') as f: + json.dump({"benchmark": benchmark.describe(), "results": confidence_intervals_by_dimension}, f) + print(file=f) + print('Benchmark finished. Result: ', rounded_confidence_intervals_by_dimension) + print() + + +def expand_benchmark_definition(benchmark_definition): + """ + Takes a benchmark definition, e.g.: + [{name: 'foo', compiler: ['g++-5', 'g++-6']}, + {name: ['bar', 'baz'], compiler: ['g++-5'], cxx_std: 'c++14'}] + + And expands it into the individual benchmarks to run, in the example above: + [{name: 'foo', compiler: 'g++-5'}, + {name: 'foo', compiler: 'g++-6'}, + {name: 'bar', compiler: 'g++-5', cxx_std: 'c++14'}, + {name: 'baz', compiler: 'g++-5', cxx_std: 'c++14'}] + """ + dict_keys = sorted(benchmark_definition.keys()) + # Turn non-list values into single-item lists. + benchmark_definition = {dict_key: value if isinstance(value, list) + else [value] + for dict_key, value in benchmark_definition.items()} + # Compute the cartesian product of the value lists + value_combinations = itertools.product(*(benchmark_definition[dict_key] for dict_key in dict_keys)) + # Then turn the result back into a dict. + return [dict(zip(dict_keys, value_combination)) + for value_combination in value_combinations] + + +def expand_benchmark_definitions(benchmark_definitions): + return list(itertools.chain(*[expand_benchmark_definition(benchmark_definition) for benchmark_definition in benchmark_definitions])) + +def group_by(l, element_to_key): + """Takes a list and returns a dict of sublists, where the elements are grouped using the provided function""" + result = defaultdict(list) + for elem in l: + result[element_to_key(elem)].append(elem) + return result.items() + +def main(): + # This configures numpy/scipy to raise an exception in case of errors, instead of printing a warning and going ahead. + numpy.seterr(all='raise') + scipy.seterr(all='raise') + + parser = argparse.ArgumentParser(description='Runs a set of benchmarks defined in a YAML file.') + parser.add_argument('--fruit-benchmark-sources-dir', help='Path to the fruit sources (used for benchmarking code only)') + parser.add_argument('--fruit-sources-dir', help='Path to the fruit sources') + parser.add_argument('--boost-di-sources-dir', help='Path to the Boost.DI sources') + parser.add_argument('--output-file', + help='The output file where benchmark results will be stored (1 per line, with each line in JSON format). These can then be formatted by e.g. the format_bench_results script.') + parser.add_argument('--benchmark-definition', help='The YAML file that defines the benchmarks (see fruit_wiki_benchs_fruit.yml for an example).') + parser.add_argument('--continue-benchmark', help='If this is \'true\', continues a previous benchmark run instead of starting from scratch (taking into account the existing benchmark results in the file specified with --output-file).') + args = parser.parse_args() + + if args.output_file is None: + raise Exception('You must specify --output_file') + if args.continue_benchmark == 'true': + try: + with open(args.output_file, 'r') as f: + previous_run_completed_benchmarks = [json.loads(line)['benchmark'] for line in f.readlines()] + except FileNotFoundError: + previous_run_completed_benchmarks = [] + else: + previous_run_completed_benchmarks = [] + run_command('rm', args=['-f', args.output_file]) + + fruit_build_tmpdir = tempfile.gettempdir() + '/fruit-benchmark-build-dir' + + with open(args.benchmark_definition, 'r') as f: + yaml_file_content = yaml.load(f) + global_definitions = yaml_file_content['global'] + benchmark_definitions = expand_benchmark_definitions(yaml_file_content['benchmarks']) + + benchmark_index = 0 + + for (compiler_executable_name, additional_cmake_args), benchmark_definitions_with_current_config \ + in group_by(benchmark_definitions, + lambda benchmark_definition: + (benchmark_definition['compiler'], tuple(benchmark_definition['additional_cmake_args']))): + + print('Preparing for benchmarks with the compiler %s, with additional CMake args %s' % (compiler_executable_name, additional_cmake_args)) + # We compute this here (and memoize the result) so that the benchmark's describe() will retrieve the cached + # value instantly. + determine_compiler_name(compiler_executable_name) + + # Build Fruit in fruit_build_tmpdir, so that fruit_build_tmpdir points to a built Fruit (useful for e.g. the config header). + shutil.rmtree(fruit_build_tmpdir, ignore_errors=True) + os.makedirs(fruit_build_tmpdir) + modified_env = os.environ.copy() + modified_env['CXX'] = compiler_executable_name + run_command('cmake', + args=[ + args.fruit_sources_dir, + '-DCMAKE_BUILD_TYPE=Release', + *additional_cmake_args, + ], + cwd=fruit_build_tmpdir, + env=modified_env) + run_command('make', args=make_args, cwd=fruit_build_tmpdir) + + for benchmark_definition in benchmark_definitions_with_current_config: + benchmark_index += 1 + print('%s/%s: %s' % (benchmark_index, len(benchmark_definitions), benchmark_definition)) + benchmark_name = benchmark_definition['name'] + + if (benchmark_name in {'boost_di_compile_time', 'boost_di_run_time', 'boost_di_executable_size'} + and args.boost_di_sources_dir is None): + raise Exception('Error: you need to specify the --boost-di-sources-dir flag in order to run Boost.DI benchmarks.') + + if benchmark_name == 'new_delete_run_time': + benchmark = NewDeleteRunTimeBenchmark( + benchmark_definition, + fruit_benchmark_sources_dir=args.fruit_benchmark_sources_dir) + elif benchmark_name == 'fruit_single_file_compile_time': + benchmark = FruitSingleFileCompileTimeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_benchmark_sources_dir=args.fruit_benchmark_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir) + elif benchmark_name == 'fruit_compile_time': + benchmark = FruitCompileTimeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir) + elif benchmark_name == 'fruit_run_time': + benchmark = FruitRunTimeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir) + elif benchmark_name == 'fruit_executable_size': + benchmark = FruitExecutableSizeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir) + elif benchmark_name == 'boost_di_compile_time': + benchmark = BoostDiCompileTimeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + boost_di_sources_dir=args.boost_di_sources_dir) + elif benchmark_name == 'boost_di_run_time': + benchmark = BoostDiRunTimeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + boost_di_sources_dir=args.boost_di_sources_dir) + elif benchmark_name == 'boost_di_executable_size': + benchmark = BoostDiExecutableSizeBenchmark( + benchmark_definition, + fruit_sources_dir=args.fruit_sources_dir, + fruit_build_tmpdir=fruit_build_tmpdir, + boost_di_sources_dir=args.boost_di_sources_dir) + else: + raise Exception("Unrecognized benchmark: %s" % benchmark_name) + + if benchmark.describe() in previous_run_completed_benchmarks: + print("Skipping benchmark that was already run previously (due to --continue-benchmark):", benchmark.describe()) + continue + + run_benchmark(benchmark, output_file=args.output_file, max_runs=global_definitions['max_runs']) + + +if __name__ == "__main__": + main() diff --git a/extras/benchmark/suites/boost_di.yml b/extras/benchmark/suites/boost_di.yml new file mode 100644 index 0000000..7aa9750 --- /dev/null +++ b/extras/benchmark/suites/boost_di.yml @@ -0,0 +1,38 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 20 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + +benchmarks: + - name: + - "boost_di_compile_time" + - "boost_di_run_time" + - "boost_di_executable_size" + loop_factor: 1.0 + num_classes: + - 100 + # Boost.DI fails to compile the generated example with 1000 classes. + compiler: *compilers + cxx_std: "c++14" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_debug.yml b/extras/benchmark/suites/fruit_debug.yml new file mode 100644 index 0000000..30ca90c --- /dev/null +++ b/extras/benchmark/suites/fruit_debug.yml @@ -0,0 +1,97 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +# This file is a simplified version of fruit_wiki_benchs_{fruit,boost_di}.yml used to debug benchmarking code. +# When using this, the benchmarks will run much faster, but the results will be unreliable and incomplete. + +global: + max_runs: 3 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + gcc: &gcc + - "g++-7" + clang: &clang + - "clang++-4.0" + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + #- ["use_fruit_2_x_syntax"] + + - name: + - "new_delete_run_time" + loop_factor: 0.01 + num_classes: + - 100 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 0.01 + num_classes: + - 100 + compiler: *gcc + cxx_std: "c++11" + additional_cmake_args: + - [] + - ['-DFRUIT_USES_BOOST=False'] + - ["-DBUILD_SHARED_LIBS=False"] + benchmark_generation_flags: + - [] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 0.01 + num_classes: + - 100 + compiler: *clang + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "boost_di_compile_time" + - "boost_di_run_time" + - "boost_di_executable_size" + loop_factor: 0.01 + num_classes: + - 100 + compiler: *compilers + cxx_std: "c++14" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_full.yml b/extras/benchmark/suites/fruit_full.yml new file mode 100644 index 0000000..aeff08b --- /dev/null +++ b/extras/benchmark/suites/fruit_full.yml @@ -0,0 +1,66 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 20 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + num_classes: &num_classes + - 100 + - 1000 + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + - 80 + - 320 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "new_delete_run_time" + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - ['-DFRUIT_USES_BOOST=False'] + - ["-DBUILD_SHARED_LIBS=False"] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_full_old_style.yml b/extras/benchmark/suites/fruit_full_old_style.yml new file mode 100644 index 0000000..413a05f --- /dev/null +++ b/extras/benchmark/suites/fruit_full_old_style.yml @@ -0,0 +1,66 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 20 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + num_classes: &num_classes + - 100 + - 1000 + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + - 80 + - 320 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] + + - name: + - "new_delete_run_time" + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - ['-DFRUIT_USES_BOOST=False'] + - ["-DBUILD_SHARED_LIBS=False"] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] diff --git a/extras/benchmark/suites/fruit_mostly_full.yml b/extras/benchmark/suites/fruit_mostly_full.yml new file mode 100644 index 0000000..00ee0dc --- /dev/null +++ b/extras/benchmark/suites/fruit_mostly_full.yml @@ -0,0 +1,51 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 20 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + num_classes: &num_classes + - 100 + - 1000 + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + - 80 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "new_delete_run_time" + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_quick.yml b/extras/benchmark/suites/fruit_quick.yml new file mode 100644 index 0000000..778bf60 --- /dev/null +++ b/extras/benchmark/suites/fruit_quick.yml @@ -0,0 +1,49 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 10 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-6" + - "clang++-4.0" + num_classes: &num_classes + - 100 + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + - 80 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_quick_old_style.yml b/extras/benchmark/suites/fruit_quick_old_style.yml new file mode 100644 index 0000000..cb6b70e --- /dev/null +++ b/extras/benchmark/suites/fruit_quick_old_style.yml @@ -0,0 +1,50 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +global: + max_runs: 10 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + - "clang++-4.0" + num_classes: &num_classes + - 100 + - 1000 + +benchmarks: + - name: "fruit_single_file_compile_time" + num_bindings: + - 20 + - 80 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] + + - name: + - "fruit_compile_time" + - "fruit_run_time" + - "fruit_executable_size" + loop_factor: 1.0 + num_classes: *num_classes + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] diff --git a/extras/benchmark/suites/fruit_single.yml b/extras/benchmark/suites/fruit_single.yml new file mode 100644 index 0000000..a6dae93 --- /dev/null +++ b/extras/benchmark/suites/fruit_single.yml @@ -0,0 +1,36 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +# This is a variant of fruit_wiki_benchs_fruit.yml that runs a single runtime benchmark. + +global: + max_runs: 8 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + +benchmarks: + - name: + - "fruit_run_time" + loop_factor: 1.0 + num_classes: + - 100 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - [] diff --git a/extras/benchmark/suites/fruit_single_old_style.yml b/extras/benchmark/suites/fruit_single_old_style.yml new file mode 100644 index 0000000..32efa0f --- /dev/null +++ b/extras/benchmark/suites/fruit_single_old_style.yml @@ -0,0 +1,36 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +# This is a variant of fruit_wiki_benchs_fruit.yml that runs a single runtime benchmark. + +global: + max_runs: 8 + +# These values are ignored, they are here just to be referenced below. +constants: + compilers: &compilers + - "g++-7" + +benchmarks: + - name: + - "fruit_run_time" + loop_factor: 1.0 + num_classes: + - 100 + compiler: *compilers + cxx_std: "c++11" + additional_cmake_args: + - [] + benchmark_generation_flags: + - ["use_fruit_2_x_syntax"] diff --git a/extras/benchmark/tables/fruit_internal.yml b/extras/benchmark/tables/fruit_internal.yml new file mode 100644 index 0000000..4dcef73 --- /dev/null +++ b/extras/benchmark/tables/fruit_internal.yml @@ -0,0 +1,192 @@ + +# These values are ignored, they are here just to be referenced below. +constants: + num_bindings_column: &num_bindings_column + dimension: "num_bindings" + pretty_printer: + format_string: "%s bindings" + + num_classes_column: &num_classes_column + dimension: "num_classes" + pretty_printer: + format_string: "%s classes" + + compiler_name_row: &compiler_name_row + dimension: "compiler_name" + pretty_printer: + format_string: "%s" + +tables: + - name: "Fruit compile time (single file)" + benchmark_filter: + name: "fruit_single_file_compile_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_bindings_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit compile time" + benchmark_filter: + name: "fruit_compile_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit full injection time" + benchmark_filter: + name: "fruit_run_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Fruit component normalization time" + benchmark_filter: + name: "fruit_run_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "componentNormalizationTime" + unit: "seconds" + + - name: "Fruit setup time" + benchmark_filter: + name: "fruit_run_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Fruit per-request time" + benchmark_filter: + name: "fruit_run_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "New/delete time" + benchmark_filter: + name: "new_delete_run_time" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total" + unit: "seconds" + + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_compile_time": "Fruit" + "boost_di_compile_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit full injection time (100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Fruit component normalization time (100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "componentNormalizationTime" + unit: "seconds" + + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_executable_size": "Fruit" + "boost_di_executable_size": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" diff --git a/extras/benchmark/tables/fruit_internal_old_vs_new_style.yml b/extras/benchmark/tables/fruit_internal_old_vs_new_style.yml new file mode 100644 index 0000000..3a74047 --- /dev/null +++ b/extras/benchmark/tables/fruit_internal_old_vs_new_style.yml @@ -0,0 +1,108 @@ + +# These values are ignored, they are here just to be referenced below. +constants: + num_bindings_column: &num_bindings_column + dimension: "num_bindings" + pretty_printer: + format_string: "%s bindings" + + num_classes_column: &num_classes_column + dimension: "num_classes" + pretty_printer: + format_string: "%s classes" + + compiler_name_row: &compiler_name_row + dimension: "compiler_name" + pretty_printer: + format_string: "%s" + +tables: + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_compile_time" + additional_cmake_args: [] + columns: + dimension: "benchmark_generation_flags" + pretty_printer: + fixed_map: + - from: ["use_fruit_2_x_syntax"] + to: "Old-style install()" + - from: [] + to: "New-style install()" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit full injection time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + additional_cmake_args: [] + columns: + dimension: "benchmark_generation_flags" + pretty_printer: + fixed_map: + - from: ["use_fruit_2_x_syntax"] + to: "Old-style install()" + - from: [] + to: "New-style install()" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + additional_cmake_args: [] + columns: + dimension: "benchmark_generation_flags" + pretty_printer: + fixed_map: + - from: ["use_fruit_2_x_syntax"] + to: "Old-style install()" + - from: [] + to: "New-style install()" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + additional_cmake_args: [] + columns: + dimension: "benchmark_generation_flags" + pretty_printer: + fixed_map: + - from: ["use_fruit_2_x_syntax"] + to: "Old-style install()" + - from: [] + to: "New-style install()" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_executable_size" + additional_cmake_args: [] + columns: + dimension: "benchmark_generation_flags" + pretty_printer: + fixed_map: + - from: ["use_fruit_2_x_syntax"] + to: "Old-style install()" + - from: [] + to: "New-style install()" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" diff --git a/extras/benchmark/tables/fruit_wiki.yml b/extras/benchmark/tables/fruit_wiki.yml new file mode 100644 index 0000000..6a0c304 --- /dev/null +++ b/extras/benchmark/tables/fruit_wiki.yml @@ -0,0 +1,282 @@ + +# These values are ignored, they are here just to be referenced below. +constants: + num_bindings_column: &num_bindings_column + dimension: "num_bindings" + pretty_printer: + format_string: "%s bindings" + + num_classes_column: &num_classes_column + dimension: "num_classes" + pretty_printer: + format_string: "%s classes" + + compiler_name_row: &compiler_name_row + dimension: "compiler_name" + pretty_printer: + format_string: "%s" + +tables: + # Main Fruit benchmark tables + + - name: "Fruit compile time (single file)" + benchmark_filter: + name: "fruit_single_file_compile_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_bindings_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit compile time" + benchmark_filter: + name: "fruit_compile_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit full injection time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Fruit setup time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Fruit per-request time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "New/delete time" + benchmark_filter: + name: "new_delete_run_time" + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total" + unit: "seconds" + + # The following tables compare Fruit to Boost.DI + + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_compile_time": "Fruit" + "boost_di_compile_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Full injection time" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: [] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_executable_size": "Fruit" + "boost_di_executable_size": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" + + + # The following tables compare various Fruit configurations + + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_compile_time" + benchmark_generation_flags: [] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Full injection time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: [] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: [] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: [] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped)" + benchmark_filter: + name: "fruit_executable_size" + benchmark_generation_flags: [] + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_executable_size" + benchmark_generation_flags: [] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" diff --git a/extras/benchmark/tables/fruit_wiki_old_style.yml b/extras/benchmark/tables/fruit_wiki_old_style.yml new file mode 100644 index 0000000..135445a --- /dev/null +++ b/extras/benchmark/tables/fruit_wiki_old_style.yml @@ -0,0 +1,270 @@ + +# These values are ignored, they are here just to be referenced below. +constants: + num_bindings_column: &num_bindings_column + dimension: "num_bindings" + pretty_printer: + format_string: "%s bindings" + + num_classes_column: &num_classes_column + dimension: "num_classes" + pretty_printer: + format_string: "%s classes" + + compiler_name_row: &compiler_name_row + dimension: "compiler_name" + pretty_printer: + format_string: "%s" + +tables: + # Main Fruit benchmark tables + + - name: "Fruit compile time (single file)" + benchmark_filter: + name: "fruit_single_file_compile_time" + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: *num_bindings_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit compile time" + benchmark_filter: + name: "fruit_compile_time" + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Fruit full injection time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Fruit setup time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Fruit per-request time" + benchmark_filter: + name: "fruit_run_time" + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "New/delete time" + benchmark_filter: + name: "new_delete_run_time" + additional_cmake_args: [] + columns: *num_classes_column + rows: *compiler_name_row + results: + dimension: "Total" + unit: "seconds" + + # The following tables compare Fruit to Boost.DI + + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_compile_time": "Fruit" + "boost_di_compile_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Full injection time" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_run_time": "Fruit" + "boost_di_run_time": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + additional_cmake_args: [] + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "name" + pretty_printer: + fixed_map: + "fruit_executable_size": "Fruit" + "boost_di_executable_size": "Boost.DI" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" + + + # The following tables compare various Fruit configurations + + - name: "Compile time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_compile_time" + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "compile_time" + unit: "seconds" + + - name: "Full injection time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Full injection time" + unit: "seconds" + + - name: "Setup time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Total for setup" + unit: "seconds" + + - name: "Per-request time (100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_run_time" + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "Total per request" + unit: "seconds" + + - name: "Executable size (stripped, 100 classes)" + benchmark_filter: + num_classes: 100 + name: "fruit_executable_size" + benchmark_generation_flags: ["use_fruit_2_x_syntax"] + columns: + dimension: "additional_cmake_args" + pretty_printer: + fixed_map: + - from: [] + to: "default" + - from: ["-DFRUIT_USES_BOOST=False"] + to: "With -DFRUIT_USES_BOOST=False" + - from: ["-DBUILD_SHARED_LIBS=False"] + to: "Statically-linking with Fruit" + rows: *compiler_name_row + results: + dimension: "num_bytes" + unit: "bytes" diff --git a/extras/doc/CMakeLists.txt b/extras/doc/CMakeLists.txt new file mode 100644 index 0000000..5907cf8 --- /dev/null +++ b/extras/doc/CMakeLists.txt @@ -0,0 +1,94 @@ + + +add_custom_command(OUTPUT tikz-uml.sty + COMMAND wget http://perso.ensta-paristech.fr/~kielbasi/tikzuml/var/files/src/tikzuml-v1.0-2016-03-29.tbz + COMMAND tar xf tikzuml-v1.0-2016-03-29.tbz + COMMAND mv tikzuml-v1.0-2016-03-29/tikz-uml.sty tikz-uml.sty + ) +add_custom_command(OUTPUT header.tex footer.tex + COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/common-header.tex header.tex + COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/common-footer.tex footer.tex + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/common-header.tex + ${CMAKE_CURRENT_SOURCE_DIR}/common-footer.tex + ) + +set(LATEX_SOURCES +bar_handler.tex +bind.tex +bind_instance.tex +car_component.tex +checked_adder.tex +checked_incrementer.tex +component_composition.tex +component_dep_loop.tex +foo_handler.tex +greeter.tex +incrementer.tex +incrementer_component.tex +inject_macro.tex +inject_macro_no_args.tex +inject_macro_template.tex +inject_typedef_greeter.tex +inject_typedef_writer.tex +inject_typedef_writer2.tex +inject_typedef_templated_constructor.tex +multiplier.tex +parametrized_component.tex +provider.tex +provider_functor.tex +register_constructor.tex +register_constructor_component.tex +register_factory.tex +register_factory_use.tex +register_factory_macro.tex +request_dispatcher.tex +request_injector.tex +scaler.tex +server.tex +simple_greeter.tex +simple_incrementer.tex +simple_adder.tex +templated_component.tex +) + +foreach(S ${LATEX_SOURCES}) + string(REPLACE ".tex" "" N ${S}) + add_custom_command(OUTPUT ${N}.png + COMMAND pdflatex -halt-on-error ${CMAKE_CURRENT_SOURCE_DIR}/${N}.tex + COMMAND convert -density 300 -trim ${N}.pdf -quality 100 -sharpen 0x1.0 ${N}.png + # This normalizes the PNG files, so that we avoid tracking multiple copies of the same file in the Github wiki repo. + COMMAND exiftool -all= -overwrite_original ${N}.png + DEPENDS + tikz-uml.sty + header.tex + footer.tex + ${N}.tex + ) + add_custom_target(${N}-png ALL + DEPENDS ${N}.png) +endforeach(S) + +set(EXAMPLE_DIRECTORIES +hello_world +server +scaling_doubles +multibindings +simple_injection +) + +foreach(D ${EXAMPLE_DIRECTORIES}) + add_custom_command(OUTPUT ${D}-deps.png + COMMAND bash < ${CMAKE_CURRENT_SOURCE_DIR}/extract_dependencies.sh > ${CMAKE_CURRENT_BINARY_DIR}/${D}.dot + COMMAND dot -Goverlap=prism10000 ${CMAKE_CURRENT_BINARY_DIR}/${D}.dot -Tpng -o ${CMAKE_CURRENT_BINARY_DIR}/${D}-deps.png + # This normalizes the PNG files, so that we avoid tracking multiple copies of the same file in the Github wiki repo. + COMMAND exiftool -all= -overwrite_original ${CMAKE_CURRENT_BINARY_DIR}/${D}-deps.png + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/${D} + DEPENDS + ../../examples/${D} + extract_dependencies.sh + ) + add_custom_target(${D}-deps ALL + DEPENDS ${D}-deps.png) +endforeach(D) + diff --git a/extras/doc/README b/extras/doc/README new file mode 100644 index 0000000..3ebf297 --- /dev/null +++ b/extras/doc/README @@ -0,0 +1,2 @@ +This directory contains files needed to re-generate the images in the online documentation. +It's not necessary to build this when building Fruit. diff --git a/extras/doc/bar_handler.tex b/extras/doc/bar_handler.tex new file mode 100644 index 0000000..5defad1 --- /dev/null +++ b/extras/doc/bar_handler.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{2.5}{BarHandlerComponent} +\umlprovidedinterface[interface=BarHandler, distance=3.3, padding=0.9cm]{BarHandlerComponent} +\umlrequiredinterface[interface={Request, ServerContext}, distance=4.2, padding=0.9cm]{BarHandlerComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/bind.tex b/extras/doc/bind.tex new file mode 100644 index 0000000..0287716 --- /dev/null +++ b/extras/doc/bind.tex @@ -0,0 +1,14 @@ + +\input{header} +\begin{tikzpicture}[scale=0.91, transform shape] + +\newnamedcomponent{0}{2.5}{bind1}{bind} +\newnamedcomponent{0}{0}{bind2}{bind} +\umlprovidedinterface[interface=Impl, distance=2, padding=0.9cm]{bind1} +\umlrequiredinterface[interface=Interface, distance=2.2, padding=0.9cm]{bind1} +\umlprovidedinterface[interface={std::function<std::unique\_ptr<Impl(T1,..,Tn)>()>}, distance=5.2, padding=0.9cm]{bind2} +\umlrequiredinterface[interface={std::function<std::unique\_ptr<Interface(T1,..,Tn)>()>}, distance=5.7, padding=0.9cm]{bind2} +\node at (8.8,-0.2) {(for any types T1,...,Tn)}; + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/bind_instance.tex b/extras/doc/bind_instance.tex new file mode 100644 index 0000000..962f48b --- /dev/null +++ b/extras/doc/bind_instance.tex @@ -0,0 +1,9 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{bindInstance} +\umlprovidedinterface[interface=Foo, distance=2.5, padding=0.9cm]{bindInstance} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/car_component.tex b/extras/doc/car_component.tex new file mode 100644 index 0000000..0843636 --- /dev/null +++ b/extras/doc/car_component.tex @@ -0,0 +1,17 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{CarComponent} +\newcomponent{0}{1.5}{CarImpl} +\umlprovidedinterface[interface=Car, distance=2cm, padding=1cm]{CarImpl} +\newcomponent{10}{0}{MainBrakeComponent} +\umlassemblyconnector[interface={Annotated<MainBrake{,} Brake>}]{CarImpl}{MainBrakeComponent} +\newcomponent{10}{3}{EmergencyBrakeComponent} +\umlassemblyconnector[interface={Annotated<EmergencyBrake{,} Brake>}]{CarImpl}{EmergencyBrakeComponent} +\end{component} +\umlprovidedinterface[interface=Car, distance=9.7, with port, padding=0cm]{CarComponent} +\umldep{CarComponent-west-port}{CarImpl-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/checked_adder.tex b/extras/doc/checked_adder.tex new file mode 100644 index 0000000..3e5d86b --- /dev/null +++ b/extras/doc/checked_adder.tex @@ -0,0 +1,9 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{2}{0}{CheckedAdderComponent} +\umlprovidedinterface[interface=Adder, distance=4, padding=0.5cm]{CheckedAdderComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/checked_incrementer.tex b/extras/doc/checked_incrementer.tex new file mode 100644 index 0000000..f5673c5 --- /dev/null +++ b/extras/doc/checked_incrementer.tex @@ -0,0 +1,15 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{CheckedIncrementerComponent} +\newcomponent{0}{0}{IncrementerImplComponent} +\newcomponent{6}{0}{CheckedAdderComponent} +\umlprovidedinterface[interface=Incrementer, distance=3.7, padding=0.9cm]{IncrementerImplComponent} +\umlassemblyconnector[interface=Adder, distance=7]{IncrementerImplComponent}{CheckedAdderComponent} +\end{component} +\umlprovidedinterface[interface=Incrementer, distance=8, with port]{CheckedIncrementerComponent} +\umlassoc{CheckedIncrementerComponent-west-port}{IncrementerImplComponent-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/common-footer.tex b/extras/doc/common-footer.tex new file mode 100644 index 0000000..300f2b1 --- /dev/null +++ b/extras/doc/common-footer.tex @@ -0,0 +1,4 @@ + +\end{center} +\end{framed} +\end{document} diff --git a/extras/doc/common-header.tex b/extras/doc/common-header.tex new file mode 100644 index 0000000..2c01e63 --- /dev/null +++ b/extras/doc/common-header.tex @@ -0,0 +1,36 @@ + +\documentclass{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[english]{babel} +\usepackage{tikz-uml} +\usepackage{graphicx} +\usepackage[margin=0in, paperwidth=18.5cm]{geometry} +\usepackage{color} +\usepackage{framed} + +\renewcommand{\strut}{\rule[-.3\baselineskip]{0pt}{\baselineskip}} + +\newenvironment{component}[4] +{\begin{umlcomponent}[width=#1,x=#2,y=#3,name=#4]{#4\strut}} +{\end{umlcomponent}} + +\newcommand{\newcomponent}[3]{\umlbasiccomponent[x=#1,y=#2,name=#3]{#3\strut}} +\newcommand{\newnamedcomponent}[4]{\umlbasiccomponent[x=#1,y=#2,name=#3]{#4\strut}} + +\definecolor{lightgray}{HTML}{FEFEFE} + +\begin{document} + +\pagecolor{white} % White background instead of transparent +\pagenumbering{gobble}% Remove page number + +\tikzumlset{fill component=white} +\tikzumlset{fill port=white} +\tikzumlset{fill assembly connector=white} + +\def\FrameCommand +{\fcolorbox{lightgray}{white}}% +\begin{framed} +\begin{center}
\ No newline at end of file diff --git a/extras/doc/component_composition.tex b/extras/doc/component_composition.tex new file mode 100644 index 0000000..035a481 --- /dev/null +++ b/extras/doc/component_composition.tex @@ -0,0 +1,35 @@ + +\input{header} +\begin{tikzpicture}[scale=0.93, transform shape] + +\begin{component}{20cm}{1}{0}{U1Component} +\newcomponent{11}{4}{SomeType} +\newcomponent{0}{4}{T1Component} +\newcomponent{6}{4}{bind} +\newcomponent{2.5}{0}{U1U2Component} +\umlassemblyconnector[interface=SomeType, distance=7]{bind}{SomeType} +\umlassemblyconnector[interface=T2, distance=7, arm2=1cm]{T1Component}{bind} +\umlassemblyconnector[distance=7, geometry=|-]{U1U2Component}{bind} +\umlassemblyconnector[interface=T1, distance=7, anchor1=120, anchor2=-90]{U1U2Component}{T1Component} +\umlprovidedinterface[interface=U2, distance=3.5, padding=0.9cm]{U1U2Component} +\umlrequiredinterface[interface=T3, distance=3, padding=0.9cm]{U1U2Component} +\umlrequiredinterface[interface=T3, distance=2, padding=0.9cm]{SomeType} + +% Hack to draw something similar to: +% \umlprovidedinterface[interface=U2, distance=3.5, padding=0.9cm]{U1U2Component} +% But at a different angle. +\node[inner sep=0] (U1U2Component-west-port) at (U1U2Component.west) {}; +\draw (U1U2Component)+(-3cm,1.5cm) node[inner sep=0, text width=1em, circle, draw, fill=white, name=U1U2Component-west-interface] {}; +\node[above] at (U1U2Component-west-interface.north) {U1}; +\umlrelation[style={tikzuml connector style}]{U1U2Component-west-port}{U1U2Component-west-interface} +\draw (U1U2Component-west-interface)+(-1cm,0) node[name=U1U2Component-west-padding] {}; + +\end{component} +\umlprovidedinterface[interface=U1, distance=9.5, with port, padding=0cm]{U1Component} +\umlrequiredinterface[interface=T3, distance=9.5, with port, padding=0cm]{U1Component} +\umldep{SomeType-east-interface}{U1Component-east-port} +\umldep{U1U2Component-east-interface}{U1Component-east-port} +\umldep{U1Component-west-port}{U1U2Component-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/component_dep_loop.tex b/extras/doc/component_dep_loop.tex new file mode 100644 index 0000000..1b6a6bb --- /dev/null +++ b/extras/doc/component_dep_loop.tex @@ -0,0 +1,11 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{T1Component} +\newcomponent{5}{0}{T2Component} +\umlassemblyconnector[interface=T2, distance=7, geometry=|-|, arm1=1cm]{T1Component}{T2Component} +\umlassemblyconnector[interface=T1, distance=7, geometry=|-|, arm1=-1cm]{T2Component}{T1Component} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/extract_dependencies.sh b/extras/doc/extract_dependencies.sh new file mode 100755 index 0000000..b4c60f5 --- /dev/null +++ b/extras/doc/extract_dependencies.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "// Running from dir $PWD" +echo 'digraph g {' +for f in $(ls *.cpp *.h) +do + echo "$(echo "$f" | sed 's/\./_/g') [label=\"$f\"]" + for g in $(fgrep -l "#include \"$f\"" *) + do + echo "$g -> $f" | sed 's/\./_/g' + done +done +echo '}' diff --git a/extras/doc/foo_handler.tex b/extras/doc/foo_handler.tex new file mode 100644 index 0000000..8f65620 --- /dev/null +++ b/extras/doc/foo_handler.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{2.5}{FooHandlerComponent} +\umlprovidedinterface[interface=FooHandler, distance=3.3, padding=0.9cm]{FooHandlerComponent} +\umlrequiredinterface[interface={Request, ServerContext}, distance=4.2, padding=0.9cm]{FooHandlerComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/generate_snippets.sh b/extras/doc/generate_snippets.sh new file mode 100755 index 0000000..bf14662 --- /dev/null +++ b/extras/doc/generate_snippets.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +for f in *.cpp *.h +do + echo "// $f" + cat "$f" + echo + echo +done | grep -v '^/\*' | grep -v '^ \*' | grep -v '^ \*/' | grep -v '_H$' diff --git a/extras/doc/greeter.tex b/extras/doc/greeter.tex new file mode 100644 index 0000000..689c3a0 --- /dev/null +++ b/extras/doc/greeter.tex @@ -0,0 +1,20 @@ + +\input{header} +\begin{tikzpicture}[scale=0.71, transform shape] + +\begin{component}{20cm}{1}{0}{GreeterComponent} +\newnamedcomponent{0}{0}{bind1}{bind<Greeter, GreeterImpl>} +\newcomponent{6}{0}{GreeterImpl} +\newnamedcomponent{12}{0}{bind2}{bind<Writer, StdoutWriter>} +\newcomponent{18}{0}{StdoutWriter} + +\umlassemblyconnector[interface=StdoutWriter]{bind2}{StdoutWriter} +\umlassemblyconnector[interface=Writer]{GreeterImpl}{bind2} +\umlassemblyconnector[interface=GreeterImpl]{bind1}{GreeterImpl} +\umlprovidedinterface[interface=Greeter, distance=3, padding=0.5cm]{bind1} +\end{component} +\umlprovidedinterface[interface=Greeter, distance=13, with port, padding=0.2cm]{GreeterComponent} +\umlassoc{GreeterComponent-west-port}{bind1-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/incrementer.tex b/extras/doc/incrementer.tex new file mode 100644 index 0000000..c3599e3 --- /dev/null +++ b/extras/doc/incrementer.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{2}{0}{IncrementerImplComponent} +\umlprovidedinterface[interface=Incrementer, distance=4, padding=0.5cm]{IncrementerImplComponent} +\umlrequiredinterface[interface=Adder, distance=4, padding=0.5cm]{IncrementerImplComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/incrementer_component.tex b/extras/doc/incrementer_component.tex new file mode 100644 index 0000000..47f5caa --- /dev/null +++ b/extras/doc/incrementer_component.tex @@ -0,0 +1,16 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{IncrementerComponent} +\newcomponent{0}{0}{SimpleIncrementerComponent} +\newcomponent{0}{2.5}{CheckedIncrementerComponent} +\umlprovidedinterface[interface=Incrementer, distance=4, padding=1cm]{SimpleIncrementerComponent} +\umlprovidedinterface[interface=Incrementer, distance=4, padding=1cm]{CheckedIncrementerComponent} +\end{component} +\umlprovidedinterface[interface=Incrementer, distance=5.5, with port]{IncrementerComponent} +\umldep{IncrementerComponent-west-port}{SimpleIncrementerComponent-west-interface} +\umldep{IncrementerComponent-west-port}{CheckedIncrementerComponent-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_macro.tex b/extras/doc/inject_macro.tex new file mode 100644 index 0000000..eb2727d --- /dev/null +++ b/extras/doc/inject_macro.tex @@ -0,0 +1,13 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{GreeterImpl1}{GreeterImpl} +\newnamedcomponent{0}{0}{GreeterImpl2}{GreeterImpl} +\umlprovidedinterface[interface=GreeterImpl, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<GreeterImpl>()>, distance=5.5, padding=0.9cm]{GreeterImpl2} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_macro_no_args.tex b/extras/doc/inject_macro_no_args.tex new file mode 100644 index 0000000..a4c2e2a --- /dev/null +++ b/extras/doc/inject_macro_no_args.tex @@ -0,0 +1,11 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{StdoutWriter1}{StdoutWriter} +\newnamedcomponent{0}{0}{StdoutWriter2}{StdoutWriter} +\umlprovidedinterface[interface=StdoutWriter, distance=3, padding=0.9cm]{StdoutWriter1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<StdoutWriter>()>, distance=5.8, padding=0.9cm]{StdoutWriter2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_macro_template.tex b/extras/doc/inject_macro_template.tex new file mode 100644 index 0000000..8b85725 --- /dev/null +++ b/extras/doc/inject_macro_template.tex @@ -0,0 +1,15 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{GreeterImpl1}{GreeterImpl} +\newnamedcomponent{0}{0}{GreeterImpl2}{GreeterImpl} +\umlprovidedinterface[interface=GreeterImpl<W>, distance=3.2, padding=0.9cm]{GreeterImpl1} +\umlrequiredinterface[interface=W, distance=2.5, padding=0.9cm]{GreeterImpl1} +\node at (4.5,2.8) {(for any type W)}; +\umlprovidedinterface[interface=std::function<std::unique\_ptr<GreeterImpl<W>{}>()>, distance=5.9, padding=0.9cm]{GreeterImpl2} +\umlrequiredinterface[interface=W, distance=2.5, padding=0.9cm]{GreeterImpl2} +\node at (4.5,0.3) {(for any type W)}; + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_typedef_greeter.tex b/extras/doc/inject_typedef_greeter.tex new file mode 100644 index 0000000..eb2727d --- /dev/null +++ b/extras/doc/inject_typedef_greeter.tex @@ -0,0 +1,13 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{GreeterImpl1}{GreeterImpl} +\newnamedcomponent{0}{0}{GreeterImpl2}{GreeterImpl} +\umlprovidedinterface[interface=GreeterImpl, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<GreeterImpl>()>, distance=5.5, padding=0.9cm]{GreeterImpl2} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_typedef_templated_constructor.tex b/extras/doc/inject_typedef_templated_constructor.tex new file mode 100644 index 0000000..eb2727d --- /dev/null +++ b/extras/doc/inject_typedef_templated_constructor.tex @@ -0,0 +1,13 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{GreeterImpl1}{GreeterImpl} +\newnamedcomponent{0}{0}{GreeterImpl2}{GreeterImpl} +\umlprovidedinterface[interface=GreeterImpl, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<GreeterImpl>()>, distance=5.5, padding=0.9cm]{GreeterImpl2} +\umlrequiredinterface[interface=Writer, distance=2.5, padding=0.9cm]{GreeterImpl2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_typedef_writer.tex b/extras/doc/inject_typedef_writer.tex new file mode 100644 index 0000000..a4c2e2a --- /dev/null +++ b/extras/doc/inject_typedef_writer.tex @@ -0,0 +1,11 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{StdoutWriter1}{StdoutWriter} +\newnamedcomponent{0}{0}{StdoutWriter2}{StdoutWriter} +\umlprovidedinterface[interface=StdoutWriter, distance=3, padding=0.9cm]{StdoutWriter1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<StdoutWriter>()>, distance=5.8, padding=0.9cm]{StdoutWriter2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/inject_typedef_writer2.tex b/extras/doc/inject_typedef_writer2.tex new file mode 100644 index 0000000..a4c2e2a --- /dev/null +++ b/extras/doc/inject_typedef_writer2.tex @@ -0,0 +1,11 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{StdoutWriter1}{StdoutWriter} +\newnamedcomponent{0}{0}{StdoutWriter2}{StdoutWriter} +\umlprovidedinterface[interface=StdoutWriter, distance=3, padding=0.9cm]{StdoutWriter1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<StdoutWriter>()>, distance=5.8, padding=0.9cm]{StdoutWriter2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/multiplier.tex b/extras/doc/multiplier.tex new file mode 100644 index 0000000..5bfb236 --- /dev/null +++ b/extras/doc/multiplier.tex @@ -0,0 +1,9 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{2}{0}{MultiplierComponent} +\umlprovidedinterface[interface=Multiplier, distance=3, padding=0.5cm]{MultiplierComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/parametrized_component.tex b/extras/doc/parametrized_component.tex new file mode 100644 index 0000000..a85663b --- /dev/null +++ b/extras/doc/parametrized_component.tex @@ -0,0 +1,20 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{FooComponent} +\newnamedcomponent{0}{0}{bind1}{bind} +\newnamedcomponent{0}{2.5}{bind2}{bind} +\newcomponent{5}{0}{OldFooImpl} +\newcomponent{5}{2.5}{NewFooImpl} +\umlprovidedinterface[interface=FooInterface, distance=2.3, padding=1cm]{bind1} +\umlprovidedinterface[interface=FooInterface, distance=2.3, padding=1cm]{bind2} +\umlassemblyconnector[interface=OldFooImpl]{bind1}{OldFooImpl} +\umlassemblyconnector[interface=NewFooImpl]{bind2}{NewFooImpl} +\end{component} +\umlprovidedinterface[interface=FooInterface, distance=6.5, with port]{FooComponent} +\umldep{FooComponent-west-port}{bind1-west-interface} +\umldep{FooComponent-west-port}{bind2-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/provider.tex b/extras/doc/provider.tex new file mode 100644 index 0000000..3709e30 --- /dev/null +++ b/extras/doc/provider.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{2.5}{registerProvider} +\umlprovidedinterface[interface=Database, distance=3, padding=0.9cm]{registerProvider} +\umlrequiredinterface[interface=DbConnectionPool, distance=3.5, padding=0.9cm]{registerProvider} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/provider_functor.tex b/extras/doc/provider_functor.tex new file mode 100644 index 0000000..543d28f --- /dev/null +++ b/extras/doc/provider_functor.tex @@ -0,0 +1,12 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{registerProvider} +\newcomponent{0}{3.8}{bindInstance} +\umlprovidedinterface[interface=MyClass, distance=2.5, padding=0.9cm]{registerProvider} +\umlassemblyconnector[interface=Functor, geometry=|-|]{registerProvider}{bindInstance} +\umlrequiredinterface[interface=Foo, distance=2.5, padding=0.9cm]{registerProvider} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_constructor.tex b/extras/doc/register_constructor.tex new file mode 100644 index 0000000..2476b4f --- /dev/null +++ b/extras/doc/register_constructor.tex @@ -0,0 +1,13 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newnamedcomponent{0}{2.5}{registerConstructor1}{registerConstructor} +\newnamedcomponent{0}{0}{registerConstructor2}{registerConstructor} +\umlprovidedinterface[interface=GreeterImpl, distance=3.3, padding=0.9cm]{registerConstructor1} +\umlrequiredinterface[interface=Writer, distance=3, padding=0.9cm]{registerConstructor1} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<GreeterImpl>()>, distance=6, padding=0.9cm]{registerConstructor2} +\umlrequiredinterface[interface=Writer, distance=3, padding=0.9cm]{registerConstructor2} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_constructor_component.tex b/extras/doc/register_constructor_component.tex new file mode 100644 index 0000000..2970cb8 --- /dev/null +++ b/extras/doc/register_constructor_component.tex @@ -0,0 +1,18 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{GreeterComponent} +\newcomponent{0}{0}{bind} +\newcomponent{6}{0}{registerConstructor} +\umlprovidedinterface[interface=Greeter, distance=2, padding=0.9cm]{bind} +\umlassemblyconnector[interface=GreeterImpl, distance=7]{registerConstructor}{bind} +\umlrequiredinterface[interface=Writer, distance=3, padding=0.9cm]{registerConstructor} +\end{component} +\umlprovidedinterface[interface=Greeter, distance=8, with port]{GreeterComponent} +\umlrequiredinterface[interface=Writer, distance=8, with port]{GreeterComponent} +\umlassoc{GreeterComponent-east-port}{registerConstructor-east-interface} +\umlassoc{GreeterComponent-west-port}{bind-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_factory.tex b/extras/doc/register_factory.tex new file mode 100644 index 0000000..13b97da --- /dev/null +++ b/extras/doc/register_factory.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{registerFactory} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<MyClass>(int)>, distance=5.7, padding=0.9cm]{registerFactory} +\umlrequiredinterface[interface=Foo, distance=2.5, padding=0.9cm]{registerFactory} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_factory_macro.tex b/extras/doc/register_factory_macro.tex new file mode 100644 index 0000000..435bf4c --- /dev/null +++ b/extras/doc/register_factory_macro.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{MyClass} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<MyClass>(int)>, distance=5.3, padding=0.9cm]{MyClass} +\umlrequiredinterface[interface=Foo, distance=2, padding=0.9cm]{MyClass} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_factory_typedef.tex b/extras/doc/register_factory_typedef.tex new file mode 100644 index 0000000..435bf4c --- /dev/null +++ b/extras/doc/register_factory_typedef.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{MyClass} +\umlprovidedinterface[interface=std::function<std::unique\_ptr<MyClass>(int)>, distance=5.3, padding=0.9cm]{MyClass} +\umlrequiredinterface[interface=Foo, distance=2, padding=0.9cm]{MyClass} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/register_factory_use.tex b/extras/doc/register_factory_use.tex new file mode 100644 index 0000000..23d4b3f --- /dev/null +++ b/extras/doc/register_factory_use.tex @@ -0,0 +1,10 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{0}{Bar} +\umlprovidedinterface[interface=Bar, distance=2, padding=0.9cm]{Bar} +\umlrequiredinterface[interface=std::function<std::unique\_ptr<MyClass>(int)>, distance=5, padding=0.9cm]{Bar} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/request_dispatcher.tex b/extras/doc/request_dispatcher.tex new file mode 100644 index 0000000..4834410 --- /dev/null +++ b/extras/doc/request_dispatcher.tex @@ -0,0 +1,22 @@ + +\input{header} +\begin{tikzpicture}[scale=0.7, transform shape] + +\begin{component}{20cm}{1}{0}{RequestDispatcherComponent} +\newcomponent{7}{0}{FooHandlerComponent} +\umlrequiredinterface[interface={Request, ServerContext}, distance=4.2, padding=1.5cm]{FooHandlerComponent} +\newcomponent{7}{3}{BarHandlerComponent} +\umlrequiredinterface[interface={Request, ServerContext}, distance=4.2, padding=1.5cm]{BarHandlerComponent} +\newcomponent{0}{1.5}{RequestDispatcherImpl} +\umlprovidedinterface[interface=RequestDispatcher, distance=3.8cm, padding=1.2cm]{RequestDispatcherImpl} +\umlassemblyconnector[interface=FooHandler]{RequestDispatcherImpl}{FooHandlerComponent} +\umlassemblyconnector[interface=BarHandler]{RequestDispatcherImpl}{BarHandlerComponent} +\end{component} +\umlprovidedinterface[interface=RequestDispatcher, distance=11.1, with port, padding=0cm]{RequestDispatcherComponent} +\umlrequiredinterface[interface={Request, ServerContext}, distance=11.5, with port]{RequestDispatcherComponent} +\umldep{RequestDispatcherComponent-west-port}{RequestDispatcherImpl-west-interface} +\umldep{FooHandlerComponent-east-interface}{RequestDispatcherComponent-east-port} +\umldep{BarHandlerComponent-east-interface}{RequestDispatcherComponent-east-port} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/request_injector.tex b/extras/doc/request_injector.tex new file mode 100644 index 0000000..9b44605 --- /dev/null +++ b/extras/doc/request_injector.tex @@ -0,0 +1,17 @@ + +\input{header} +\begin{tikzpicture}[scale=0.95, transform shape] + +\begin{component}{20cm}{1}{0}{injector} +\newcomponent{0}{1.5}{RequestDispatcherComponent} +\umlprovidedinterface[interface=RequestDispatcher, distance=4.4cm, padding=1.5cm]{RequestDispatcherComponent} +\newnamedcomponent{7.5}{0}{bindInstance1}{bindInstance} +\umlassemblyconnector[interface=ServerContext]{RequestDispatcherComponent}{bindInstance1} +\newnamedcomponent{7.5}{3}{bindInstance2}{bindInstance} +\umlassemblyconnector[interface=Request]{RequestDispatcherComponent}{bindInstance2} +\end{component} +\umlprovidedinterface[interface=RequestDispatcher, distance=9.7, with port, padding=0cm]{injector} +\umldep{injector-west-port}{RequestDispatcherComponent-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/scaler.tex b/extras/doc/scaler.tex new file mode 100644 index 0000000..e25b009 --- /dev/null +++ b/extras/doc/scaler.tex @@ -0,0 +1,15 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{ScalerComponent} +\newcomponent{0}{0}{ScalerImpl} +\newcomponent{6}{0}{MultiplierComponent} +\umlprovidedinterface[interface=ScalerFactory, distance=2.5, padding=0.9cm]{ScalerImpl} +\umlassemblyconnector[interface=Multiplier, distance=6]{ScalerImpl}{MultiplierComponent} +\end{component} +\umlprovidedinterface[interface=ScalerFactory, distance=7.5, with port]{ScalerComponent} +\umlassoc{ScalerComponent-west-port}{ScalerImpl-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/server.tex b/extras/doc/server.tex new file mode 100644 index 0000000..540f3eb --- /dev/null +++ b/extras/doc/server.tex @@ -0,0 +1,9 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{0}{2.5}{ServerComponent} +\umlprovidedinterface[interface=Server, distance=2.8, padding=0.9cm]{ServerComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/simple_adder.tex b/extras/doc/simple_adder.tex new file mode 100644 index 0000000..fa37fea --- /dev/null +++ b/extras/doc/simple_adder.tex @@ -0,0 +1,9 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\newcomponent{2}{0}{SimpleAdderComponent} +\umlprovidedinterface[interface=Adder, distance=4, padding=0.5cm]{SimpleAdderComponent} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/simple_greeter.tex b/extras/doc/simple_greeter.tex new file mode 100644 index 0000000..20d0db3 --- /dev/null +++ b/extras/doc/simple_greeter.tex @@ -0,0 +1,15 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{GreeterComponent} +\newcomponent{0}{0}{GreeterImpl} +\newcomponent{5}{0}{StdoutWriter} +\umlprovidedinterface[interface=Greeter, distance=2.5, padding=0.5cm]{GreeterImpl} +\umlassemblyconnector[interface=Writer, distance=5]{GreeterImpl}{StdoutWriter} +\end{component} +\umlprovidedinterface[interface=Greeter, distance=6.5, with port]{GreeterComponent} +\umlassoc{GreeterComponent-west-port}{GreeterImpl-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/simple_incrementer.tex b/extras/doc/simple_incrementer.tex new file mode 100644 index 0000000..aa8b700 --- /dev/null +++ b/extras/doc/simple_incrementer.tex @@ -0,0 +1,15 @@ + +\input{header} +\begin{tikzpicture}[scale=1, transform shape] + +\begin{component}{20cm}{1}{0}{SimpleIncrementerComponent} +\newcomponent{0}{0}{IncrementerImplComponent} +\newcomponent{6}{0}{SimpleAdderComponent} +\umlprovidedinterface[interface=Incrementer, distance=3.7, padding=0.9cm]{IncrementerImplComponent} +\umlassemblyconnector[interface=Adder, distance=7]{IncrementerImplComponent}{SimpleAdderComponent} +\end{component} +\umlprovidedinterface[interface=Incrementer, distance=8, with port]{SimpleIncrementerComponent} +\umlassoc{SimpleIncrementerComponent-west-port}{IncrementerImplComponent-west-interface} + +\end{tikzpicture} +\input{footer} diff --git a/extras/doc/templated_component.tex b/extras/doc/templated_component.tex new file mode 100644 index 0000000..10fe8c6 --- /dev/null +++ b/extras/doc/templated_component.tex @@ -0,0 +1,16 @@ + +\input{header} +\begin{tikzpicture}[scale=0.91, transform shape] + +\begin{component}{20cm}{1}{0}{FooComponent} +\newcomponent{0}{0}{bind} +\newcomponent{5}{0}{FooImpl} +\umlprovidedinterface[interface={FooInterface<T, std::vector<T>{}>}, distance=4, padding=3.5cm]{bind} +\umlassemblyconnector[interface=FooImpl<T>]{bind}{FooImpl} +\end{component} +\umlprovidedinterface[interface={FooInterface<T, std::vector<T>{}>}, distance=10, with port]{FooComponent} +\umlassoc{FooComponent-west-port}{bind-west-interface} +\node at (9.1,0.3) {(for any type T)}; + +\end{tikzpicture} +\input{footer} diff --git a/extras/dockerfiles/Dockerfile.ubuntu-14.04 b/extras/dockerfiles/Dockerfile.ubuntu-14.04 new file mode 100644 index 0000000..42acb44 --- /dev/null +++ b/extras/dockerfiles/Dockerfile.ubuntu-14.04 @@ -0,0 +1,9 @@ +FROM ubuntu:14.04 +MAINTAINER Marco Poletti <poletti.marco@gmail.com> + +COPY ubuntu-14.04_custom.list /etc/apt/sources.list.d/ +COPY common_install.sh common_cleanup.sh ubuntu-14.04_install.sh / + +RUN bash -x /common_install.sh && \ + bash -x /ubuntu-14.04_install.sh && \ + bash -x /common_cleanup.sh diff --git a/extras/dockerfiles/Dockerfile.ubuntu-16.04 b/extras/dockerfiles/Dockerfile.ubuntu-16.04 new file mode 100644 index 0000000..b53b883 --- /dev/null +++ b/extras/dockerfiles/Dockerfile.ubuntu-16.04 @@ -0,0 +1,9 @@ +FROM ubuntu:16.04 +MAINTAINER Marco Poletti <poletti.marco@gmail.com> + +COPY ubuntu-16.04_custom.list /etc/apt/sources.list.d/ +COPY common_install.sh common_cleanup.sh ubuntu-16.04_install.sh / + +RUN bash -x /common_install.sh && \ + bash -x /ubuntu-16.04_install.sh && \ + bash -x /common_cleanup.sh diff --git a/extras/dockerfiles/Dockerfile.ubuntu-17.04 b/extras/dockerfiles/Dockerfile.ubuntu-17.04 new file mode 100644 index 0000000..6718523 --- /dev/null +++ b/extras/dockerfiles/Dockerfile.ubuntu-17.04 @@ -0,0 +1,9 @@ +FROM ubuntu:17.04 +MAINTAINER Marco Poletti <poletti.marco@gmail.com> + +COPY ubuntu-17.04_custom.list /etc/apt/sources.list.d/ +COPY common_install.sh common_cleanup.sh ubuntu-17.04_install.sh / + +RUN bash -x /common_install.sh && \ + bash -x /ubuntu-17.04_install.sh && \ + bash -x /common_cleanup.sh diff --git a/extras/dockerfiles/Dockerfile.ubuntu-17.10 b/extras/dockerfiles/Dockerfile.ubuntu-17.10 new file mode 100644 index 0000000..3dae283 --- /dev/null +++ b/extras/dockerfiles/Dockerfile.ubuntu-17.10 @@ -0,0 +1,9 @@ +FROM ubuntu:17.10 +MAINTAINER Marco Poletti <poletti.marco@gmail.com> + +COPY ubuntu-17.10_custom.list /etc/apt/sources.list.d/ +COPY common_install.sh common_cleanup.sh ubuntu-17.10_install.sh / + +RUN bash -x /common_install.sh && \ + bash -x /ubuntu-17.10_install.sh && \ + bash -x /common_cleanup.sh diff --git a/extras/dockerfiles/common_cleanup.sh b/extras/dockerfiles/common_cleanup.sh new file mode 100644 index 0000000..a02258a --- /dev/null +++ b/extras/dockerfiles/common_cleanup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +# Strip some binaries that aren't already stripped, to save space. +for f in $(find /usr/lib/ /usr/bin -type f | fgrep -v bazel | fgrep -v python) +do + if file "$f" | fgrep 'executable' | fgrep -q 'stripped' + then + strip --strip-unneeded $f + fi +done + +# This was only needed above, we don't need it in the final image. +apt-get remove -y wget file python3-pip +apt-get autoremove -y + +# Remove temporary files, to save space. +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/extras/dockerfiles/common_install.sh b/extras/dockerfiles/common_install.sh new file mode 100644 index 0000000..8f32478 --- /dev/null +++ b/extras/dockerfiles/common_install.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +apt-get update -qq +apt-get install -y --no-install-recommends wget + +wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | apt-key add - + +# 1E9377A2BA9EF27F is the key for the ubuntu-toolchain-r PPA. +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F | cat + +# 15CF4D18AF4F7421 is the key for the http://apt.llvm.org/artful PPA. +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15CF4D18AF4F7421 | cat + +apt-get update -qq +apt-get install -y --allow-unauthenticated --no-install-recommends \ + file \ + valgrind \ + make \ + cmake \ + libboost-dev \ + g++-5 \ + clang-3.8 \ + clang-3.9 \ + clang-4.0 \ + libc++-dev \ + python3-pip \ + python3-setuptools \ + dirmngr + +pip3 install --upgrade pip +pip3 install wheel +pip3 install pytest +pip3 install pytest-xdist +pip3 install sh diff --git a/extras/dockerfiles/ubuntu-14.04_custom.list b/extras/dockerfiles/ubuntu-14.04_custom.list new file mode 100644 index 0000000..b45b5b3 --- /dev/null +++ b/extras/dockerfiles/ubuntu-14.04_custom.list @@ -0,0 +1,10 @@ +deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main +deb-src http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main +deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.7 main +deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.7 main +deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.8 main +deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.8 main +deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main +deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main +deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-4.0 main +deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-4.0 main diff --git a/extras/dockerfiles/ubuntu-14.04_install.sh b/extras/dockerfiles/ubuntu-14.04_install.sh new file mode 100644 index 0000000..edb7825 --- /dev/null +++ b/extras/dockerfiles/ubuntu-14.04_install.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +apt-get install -y --allow-unauthenticated --no-install-recommends \ + clang-3.5 \ + clang-3.6 \ + clang-3.7 \ + g++-4.9 diff --git a/extras/dockerfiles/ubuntu-16.04_custom.list b/extras/dockerfiles/ubuntu-16.04_custom.list new file mode 100644 index 0000000..54c95ff --- /dev/null +++ b/extras/dockerfiles/ubuntu-16.04_custom.list @@ -0,0 +1,11 @@ +deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main +deb-src http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main +deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main +deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main +deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.8 main +deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.8 main +deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.9 main +deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.9 main +deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main +deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main +deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8 diff --git a/extras/dockerfiles/ubuntu-16.04_install.sh b/extras/dockerfiles/ubuntu-16.04_install.sh new file mode 100644 index 0000000..1452630 --- /dev/null +++ b/extras/dockerfiles/ubuntu-16.04_install.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +apt-get install -y --no-install-recommends \ + curl + +# For the Bazel repository +curl https://bazel.build/bazel-release.pub.gpg | apt-key add - + +apt-get install -y --allow-unauthenticated --no-install-recommends \ + clang-3.5 \ + clang-3.6 \ + clang-3.7 \ + g++-4.9 \ + g++-6 \ + python \ + bazel \ + openjdk-8-jdk \ + clang-format + +pip3 install typed_ast diff --git a/extras/dockerfiles/ubuntu-17.04_custom.list b/extras/dockerfiles/ubuntu-17.04_custom.list new file mode 100644 index 0000000..dfafeb4 --- /dev/null +++ b/extras/dockerfiles/ubuntu-17.04_custom.list @@ -0,0 +1,8 @@ +deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu zesty main +deb-src http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu zesty main +deb http://apt.llvm.org/zesty/ llvm-toolchain-zesty main +deb-src http://apt.llvm.org/zesty/ llvm-toolchain-zesty main +deb http://apt.llvm.org/zesty/ llvm-toolchain-zesty-3.9 main +deb-src http://apt.llvm.org/zesty/ llvm-toolchain-zesty-3.9 main +deb http://apt.llvm.org/zesty/ llvm-toolchain-zesty-4.0 main +deb-src http://apt.llvm.org/zesty/ llvm-toolchain-zesty-4.0 main diff --git a/extras/dockerfiles/ubuntu-17.04_install.sh b/extras/dockerfiles/ubuntu-17.04_install.sh new file mode 100644 index 0000000..afa7b5e --- /dev/null +++ b/extras/dockerfiles/ubuntu-17.04_install.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F + +apt-get install -y --allow-unauthenticated --no-install-recommends \ + clang-3.7 \ + g++-4.9 \ + g++-6 \ + python \ + clang-format + +pip3 install typed_ast diff --git a/extras/dockerfiles/ubuntu-17.10_custom.list b/extras/dockerfiles/ubuntu-17.10_custom.list new file mode 100644 index 0000000..e72d24a --- /dev/null +++ b/extras/dockerfiles/ubuntu-17.10_custom.list @@ -0,0 +1,8 @@ +deb [trusted=yes] http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu artful main +deb-src [trusted=yes] http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu artful main +deb [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful main +deb-src [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful main +deb [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful-4.0 main +deb-src [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful-4.0 main +deb [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful-5.0 main +deb-src [trusted=yes] http://apt.llvm.org/artful/ llvm-toolchain-artful-5.0 main diff --git a/extras/dockerfiles/ubuntu-17.10_install.sh b/extras/dockerfiles/ubuntu-17.10_install.sh new file mode 100644 index 0000000..36fd97e --- /dev/null +++ b/extras/dockerfiles/ubuntu-17.10_install.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F + +apt-get install -y --allow-unauthenticated --no-install-recommends \ + g++-7 \ + python \ + python3-sh \ + python3-typed-ast \ + clang-format + +pip3 install typed_ast diff --git a/extras/fruit-2.0.0.ebuild b/extras/fruit-2.0.0.ebuild new file mode 100644 index 0000000..da476a9 --- /dev/null +++ b/extras/fruit-2.0.0.ebuild @@ -0,0 +1,28 @@ + +# Copyright 2014 Google Inc. All rights reserved. +# +# 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. +# +# $Header: $ + +EAPI=4 +inherit cmake-utils + +DESCRIPTION="Dependency Injection Framework For C++" +HOMEPAGE="https://github.com/google/fruit" +SRC_URI="https://github.com/google/fruit/archive/v${PV}.tar.gz" + +LICENSE="Apache-2.0" +SLOT="0" +KEYWORDS="amd64 ~x86" +IUSE="" diff --git a/extras/git_hooks/pre-commit b/extras/git_hooks/pre-commit new file mode 100755 index 0000000..faf6ee0 --- /dev/null +++ b/extras/git_hooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/bash + +check_travis_yml() { + X=`mktemp` + if ! (extras/scripts/travis_yml_generator.py >$X && cmp -s $X .travis.yml) + then + echo 'The .travis.yml file is out of date, run "extras/scripts/travis_yml_generator.py >.travis.yml" and try again.' + return 1 + fi +} + +check_travis_yml || exit 1 diff --git a/extras/packaging/CMakeLists.txt b/extras/packaging/CMakeLists.txt new file mode 100644 index 0000000..7592bf0 --- /dev/null +++ b/extras/packaging/CMakeLists.txt @@ -0,0 +1,24 @@ + +set(PACKAGING_FILES +debian.control +debian.rules +debian.compat +libfruit.dsc +libfruit.install +libfruit.spec +) + +# This places configured files (build files with @FRUIT_VERSION@ replaced) in build/extras/packaging/built + +foreach(F ${PACKAGING_FILES}) + configure_file(${F} built/${F} @ONLY) +endforeach(F) + +configure_file(PKGBUILD PKGBUILD-template @ONLY) + +add_custom_target(fruit-${FRUIT_VERSION}.tar.gz ALL + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/PKGBUILD-template + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../.. + COMMAND git archive -o ${CMAKE_CURRENT_BINARY_DIR}/built/fruit-${FRUIT_VERSION}.tar.gz --prefix=fruit-${FRUIT_VERSION}/ HEAD + COMMAND md5sum ${CMAKE_CURRENT_BINARY_DIR}/built/fruit-${FRUIT_VERSION}.tar.gz | awk '{print $$1}' >${CMAKE_CURRENT_BINARY_DIR}/tarball-md5 + COMMAND sed "\"s/.*md5sums.*/md5sums=(`cat" "${CMAKE_CURRENT_BINARY_DIR}/tarball-md5`)/\"" <${CMAKE_CURRENT_BINARY_DIR}/PKGBUILD-template >${CMAKE_CURRENT_BINARY_DIR}/built/PKGBUILD) diff --git a/extras/packaging/PKGBUILD b/extras/packaging/PKGBUILD new file mode 100644 index 0000000..b7b06cc --- /dev/null +++ b/extras/packaging/PKGBUILD @@ -0,0 +1,30 @@ +# Maintainer: Marco Poletti <poletti.marco@gmail.com> +pkgname=libfruit +pkgver=@FRUIT_VERSION@ +pkgrel=0 +pkgdesc="Fruit is a dependency injection framework for C++." +url="https://github.com/google/fruit" +arch=('x86_64' 'i686') +license=('Apache') +depends=('cmake' 'boost' 'gcc>=5.0.0') +optdepends=() +makedepends=() +conflicts=() +replaces=() +backup=() +install='libfruit.install' +source=("fruit-${pkgver}.tar.gz") +md5sums=('d66cc12861e105cc38e5b878776f9f79') + +build() { + cd "${srcdir}/fruit-${pkgver}" + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo . + make +} + +package() { + cd "${srcdir}/fruit-${pkgver}" + make DESTDIR="${pkgdir}" install +} + +# vim:set ts=2 sw=2 et:
\ No newline at end of file diff --git a/extras/packaging/debian.compat b/extras/packaging/debian.compat new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/extras/packaging/debian.compat @@ -0,0 +1 @@ +5
\ No newline at end of file diff --git a/extras/packaging/debian.control b/extras/packaging/debian.control new file mode 100644 index 0000000..6fb8f7a --- /dev/null +++ b/extras/packaging/debian.control @@ -0,0 +1,23 @@ +Source: libfruit +Section: devel +Priority: optional +Maintainer: Marco Poletti <poletti.marco@gmail.com> +Build-Depends: debhelper (>= 4.1.16), cmake, libboost-dev, gcc (>= 4:5.0.0) + +Package: libfruit +Architecture: any +Depends: ${shlibs:Depends} +Description: Dependency Injection Framework For C++ + Fruit is a dependency injection framework for C++, loosely inspired by the + Guice framework for Java. + It uses C++ metaprogramming together with some new C++11 features to detect + most injection problems at compile-time. + +Package: fruit-dev +Architecture: any +Depends: ${shlibs:Depends}, libfruit +Description: Dependency Injection Framework For C++ - Development files + Fruit is a dependency injection framework for C++, loosely inspired by the + Guice framework for Java. + It uses C++ metaprogramming together with some new C++11 features to detect + most injection problems at compile-time. diff --git a/extras/packaging/debian.rules b/extras/packaging/debian.rules new file mode 100644 index 0000000..6ce5073 --- /dev/null +++ b/extras/packaging/debian.rules @@ -0,0 +1,74 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +CFLAGS = -g +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) +CFLAGS += -O0 +else +CFLAGS += -O2 +endif + +build: build-stamp +build-stamp: + dh_testdir + + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo + make all + + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + + make clean || true + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + make install DESTDIR=/usr/src/packages/BUILD/debian/libfruit + +# Build architecture-independent files here. +binary-indep: build install + # We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot +# dh_installdebconf + dh_installdocs + dh_installexamples + dh_installmenu +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_installinit + dh_installcron + dh_installman + dh_installinfo +# dh_undocumented + dh_installchangelogs + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_makeshlibs + dh_installdeb +# dh_perl + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install
\ No newline at end of file diff --git a/extras/packaging/libfruit.dsc b/extras/packaging/libfruit.dsc new file mode 100644 index 0000000..07bd92e --- /dev/null +++ b/extras/packaging/libfruit.dsc @@ -0,0 +1,10 @@ +Format: 1.0 +Source: libfruit +Version: @FRUIT_VERSION@-0 +Binary: libfruit +Maintainer: Marco Poletti <poletti.marco@gmail.com> +Architecture: any +Build-Depends: debhelper (>= 4.1.16), cmake, libboost-dev, gcc (>= 4:5.0.0) +Files: + d57283ebb8157ae919762c58419353c8 133282 libfruit_@FRUIT_VERSION@.orig.tar.gz + 2fecf324a32123b08cefc0f047bca5ee 63176 libfruit_@FRUIT_VERSION@-0.diff.tar.gz
\ No newline at end of file diff --git a/extras/packaging/libfruit.install b/extras/packaging/libfruit.install new file mode 100644 index 0000000..bb7b160 --- /dev/null +++ b/extras/packaging/libfruit.install @@ -0,0 +1 @@ +# Intentionally empty diff --git a/extras/packaging/libfruit.spec b/extras/packaging/libfruit.spec new file mode 100644 index 0000000..b84e178 --- /dev/null +++ b/extras/packaging/libfruit.spec @@ -0,0 +1,64 @@ +# +# spec file for package fruit +# + +Name: libfruit +Version: @FRUIT_VERSION@ +Release: 0 +Summary: Dependency Injection Framework For C++ +License: Apache-2.0 +Group: Development/Libraries/C and C++ +Url: https://github.com/google/fruit +Source0: fruit-%{version}.tar.gz + +BuildRequires: cmake +BuildRequires: boost-devel + +%if 0%{?fedora_version} || 0%{?rhel_version} || 0%{?centos_version} +BuildRequires: gcc-c++ >= 5.0.0 +%else +# OpenSUSE doesn't include the bugfix release version component in the package version. +BuildRequires: gcc-c++ >= 5.0 +%endif + +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +%description +Fruit is a dependency injection framework for C++, loosely inspired by the +Guice framework for Java. +It uses C++ metaprogramming together with some new C++11 features to detect +most injection problems at compile-time. + +%package devel +Summary: Dependency Injection Framework For C++ - Development Files +License: Apache-2.0 +Group: Development/Libraries/C and C++ +Url: https://github.com/google/fruit +Requires: libfruit = %{version} + +%description devel +Fruit is a dependency injection framework for C++, loosely inspired by the +Guice framework for Java. +It uses C++ metaprogramming together with some new C++11 features to detect +most injection problems at compile-time. + +%prep +%setup -q -n fruit-%{version} + +%build +cmake -DCMAKE_INSTALL_PREFIX=%{_prefix} -DINSTALL_LIBRARY_DIR=%{_libdir} -DCMAKE_BUILD_TYPE=RelWithDebInfo + +%{__make} %{?jobs:-j%jobs} + +%install +%{__make} DESTDIR=%{buildroot} install + +%files +%defattr(-,root,root) +%{_libdir}/libfruit.* + +%files devel +%defattr(-,root,root) +%{_includedir}/fruit + +%changelog diff --git a/extras/scripts/analyze_template_instantiations_clang_diagnostics.py b/extras/scripts/analyze_template_instantiations_clang_diagnostics.py new file mode 100755 index 0000000..3e661c8 --- /dev/null +++ b/extras/scripts/analyze_template_instantiations_clang_diagnostics.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from concurrent import futures + +import itertools +import sys +import re +import pygraphviz as gv +import ply.lex as lex +import ply.yacc as yacc +from functools import lru_cache as memoize + +diagnostic_header_pattern = re.compile('[^ ]+\.[^ ]+:[0-9]+:[0-9]+: ([^ ]*): (.*)') +in_file_included_from_pattern = re.compile('In file included from .*:') +in_instantiation_of_template_pattern = re.compile('in instantiation of (.*) (?:requested|required) here') +static_warning_marked_deprecated_here_pattern = re.compile('\'static_warning\' has been explicitly marked deprecated here') + +class Diagnostic: + def __init__(self, kind, message): + self.kind = kind + self.message = message + self.template_instantiation_trace = [] + +tokens = ( + 'LPAREN', + 'RPAREN', + 'LBRACKET', + 'RBRACKET', + 'LBRACE', + 'RBRACE', + 'LESS_THAN', + 'GREATER_THAN', + 'DOUBLE_COLON', + 'COMMA', + 'IDENTIFIER', + 'ASTERISK', + 'AMPERSAND', +) + +t_LPAREN = r'\(' +t_RPAREN = r'\)' +t_LBRACKET = r'\[' +t_RBRACKET = r'\]' +t_LBRACE = r'}' +t_RBRACE = r'{' +t_LESS_THAN = r'<' +t_GREATER_THAN = r'>' +t_DOUBLE_COLON = r'::' +t_COMMA = r',' +t_ASTERISK = r'\*' +t_AMPERSAND = r'&' +# We conflate numbers as identifiers too, we don't care about the difference. +t_IDENTIFIER = r'[a-zA-Z0-9_]+' + +t_ignore = ' \t' + +def t_error(t): + raise Exception("Illegal character '%s' followed by %s" % (t.value[0], t.value[1:])) + +class LayoutNeedsMultipleLinesException(Exception): + pass + +class AstNode: + def __str__(self): + return ''.join(self) + +class TerminalAstNode(AstNode): + def __init__(self, s): + self.s = s + self.is_multiline = (s == '\n') + # last_line_length is the string length if s is not a multiline string. + # For multiline strings ending in a newline, this is 0. + if self.is_multiline: + self.first_line_length = 0 + self.last_line_length = 0 + self.max_line_length = 0 + else: + # This never happens ATM, so we don't handle it. + assert not '\n' in s + + self.first_line_length = len(s) + self.last_line_length = len(s) + self.max_line_length = len(s) + + def __iter__(self): + return iter((self.s,)) + +class NonTerminalAstNode(AstNode): + def __init__(self, children_ast_nodes): + self.children_ast_nodes = children_ast_nodes + first_line_length = 0 + last_line_length = 0 + is_multiline = False + max_line_length = 0 + for node in children_ast_nodes: + if node.is_multiline: + last_line_length = node.last_line_length + max_line_length = max(max_line_length, last_line_length + node.first_line_length, node.max_line_length) + is_multiline = True + else: + last_line_length += node.last_line_length + max_line_length = max(max_line_length, last_line_length) + + self.first_line_length = first_line_length + self.last_line_length = last_line_length + self.is_multiline = is_multiline + self.max_line_length = max_line_length + + def __iter__(self): + return itertools.chain(*self.children_ast_nodes) + +max_line_length = 80 +# Size of an indent in spaces. +single_indent_length = 4 + +class TerminalNodeFactory(): + def __init__(self, s): + self.s = s + + def __call__(self, current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only): + return TerminalAstNode(self.s) + +# 'balanced_string' nodes evaluate to a function (or a callable object) taking these parameters: +# current_indent (integer): the indentation in the current line (spaces only) +# current_line_length (integer): the number of preceding characters in the current line (>=current_indent) +# inside_meta_type (boolean): whether we're inside a Type<...> +# last_token_was_type_wrapper (boolean): whether the immediately-preceding token was the identifier 'Type' +# and returning an AstNode +# 'comma_separated_balanced_string' nodes evaluate to a tuple of such functions + +def p_comma_separated_balanced_string_empty(p): + 'comma_separated_balanced_string : ' + p[0] = tuple() + +def p_comma_separated_balanced_string_not_empty(p): + 'comma_separated_balanced_string : COMMA balanced_string comma_separated_balanced_string' + p[0] = ( + p[2], + *(p[3]) + ) + +def p_optional_balanced_string_empty(p): + 'optional_balanced_string : ' + p[0] = TerminalNodeFactory('') + +def p_optional_balanced_string_not_empty(p): + 'optional_balanced_string : balanced_string' + p[0] = p[1] + +class BalancedStringTerminalNodeFactory(): + def __init__(self, first_token, node_factory): + self.first_token = first_token + self.node_factory = node_factory + + def __call__(self, current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only): + terminal_node = TerminalAstNode(self.first_token) + non_terminal_node = self.node_factory( + current_indent, + current_line_length + len(self.first_token), + inside_meta_type, + self.first_token == 'Type', + accept_single_line_only) + if non_terminal_node is None: + return None + return NonTerminalAstNode((terminal_node, non_terminal_node)) + +def p_balanced_string_terminal(p): + '''balanced_string : DOUBLE_COLON balanced_string + | IDENTIFIER optional_balanced_string + | ASTERISK optional_balanced_string + | AMPERSAND optional_balanced_string + ''' + first_token = p[1] + node_factory = p[2] + + p[0] = BalancedStringTerminalNodeFactory(first_token, node_factory) + +def create_composite_node_from_factories(node_factory_inside_meta_type_pairs, current_line_length, accept_single_line_only): + nodes = [] + for node_factory, current_indent, inside_meta_type in node_factory_inside_meta_type_pairs: + node = node_factory(current_indent, current_line_length, inside_meta_type, False, accept_single_line_only) + if node is None: + return None + nodes.append(node) + if node.is_multiline: + if accept_single_line_only: + raise Exception('Unexpected multiline, due to factory: ' + node_factory) + # Note that due to the way we break lines, the last line will have the same indent as the first. + # So we don't need to update current_indent here. + current_line_length = node.last_line_length + else: + current_line_length += node.last_line_length + return NonTerminalAstNode(nodes) + +def compute_layout(left_token, intermediate_node_factories, right_token, rhs_node_factory, current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only): + # We lay out the result in one of two ways: + # + # $previousIndent $previousContent LPAREN x1, x2, x3 RPAREN balanced_string + # + # Or: + # + # $previousIndent $previousContent LPAREN + # $previousIndent $indent x1 , + # $previousIndent $indent x2 , + # $previousIndent $indent x3 RPAREN balanced_string + + entering_meta_type = last_token_was_type_wrapper + + # First, we try to use the first format if possible + node_factory_inside_meta_type_pairs = [ + (TerminalNodeFactory(left_token), current_indent, inside_meta_type), + *((intermediate_node_factory, current_indent, (inside_meta_type or entering_meta_type)) + for intermediate_node_factory in intermediate_node_factories), + (TerminalNodeFactory(right_token), current_indent, inside_meta_type), + (rhs_node_factory, current_indent, inside_meta_type), + ] + node_with_single_line_layout = create_composite_node_from_factories(node_factory_inside_meta_type_pairs, current_line_length, True) + if node_with_single_line_layout is not None and node_with_single_line_layout.max_line_length <= max_line_length: + assert not node_with_single_line_layout.is_multiline + return node_with_single_line_layout + + if accept_single_line_only: + return None + + # The result exceeds the line length, let's switch to the second one. + node_factory_inside_meta_type_pairs = [ + (TerminalNodeFactory(left_token), + current_indent, + inside_meta_type) + ] + new_indent_length = current_indent + single_indent_length + comma_node_factory_inside_meta_type_pair = (TerminalNodeFactory(','), current_indent, inside_meta_type or entering_meta_type) + newline_node_factory_inside_meta_type_pair = (TerminalNodeFactory('\n'), current_indent, inside_meta_type or entering_meta_type) + indent_node_factory_inside_meta_type_pair = (TerminalNodeFactory(' ' * new_indent_length), current_indent, inside_meta_type or entering_meta_type) + for inner_node_factory in intermediate_node_factories: + node_factory_inside_meta_type_pairs.append(newline_node_factory_inside_meta_type_pair) + node_factory_inside_meta_type_pairs.append(indent_node_factory_inside_meta_type_pair) + node_factory_inside_meta_type_pairs.append((inner_node_factory, new_indent_length, inside_meta_type or entering_meta_type)) + node_factory_inside_meta_type_pairs.append(comma_node_factory_inside_meta_type_pair) + node_factory_inside_meta_type_pairs.pop() + node_factory_inside_meta_type_pairs.append((TerminalNodeFactory(right_token), current_indent, inside_meta_type)) + node_factory_inside_meta_type_pairs.append((rhs_node_factory, current_indent, inside_meta_type)) + return create_composite_node_from_factories(node_factory_inside_meta_type_pairs, current_line_length, accept_single_line_only) + + +def p_balanced_string_with_balanced_token_no_comma_separated_elems(p): + '''balanced_string : LPAREN RPAREN optional_balanced_string + | LBRACKET RBRACKET optional_balanced_string + | LBRACE RBRACE optional_balanced_string + | LESS_THAN GREATER_THAN optional_balanced_string + ''' + p_1 = p[1] + p_2 = p[2] + p_3 = p[3] + def result(current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only): + return compute_layout(p_1, [], p_2, p_3, current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only) + + p[0] = result + +def p_balanced_string_with_balanced_token_some_comma_separated_elems(p): + '''balanced_string : LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + | LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + | LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + | LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + ''' + p_1 = p[1] + p_2 = p[2] + p_3 = p[3] + p_4 = p[4] + p_5 = p[5] + def result(current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only): + if not inside_meta_type: + if p_1 == '(' and p_4 == ')': + if len(p_3) == 0: + if isinstance(p_2, BalancedStringTerminalNodeFactory) and p_2.first_token == '*': + if isinstance(p_2.node_factory, TerminalNodeFactory) and p_2.node_factory.s == '': + # Special case: we're not inside a Type<...> and we've encountered a '(*)'. + # Discard it and just print the rhs. + return p_5(current_indent, current_line_length, inside_meta_type, False, accept_single_line_only) + + return compute_layout(p_1, (p_2, *(p_3)), p_4, p_5, current_indent, current_line_length, inside_meta_type, last_token_was_type_wrapper, accept_single_line_only) + + p[0] = result + +def p_error(p): + raise Exception("Syntax error when parsing meta type: ", p[:]) + +lexer = lex.lex() +parser = yacc.yacc(start='balanced_string') + +strings_to_remove = re.compile(r'template class |template type alias |function template specialization |member class |member function |default argument for |fruit::impl::meta::|fruit::impl::|fruit::') + +def do_simplify_template_trace_element(element): + element, _ = re.subn(strings_to_remove, '', element) + element = element.strip() + if element[0] != '\'' or element[-1] != '\'': + raise Exception('Expected single quotes in: ' + element) + element = element[1:-1] + if element.startswith('DoEval<') and element[-1] == '>': + element = element[7:-1] + result = ''.join(parser.parse(element, lexer)(0, 0, False, False, False)) + return result + +@memoize(maxsize=1000) +def simplify_template_trace_element(element, executor): + return executor.submit(do_simplify_template_trace_element, element) + +def to_dot_left_justified_string(s): + return '\\l'.join(s.splitlines() + ['']) + +def main(): + diagnostics = [] + + with futures.ProcessPoolExecutor() as executor: + lines = sys.stdin.readlines() + for line_number, line in enumerate(lines): + # Remove the newline + line = line[:-1] + + matches = in_file_included_from_pattern.search(line) + if matches: + continue + + matches = diagnostic_header_pattern.search(line) + if matches: + diagnostic_kind, diagnostic_message = matches.groups() + if diagnostic_kind == 'error': + diagnostics.append(Diagnostic(diagnostic_kind, diagnostic_message)) + print('Processing diagnostic. (%s / %s) ' % (line_number, len(lines)), file=sys.stderr) + elif diagnostic_kind == 'note': + matches = in_instantiation_of_template_pattern.search(diagnostic_message) + if matches: + if not diagnostics: + raise Exception('Found template instantiation note before any error diagnostic: %s' % diagnostic_message) + if 'in instantiation of template type alias' in line: + pass + else: + group = matches.groups()[0] + trace_element_future = simplify_template_trace_element(group, executor) + diagnostics[-1].template_instantiation_trace.append(trace_element_future) + continue + + matches = static_warning_marked_deprecated_here_pattern.search(diagnostic_message) + if matches: + continue + + raise Exception('Found unknown note: %s' % diagnostic_message) + + call_graph = {} + graph = gv.AGraph(directed=True) + + for diagnostic_index, diagnostic in enumerate(diagnostics): + if diagnostic_index % 10 == 0: + print('Constructing dep graph: iteration %s/%s' % (diagnostic_index, len(diagnostics)), file=sys.stderr) + + template_instantiation_trace = [trace_element_future.result() for trace_element_future in diagnostic.template_instantiation_trace] + for called, caller in zip(template_instantiation_trace[1:], template_instantiation_trace[2:]): + if called in call_graph and call_graph[called] != caller: + # Avoid this edge, so that the resulting graph is a tree + continue + graph.add_edge(to_dot_left_justified_string(caller), to_dot_left_justified_string(called)) + call_graph[called] = caller + + print(graph) + +if __name__ == '__main__': + main() diff --git a/extras/scripts/filter_gcc_error.sh b/extras/scripts/filter_gcc_error.sh new file mode 100755 index 0000000..2fecab6 --- /dev/null +++ b/extras/scripts/filter_gcc_error.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +export CurPos=0 +export TOKENS=() + +mergeInSingleLine() { + OPENING_TOKEN="${TOKENS[$CurPos]}" + echo -n "${OPENING_TOKEN}" + CurPos=$[$CurPos + 1] + if ! [[ "${OPENING_TOKEN}" =~ \< ]] && ! [[ "${OPENING_TOKEN}" =~ \( ]] + then + # This is not an opening token, it's an atomic component. + return + fi + while true + do + if ! [[ "${TOKENS[$CurPos]}" =~ \> ]] && ! [[ "${TOKENS[$CurPos]}" =~ \) ]] + then + mergeInSingleLine + else + if [[ "${TOKENS[$CurPos]}" =~ \< ]] || [[ "${TOKENS[$CurPos]}" =~ \( ]] + then + # Both an opening and a closing token, print it and go ahead. + echo -n "${TOKENS[$CurPos]}" + CurPos=$[$CurPos + 1] + else + break + fi + fi + done + echo -n "${TOKENS[CurPos]}" + CurPos=$[$CurPos + 1] +} + +considerMerging() { + OPENING_TOKEN="${TOKENS[CurPos]}" + if ! [[ "${OPENING_TOKEN}" =~ \< ]] && ! [[ "${OPENING_TOKEN}" =~ \( ]] + then + # This is not an opening token, it's an atomic component. + echo "${OPENING_TOKEN}" + CurPos=$[$CurPos + 1] + return; + fi + if [[ "${OPENING_TOKEN}" = "Type<" ]] + then + # Type<...> should be on a single line. + mergeInSingleLine + echo + return; + fi + echo "${OPENING_TOKEN}" + CurPos=$[$CurPos + 1] + while true + do + if ! [[ "${TOKENS[$CurPos]}" =~ \> ]] && ! [[ "${TOKENS[$CurPos]}" =~ \) ]] + then + considerMerging + else + if [[ "${TOKENS[$CurPos]}" =~ \< ]] || [[ "${TOKENS[$CurPos]}" =~ \( ]] + then + # Both an opening and a closing token, print it and go ahead. + echo "${TOKENS[$CurPos]}" + CurPos=$[$CurPos + 1] + else + break + fi + fi + done + echo "${TOKENS[$CurPos]}" + CurPos=$[$CurPos + 1] +} + +while read line +do + if [[ "${line}" =~ required\ from\ .*DoEval\< ]] + then + echo + IFS=$'\r\n' GLOBIGNORE='*' :; + TOKENS=($(echo "${line}" | + sed 's| (\*)||g; + s|.* required from .*DoEval<||; + s|>.$||; + s/fruit::impl::meta:://g; + s| >|>|g; + s|, |,|g' | + sed 's|[>]|,>|g; + s|[)]|,)|g' | + sed 's|[<]|<\n|g; + s|[(]|(\n|g; + s|[,]|,\n|g' | + grep -v "^,$" )) + considerMerging | + sed 's|,>|>|g; + s|,)|)|g' | + awk -F@ '/^[^()<>]*[>)][^()<>]*[(<][^()<>]*$/ {curIndent-=2; for (i=0; i<curIndent; i++) { printf(" "); } print; curIndent+=2; next;} + /[()<>].*[()<>]/ || !/[()<>]/ {for (i=0; i<curIndent; i++) { printf(" "); } print; next;} + /[(<]/ { for (i=0; i<curIndent; i++) { printf(" "); } print; curIndent+=2; next; } + /[)>]/ || /^[^()<>]*>/ { curIndent-=2; for (i=0; i<curIndent; i++) { printf(" "); } print; next; } + ' + elif [[ "${line}" =~ required\ from\ .*EvalFun\< ]] + then + echo + IFS=$'\r\n' GLOBIGNORE='*' :; + TOKENS=($(echo "${line}" | + sed 's|,|(|' | + sed 's| (\*)||g; + s|.* required from .*EvalFun<||; + s|>.$||; + s/fruit::impl::meta:://g; + s| >|>|g; + s|, |,|g' | + sed 's|[>]|,>|g; + s|[)]|,)|g' | + sed 's|[<]|<\n|g; + s|[(]|(\n|g; + s|[,]|,\n|g' | + grep -v "^,$"; + echo ')')) + considerMerging | + sed 's|,>|>|g; + s|,)|)|g' | + awk -F@ '/^[^()<>]*[>)][^()<>]*[(<][^()<>]*$/ {curIndent-=2; for (i=0; i<curIndent; i++) { printf(" "); } print; curIndent+=2; next;} + /[()<>].*[()<>]/ || !/[()<>]/ {for (i=0; i<curIndent; i++) { printf(" "); } print; next;} + /[(<]/ { for (i=0; i<curIndent; i++) { printf(" "); } print; curIndent+=2; next; } + /[)>]/ || /^[^()<>]*>/ { curIndent-=2; for (i=0; i<curIndent; i++) { printf(" "); } print; next; } + ' + else + echo "${line}" + fi +done | sed 's/fruit::impl::meta:://g;s/struct //g' | grep -v "required from .EvalIf<" diff --git a/extras/scripts/find_untested_types.sh b/extras/scripts/find_untested_types.sh new file mode 100755 index 0000000..11f31eb --- /dev/null +++ b/extras/scripts/find_untested_types.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +F=$(mktemp) + +find include/fruit/impl/ -type f | fgrep -v 'component_functors.defn.h' | xargs cat \ +| egrep "^(struct|class) [A-Za-z0-9_]* " \ +| sed -r 's/struct ([A-Za-z0-9_]*).*/\1/' \ +| egrep -v 'Error(Tag)?$' | fgrep -v Helper \ +| sort | uniq \ +>$F + +for t in `cat $F` +do + egrep -qR "[^a-zA-Z0-9]$t[^a-zA-Z0-9]" tests/ || echo $t +done diff --git a/extras/scripts/find_unused_types.sh b/extras/scripts/find_unused_types.sh new file mode 100755 index 0000000..b14c1d0 --- /dev/null +++ b/extras/scripts/find_unused_types.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script greps the source code trying to find unused types. + +TYPES=($(egrep -R '^( *class| *struct|using) ' include/ src/ | sed -e 's/.*\(class\|struct\|using\) \([a-zA-Z0-9_]*\).*/\2/' | sort | uniq)) + +echo Candidates: +for I in ${TYPES[@]} +do + N=$(fgrep -Rl "$I" include/ src/ | wc -l) + if [ $N == 1 ] + then + echo "$I" + grep -R "$I" include/ src/ + echo + fi +done + + +echo Strong candidates: +for I in ${TYPES[@]} +do + N=$(fgrep -R "$I" include/ src/ | egrep -v '.*: *(class|struct) '"$I" | wc -l) + if [ $N == 0 ] + then + echo "$I" + fgrep -R "$I" include/ src/ + echo + fi +done diff --git a/extras/scripts/parser.out b/extras/scripts/parser.out new file mode 100644 index 0000000..32b5fa7 --- /dev/null +++ b/extras/scripts/parser.out @@ -0,0 +1,897 @@ +Created by PLY version 3.7 (http://www.dabeaz.com/ply) + +Grammar + +Rule 0 S' -> balanced_string +Rule 1 comma_separated_balanced_string -> <empty> +Rule 2 comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string +Rule 3 optional_balanced_string -> <empty> +Rule 4 optional_balanced_string -> balanced_string +Rule 5 balanced_string -> ASTERISK optional_balanced_string +Rule 6 balanced_string -> AMPERSAND optional_balanced_string +Rule 7 balanced_string -> DOUBLE_COLON balanced_string +Rule 8 balanced_string -> IDENTIFIER optional_balanced_string +Rule 9 balanced_string -> LPAREN RPAREN optional_balanced_string +Rule 10 balanced_string -> LBRACKET RBRACKET optional_balanced_string +Rule 11 balanced_string -> LBRACE RBRACE optional_balanced_string +Rule 12 balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string +Rule 13 balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string +Rule 14 balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string +Rule 15 balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string +Rule 16 balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + +Terminals, with rules where they appear + +AMPERSAND : 6 +ASTERISK : 5 +COMMA : 2 +DOUBLE_COLON : 7 +GREATER_THAN : 12 16 +IDENTIFIER : 8 +LBRACE : 11 15 +LBRACKET : 10 14 +LESS_THAN : 12 16 +LPAREN : 9 13 +RBRACE : 11 15 +RBRACKET : 10 14 +RPAREN : 9 13 +error : + +Nonterminals, with rules where they appear + +balanced_string : 2 4 7 13 14 15 16 0 +comma_separated_balanced_string : 2 13 14 15 16 +optional_balanced_string : 5 6 8 9 10 11 12 13 14 15 16 + +Parsing method: LALR + +state 0 + + (0) S' -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 1 + +state 1 + + (0) S' -> balanced_string . + + + +state 2 + + (12) balanced_string -> LESS_THAN . GREATER_THAN optional_balanced_string + (16) balanced_string -> LESS_THAN . balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + GREATER_THAN shift and go to state 11 + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 10 + +state 3 + + (6) balanced_string -> AMPERSAND . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 13 + +state 4 + + (5) balanced_string -> ASTERISK . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 14 + +state 5 + + (10) balanced_string -> LBRACKET . RBRACKET optional_balanced_string + (14) balanced_string -> LBRACKET . balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + RBRACKET shift and go to state 16 + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 15 + +state 6 + + (8) balanced_string -> IDENTIFIER . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 17 + +state 7 + + (7) balanced_string -> DOUBLE_COLON . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 18 + +state 8 + + (9) balanced_string -> LPAREN . RPAREN optional_balanced_string + (13) balanced_string -> LPAREN . balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + RPAREN shift and go to state 20 + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 19 + +state 9 + + (11) balanced_string -> LBRACE . RBRACE optional_balanced_string + (15) balanced_string -> LBRACE . balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + RBRACE shift and go to state 22 + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 21 + +state 10 + + (16) balanced_string -> LESS_THAN balanced_string . comma_separated_balanced_string GREATER_THAN optional_balanced_string + (1) comma_separated_balanced_string -> . + (2) comma_separated_balanced_string -> . COMMA balanced_string comma_separated_balanced_string + + GREATER_THAN reduce using rule 1 (comma_separated_balanced_string -> .) + COMMA shift and go to state 23 + + comma_separated_balanced_string shift and go to state 24 + +state 11 + + (12) balanced_string -> LESS_THAN GREATER_THAN . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 25 + +state 12 + + (4) optional_balanced_string -> balanced_string . + + $end reduce using rule 4 (optional_balanced_string -> balanced_string .) + COMMA reduce using rule 4 (optional_balanced_string -> balanced_string .) + GREATER_THAN reduce using rule 4 (optional_balanced_string -> balanced_string .) + RBRACKET reduce using rule 4 (optional_balanced_string -> balanced_string .) + RPAREN reduce using rule 4 (optional_balanced_string -> balanced_string .) + RBRACE reduce using rule 4 (optional_balanced_string -> balanced_string .) + + +state 13 + + (6) balanced_string -> AMPERSAND optional_balanced_string . + + $end reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + COMMA reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + GREATER_THAN reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + RBRACKET reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + RPAREN reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + RBRACE reduce using rule 6 (balanced_string -> AMPERSAND optional_balanced_string .) + + +state 14 + + (5) balanced_string -> ASTERISK optional_balanced_string . + + $end reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + COMMA reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + GREATER_THAN reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + RBRACKET reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + RPAREN reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + RBRACE reduce using rule 5 (balanced_string -> ASTERISK optional_balanced_string .) + + +state 15 + + (14) balanced_string -> LBRACKET balanced_string . comma_separated_balanced_string RBRACKET optional_balanced_string + (1) comma_separated_balanced_string -> . + (2) comma_separated_balanced_string -> . COMMA balanced_string comma_separated_balanced_string + + RBRACKET reduce using rule 1 (comma_separated_balanced_string -> .) + COMMA shift and go to state 23 + + comma_separated_balanced_string shift and go to state 26 + +state 16 + + (10) balanced_string -> LBRACKET RBRACKET . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 27 + +state 17 + + (8) balanced_string -> IDENTIFIER optional_balanced_string . + + $end reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + COMMA reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + GREATER_THAN reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + RBRACKET reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + RPAREN reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + RBRACE reduce using rule 8 (balanced_string -> IDENTIFIER optional_balanced_string .) + + +state 18 + + (7) balanced_string -> DOUBLE_COLON balanced_string . + + $end reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + COMMA reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + GREATER_THAN reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + RBRACKET reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + RPAREN reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + RBRACE reduce using rule 7 (balanced_string -> DOUBLE_COLON balanced_string .) + + +state 19 + + (13) balanced_string -> LPAREN balanced_string . comma_separated_balanced_string RPAREN optional_balanced_string + (1) comma_separated_balanced_string -> . + (2) comma_separated_balanced_string -> . COMMA balanced_string comma_separated_balanced_string + + RPAREN reduce using rule 1 (comma_separated_balanced_string -> .) + COMMA shift and go to state 23 + + comma_separated_balanced_string shift and go to state 28 + +state 20 + + (9) balanced_string -> LPAREN RPAREN . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 29 + +state 21 + + (15) balanced_string -> LBRACE balanced_string . comma_separated_balanced_string RBRACE optional_balanced_string + (1) comma_separated_balanced_string -> . + (2) comma_separated_balanced_string -> . COMMA balanced_string comma_separated_balanced_string + + RBRACE reduce using rule 1 (comma_separated_balanced_string -> .) + COMMA shift and go to state 23 + + comma_separated_balanced_string shift and go to state 30 + +state 22 + + (11) balanced_string -> LBRACE RBRACE . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 31 + +state 23 + + (2) comma_separated_balanced_string -> COMMA . balanced_string comma_separated_balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 32 + +state 24 + + (16) balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string . GREATER_THAN optional_balanced_string + + GREATER_THAN shift and go to state 33 + + +state 25 + + (12) balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string . + + $end reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + COMMA reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + GREATER_THAN reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + RBRACKET reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + RPAREN reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + RBRACE reduce using rule 12 (balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string .) + + +state 26 + + (14) balanced_string -> LBRACKET balanced_string comma_separated_balanced_string . RBRACKET optional_balanced_string + + RBRACKET shift and go to state 34 + + +state 27 + + (10) balanced_string -> LBRACKET RBRACKET optional_balanced_string . + + $end reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + COMMA reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + GREATER_THAN reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + RBRACKET reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + RPAREN reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + RBRACE reduce using rule 10 (balanced_string -> LBRACKET RBRACKET optional_balanced_string .) + + +state 28 + + (13) balanced_string -> LPAREN balanced_string comma_separated_balanced_string . RPAREN optional_balanced_string + + RPAREN shift and go to state 35 + + +state 29 + + (9) balanced_string -> LPAREN RPAREN optional_balanced_string . + + $end reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + COMMA reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + GREATER_THAN reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + RBRACKET reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + RPAREN reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + RBRACE reduce using rule 9 (balanced_string -> LPAREN RPAREN optional_balanced_string .) + + +state 30 + + (15) balanced_string -> LBRACE balanced_string comma_separated_balanced_string . RBRACE optional_balanced_string + + RBRACE shift and go to state 36 + + +state 31 + + (11) balanced_string -> LBRACE RBRACE optional_balanced_string . + + $end reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + COMMA reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + GREATER_THAN reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + RBRACKET reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + RPAREN reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + RBRACE reduce using rule 11 (balanced_string -> LBRACE RBRACE optional_balanced_string .) + + +state 32 + + (2) comma_separated_balanced_string -> COMMA balanced_string . comma_separated_balanced_string + (1) comma_separated_balanced_string -> . + (2) comma_separated_balanced_string -> . COMMA balanced_string comma_separated_balanced_string + + GREATER_THAN reduce using rule 1 (comma_separated_balanced_string -> .) + RBRACKET reduce using rule 1 (comma_separated_balanced_string -> .) + RPAREN reduce using rule 1 (comma_separated_balanced_string -> .) + RBRACE reduce using rule 1 (comma_separated_balanced_string -> .) + COMMA shift and go to state 23 + + comma_separated_balanced_string shift and go to state 37 + +state 33 + + (16) balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 38 + +state 34 + + (14) balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 39 + +state 35 + + (13) balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 40 + +state 36 + + (15) balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE . optional_balanced_string + (3) optional_balanced_string -> . + (4) optional_balanced_string -> . balanced_string + (5) balanced_string -> . ASTERISK optional_balanced_string + (6) balanced_string -> . AMPERSAND optional_balanced_string + (7) balanced_string -> . DOUBLE_COLON balanced_string + (8) balanced_string -> . IDENTIFIER optional_balanced_string + (9) balanced_string -> . LPAREN RPAREN optional_balanced_string + (10) balanced_string -> . LBRACKET RBRACKET optional_balanced_string + (11) balanced_string -> . LBRACE RBRACE optional_balanced_string + (12) balanced_string -> . LESS_THAN GREATER_THAN optional_balanced_string + (13) balanced_string -> . LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string + (14) balanced_string -> . LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string + (15) balanced_string -> . LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string + (16) balanced_string -> . LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string + + $end reduce using rule 3 (optional_balanced_string -> .) + COMMA reduce using rule 3 (optional_balanced_string -> .) + GREATER_THAN reduce using rule 3 (optional_balanced_string -> .) + RBRACKET reduce using rule 3 (optional_balanced_string -> .) + RPAREN reduce using rule 3 (optional_balanced_string -> .) + RBRACE reduce using rule 3 (optional_balanced_string -> .) + ASTERISK shift and go to state 4 + AMPERSAND shift and go to state 3 + DOUBLE_COLON shift and go to state 7 + IDENTIFIER shift and go to state 6 + LPAREN shift and go to state 8 + LBRACKET shift and go to state 5 + LBRACE shift and go to state 9 + LESS_THAN shift and go to state 2 + + balanced_string shift and go to state 12 + optional_balanced_string shift and go to state 41 + +state 37 + + (2) comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string . + + RBRACE reduce using rule 2 (comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string .) + GREATER_THAN reduce using rule 2 (comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string .) + RBRACKET reduce using rule 2 (comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string .) + RPAREN reduce using rule 2 (comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string .) + + +state 38 + + (16) balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string . + + $end reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + COMMA reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + GREATER_THAN reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + RBRACKET reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + RPAREN reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + RBRACE reduce using rule 16 (balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string .) + + +state 39 + + (14) balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string . + + $end reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + COMMA reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + GREATER_THAN reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + RBRACKET reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + RPAREN reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + RBRACE reduce using rule 14 (balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string .) + + +state 40 + + (13) balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string . + + $end reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + COMMA reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + GREATER_THAN reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + RBRACKET reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + RPAREN reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + RBRACE reduce using rule 13 (balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string .) + + +state 41 + + (15) balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string . + + $end reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + COMMA reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + GREATER_THAN reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + RBRACKET reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + RPAREN reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + RBRACE reduce using rule 15 (balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string .) + diff --git a/extras/scripts/parsetab.py b/extras/scripts/parsetab.py new file mode 100644 index 0000000..119d710 --- /dev/null +++ b/extras/scripts/parsetab.py @@ -0,0 +1,45 @@ + +# parsetab.py +# This file is automatically generated. Do not edit. +_tabversion = '3.5' + +_lr_method = 'LALR' + +_lr_signature = 'A4908DFFF94F3402E0156DF82495775C' + +_lr_action_items = {'GREATER_THAN':([2,3,4,6,10,11,12,13,14,16,17,18,20,22,24,25,27,29,31,32,33,34,35,36,37,38,39,40,41,],[11,-3,-3,-3,-1,-3,-4,-6,-5,-3,-8,-7,-3,-3,33,-12,-10,-9,-11,-1,-3,-3,-3,-3,-2,-16,-14,-13,-15,]),'AMPERSAND':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,]),'RBRACE':([3,4,6,9,11,12,13,14,16,17,18,20,21,22,25,27,29,30,31,32,33,34,35,36,37,38,39,40,41,],[-3,-3,-3,22,-3,-4,-6,-5,-3,-8,-7,-3,-1,-3,-12,-10,-9,36,-11,-1,-3,-3,-3,-3,-2,-16,-14,-13,-15,]),'RPAREN':([3,4,6,8,11,12,13,14,16,17,18,19,20,22,25,27,28,29,31,32,33,34,35,36,37,38,39,40,41,],[-3,-3,-3,20,-3,-4,-6,-5,-3,-8,-7,-1,-3,-3,-12,-10,35,-9,-11,-1,-3,-3,-3,-3,-2,-16,-14,-13,-15,]),'ASTERISK':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,]),'$end':([1,3,4,6,11,12,13,14,16,17,18,20,22,25,27,29,31,33,34,35,36,38,39,40,41,],[0,-3,-3,-3,-3,-4,-6,-5,-3,-8,-7,-3,-3,-12,-10,-9,-11,-3,-3,-3,-3,-16,-14,-13,-15,]),'COMMA':([3,4,6,10,11,12,13,14,15,16,17,18,19,20,21,22,25,27,29,31,32,33,34,35,36,38,39,40,41,],[-3,-3,-3,23,-3,-4,-6,-5,23,-3,-8,-7,23,-3,23,-3,-12,-10,-9,-11,23,-3,-3,-3,-3,-16,-14,-13,-15,]),'LESS_THAN':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,]),'LBRACKET':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,]),'IDENTIFIER':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,]),'RBRACKET':([3,4,5,6,11,12,13,14,15,16,17,18,20,22,25,26,27,29,31,32,33,34,35,36,37,38,39,40,41,],[-3,-3,16,-3,-3,-4,-6,-5,-1,-3,-8,-7,-3,-3,-12,34,-10,-9,-11,-1,-3,-3,-3,-3,-2,-16,-14,-13,-15,]),'DOUBLE_COLON':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,]),'LPAREN':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,]),'LBRACE':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'balanced_string':([0,2,3,4,5,6,7,8,9,11,16,20,22,23,33,34,35,36,],[1,10,12,12,15,12,18,19,21,12,12,12,12,32,12,12,12,12,]),'comma_separated_balanced_string':([10,15,19,21,32,],[24,26,28,30,37,]),'optional_balanced_string':([3,4,6,11,16,20,22,33,34,35,36,],[13,14,17,25,27,29,31,38,39,40,41,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> balanced_string","S'",1,None,None,None), + ('comma_separated_balanced_string -> <empty>','comma_separated_balanced_string',0,'p_comma_separated_balanced_string_empty','analyze_template_instantiations_clang_diagnostics.py',156), + ('comma_separated_balanced_string -> COMMA balanced_string comma_separated_balanced_string','comma_separated_balanced_string',3,'p_comma_separated_balanced_string_not_empty','analyze_template_instantiations_clang_diagnostics.py',160), + ('optional_balanced_string -> <empty>','optional_balanced_string',0,'p_optional_balanced_string_empty','analyze_template_instantiations_clang_diagnostics.py',167), + ('optional_balanced_string -> balanced_string','optional_balanced_string',1,'p_optional_balanced_string_not_empty','analyze_template_instantiations_clang_diagnostics.py',171), + ('balanced_string -> ASTERISK optional_balanced_string','balanced_string',2,'p_balanced_string_terminal_symbol','analyze_template_instantiations_clang_diagnostics.py',200), + ('balanced_string -> AMPERSAND optional_balanced_string','balanced_string',2,'p_balanced_string_terminal_symbol','analyze_template_instantiations_clang_diagnostics.py',201), + ('balanced_string -> DOUBLE_COLON balanced_string','balanced_string',2,'p_balanced_string_terminal','analyze_template_instantiations_clang_diagnostics.py',206), + ('balanced_string -> IDENTIFIER optional_balanced_string','balanced_string',2,'p_balanced_string_terminal','analyze_template_instantiations_clang_diagnostics.py',207), + ('balanced_string -> LPAREN RPAREN optional_balanced_string','balanced_string',3,'p_balanced_string_with_balanced_token_no_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',283), + ('balanced_string -> LBRACKET RBRACKET optional_balanced_string','balanced_string',3,'p_balanced_string_with_balanced_token_no_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',284), + ('balanced_string -> LBRACE RBRACE optional_balanced_string','balanced_string',3,'p_balanced_string_with_balanced_token_no_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',285), + ('balanced_string -> LESS_THAN GREATER_THAN optional_balanced_string','balanced_string',3,'p_balanced_string_with_balanced_token_no_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',286), + ('balanced_string -> LPAREN balanced_string comma_separated_balanced_string RPAREN optional_balanced_string','balanced_string',5,'p_balanced_string_with_balanced_token_some_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',297), + ('balanced_string -> LBRACKET balanced_string comma_separated_balanced_string RBRACKET optional_balanced_string','balanced_string',5,'p_balanced_string_with_balanced_token_some_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',298), + ('balanced_string -> LBRACE balanced_string comma_separated_balanced_string RBRACE optional_balanced_string','balanced_string',5,'p_balanced_string_with_balanced_token_some_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',299), + ('balanced_string -> LESS_THAN balanced_string comma_separated_balanced_string GREATER_THAN optional_balanced_string','balanced_string',5,'p_balanced_string_with_balanced_token_some_comma_separated_elems','analyze_template_instantiations_clang_diagnostics.py',300), +] diff --git a/extras/scripts/postsubmit-helper.sh b/extras/scripts/postsubmit-helper.sh new file mode 100755 index 0000000..4f6fcaa --- /dev/null +++ b/extras/scripts/postsubmit-helper.sh @@ -0,0 +1,154 @@ +#!/bin/bash -x + +set -e + +# This only exists in OS X, but it doesn't cause issues in Linux (the dir doesn't exist, so it's +# ignored). +export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" + +case $COMPILER in +gcc-4.9) + export CC=gcc-4.9 + export CXX=g++-4.9 + ;; + +gcc-5) + export CC=gcc-5 + export CXX=g++-5 + ;; + +gcc-6) + export CC=gcc-6 + export CXX=g++-6 + ;; + +gcc-7) + export CC=gcc-7 + export CXX=g++-7 + ;; + +clang-3.5) + export CC=clang-3.5 + export CXX=clang++-3.5 + ;; + +clang-3.6) + export CC=clang-3.6 + export CXX=clang++-3.6 + ;; + +clang-3.7) + export CC=clang-3.7 + export CXX=clang++-3.7 + ;; + +clang-3.8) + export CC=clang-3.8 + export CXX=clang++-3.8 + ;; + +clang-3.9) + export CC=clang-3.9 + export CXX=clang++-3.9 + ;; + +clang-4.0) + case "$OS" in + linux) + export CC=clang-4.0 + export CXX=clang++-4.0 + ;; + osx) + export CC=/usr/local/opt/llvm/bin/clang + export CXX=/usr/local/opt/llvm/bin/clang++ + ;; + *) echo "Error: unexpected OS: $OS"; exit 1 ;; + esac + ;; + +clang-default) + export CC=clang + export CXX=clang++ + ;; + +bazel) + ;; + +*) + echo "Unrecognized value of COMPILER: $COMPILER" + exit 1 +esac + +run_make() { + make -j$N_JOBS +} + +if [[ "${COMPILER}" != "bazel" ]] +then + echo CXX version: $($CXX --version) + echo C++ Standard library location: $(echo '#include <vector>' | $CXX -x c++ -E - | grep 'vector\"' | awk '{print $3}' | sed 's@/vector@@;s@\"@@g' | head -n 1) + echo Normalized C++ Standard library location: $(readlink -f $(echo '#include <vector>' | $CXX -x c++ -E - | grep 'vector\"' | awk '{print $3}' | sed 's@/vector@@;s@\"@@g' | head -n 1)) + + case "$1" in + DebugPlain) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O2") ;; + DebugPlainNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O2" -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + DebugAsan) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O0 -fsanitize=address") ;; + DebugAsanNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O0 -fsanitize=address" -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + DebugAsanUbsan) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O0 -fsanitize=address,undefined") ;; + DebugAsanUbsanNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O0 -fsanitize=address,undefined" -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + DebugValgrind) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O2" -DRUN_TESTS_UNDER_VALGRIND=TRUE) ;; + DebugValgrindNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic -DFRUIT_DEBUG -DFRUIT_EXTRA_DEBUG -D_GLIBCXX_DEBUG -O2" -DRUN_TESTS_UNDER_VALGRIND=TRUE -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + ReleasePlain) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic") ;; + ReleasePlainNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic" -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + ReleaseValgrind) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic" -DRUN_TESTS_UNDER_VALGRIND=TRUE) ;; + ReleaseValgrindNoPch) CMAKE_ARGS=(-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="$STLARG -Werror -pedantic" -DRUN_TESTS_UNDER_VALGRIND=TRUE -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF) ;; + *) echo "Error: you need to specify one of the supported postsubmit modes (see postsubmit.sh)."; exit 1 ;; + esac + + SOURCES_PATH="$PWD" + + # This is not needed on Travis CI, but it's sometimes needed when running postsubmit.sh locally, to avoid "import + # file mismatch" errors. + rm -rf tests/__pycache__/ tests/*.pyc tests/*/__pycache__/ tests/*/*.pyc + + rm -rf build + mkdir build + cd build + cmake .. "${CMAKE_ARGS[@]}" + echo + echo "Content of CMakeFiles/CMakeError.log:" + if [ -f "CMakeFiles/CMakeError.log" ] + then + cat CMakeFiles/CMakeError.log + fi + echo + run_make + + cd examples + run_make + cd .. + + cd tests + run_make + + # We specify the path explicitly because old versions of pytest (e.g. the one in Ubuntu 14.04) + # don't support the testpaths setting in pytest.ini, so they will ignore it and they would + # otherwise run no tests. + py.test -n auto -r a "$SOURCES_PATH"/tests + cd .. + + make install +else + # COMPILER=bazel + + BAZEL_FLAGS=("--python_path=$(which python3)") + case "$1" in + DebugPlain) ;; + ReleasePlain) BAZEL_FLAGS+=("-c" "opt") ;; + *) echo "Error: you need to specify one of the supported postsubmit modes (see postsubmit.sh)."; exit 1 ;; + esac + + cd extras/bazel_root/third_party/fruit + bazel build "${BAZEL_FLAGS[@]}" :fruit examples/... tests/... + bazel test "${BAZEL_FLAGS[@]}" --test_output=errors tests/... +fi diff --git a/extras/scripts/postsubmit.bat b/extras/scripts/postsubmit.bat new file mode 100644 index 0000000..59fbb4c --- /dev/null +++ b/extras/scripts/postsubmit.bat @@ -0,0 +1,49 @@ + +echo on +setlocal EnableDelayedExpansion + +SET POWERSHELL_PATH= +FOR /F "delims=" %%F IN ('where powershell.exe') DO (SET POWERSHELL_PATH=!POWERSHELL_PATH!%%~dpF.;) + +set NEW_PATH=C:\Windows\system32;C:\Windows;%PYTHON3_PATH%;%PYTHON3_PATH%\Scripts;C:\Program Files (x86)\CMake\bin; +set PATH=%NEW_PATH% + +SET CL_PATH= +SET MSBUILD_PATH= + +if not "%VCVARSALL_DIR%" == "" ( + CALL "%VCVARSALL_DIR%\vcvarsall.bat" amd64 + echo on + FOR /F "delims=" %%F IN ('where cl.exe') DO (SET CL_PATH=!CL_PATH!%%~dpF.;) + FOR /F "delims=" %%F IN ('where msbuild.exe') DO (SET MSBUILD_PATH=!MSBUILD_PATH!%%~dpF.;) +) + +set NEW_PATH=%NEW_PATH%%CL_PATH%%MSBUILD_PATH%%POWERSHELL_PATH% + +if not "%MINGW_PATH%" == "" SET NEW_PATH=%NEW_PATH%%MINGW_PATH%; + +set PATH=%NEW_PATH% +setx PATH "%PATH%" + +mkdir C:\Fruit\build-%CONFIGURATION% +cd C:\Fruit\build-%CONFIGURATION% + +cmake.exe -G "%CMAKE_GENERATOR%" .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% %ADDITIONAL_CMAKE_ARGS% + +echo "Content of CMakeFiles\CMakeError.log:" +if exist "CMakeFiles\CMakeError.log" ( + type "CMakeFiles\CMakeError.log" +) + +IF "%CMAKE_GENERATOR%"=="MinGW Makefiles" ( + mingw32-make -j12 || exit /b 1 +) ELSE ( + type ALL_BUILD.vcxproj + msbuild ALL_BUILD.vcxproj || exit /b 1 +) + +pip3 install pytest +pip3 install pytest-xdist + +cd tests +py.test -r a -n 1 || exit /b 1 diff --git a/extras/scripts/postsubmit.sh b/extras/scripts/postsubmit.sh new file mode 100755 index 0000000..96c277a --- /dev/null +++ b/extras/scripts/postsubmit.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -e + +: ${N_JOBS:=2} + +if [ "$STL" != "" ] +then + STLARG="-stdlib=$STL" +fi + +case $OS in +linux) + docker rm -f fruit &>/dev/null || true + docker run -d -it --name fruit --privileged polettimarco/fruit-basesystem:ubuntu-$UBUNTU + docker exec fruit mkdir fruit + docker cp . fruit:/fruit + + docker exec fruit bash -c " + export COMPILER=$COMPILER; + export N_JOBS=$N_JOBS; + export STLARG=$STLARG; + export ASAN_OPTIONS=$ASAN_OPTIONS; + export OS=$OS; + cd fruit; extras/scripts/postsubmit-helper.sh $1" + exit $? + ;; + +osx) + export COMPILER + export N_JOBS + export STLARG + export ASAN_OPTIONS + export OS + extras/scripts/postsubmit-helper.sh "$@" + exit $? + ;; + +*) + echo "Unsupported OS: $OS" + exit 1 +esac diff --git a/extras/scripts/run_benchs.sh b/extras/scripts/run_benchs.sh new file mode 100755 index 0000000..78e2ad9 --- /dev/null +++ b/extras/scripts/run_benchs.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +set -e +set -o pipefail + +prefix_with() ( + local PREFIX=("$@") + while read line + do + echo "${PREFIX[@]}" "$line" + done +) + +progress() ( + local EXPECTED_LINES=$1 + local I=0 + echo "0/$EXPECTED_LINES (0%)" >&2 + while read line + do + echo "$line" + I="$[$I + 1]" + echo "$I/$EXPECTED_LINES ($[100 * $I / $EXPECTED_LINES]%)" >&2 + done +) + +print_stats() ( + local TOTAL=0 + local N=0 + for VALUE in "$@" + do + TOTAL="$(echo $TOTAL + $VALUE | bc -l)" + N="$[$N + 1]" + done + if [[ $N == 0 ]] + then + echo "N/A ()" + else + echo "$(echo $TOTAL / $N | bc -l) ($@)" + fi +) + +BASEDIR="$PWD" +COMPILERS=(g++ clang++) +# NUM_ITERATIONS = ITERATIONS_FACTOR/NUM_BINDINGS +ITERATIONS_FACTOR="$[400 * 1000 * 1000]" +# Must be multiples of 10 +NUM_BINDINGS_FOR_RUNTIME_TESTS=(100 1000) +# Must be multiples of 5 +NUM_BINDINGS_FOR_COMPILE_TESTS=(20 80 320) +NUM_LINES="$[${#COMPILERS[@]} * (${#NUM_BINDINGS_FOR_RUNTIME_TESTS[@]} * 3 + ${#NUM_BINDINGS_FOR_COMPILE_TESTS[@]})]" + +# All result lines are of the form: +# <compiler> <n> <test> <avg. time> (<time>...) +for compiler in ${COMPILERS[@]} +do + rm -rf build + mkdir build + ( + cd build + cmake .. -DCMAKE_CXX_COMPILER=$(which $compiler) -DCMAKE_BUILD_TYPE=Release &>/dev/null + ( + cd examples/benchmark + for N in ${NUM_BINDINGS_FOR_RUNTIME_TESTS[@]} + do + ( + NUM_ITERATIONS="$[$ITERATIONS_FACTOR / $N]" + sed -i "s/num_components_with_no_deps = .*/num_components_with_no_deps = $[$N / 10];/" $BASEDIR/examples/benchmark/generate_benchmark.cpp + sed -i "s/num_components_with_deps = .*/num_components_with_deps = $[9 * ($N / 10)];/" $BASEDIR/examples/benchmark/generate_benchmark.cpp + make benchmark &>/dev/null + SETUP_TIMES=() + REQUEST_TIMES=() + for i in $(seq 1 4) + do + RESULTS=($(echo $NUM_ITERATIONS | ./main $NUM_ITERATIONS | fgrep Total | awk '{print $5}')) + SETUP_TIMES+=("${RESULTS[0]}") + REQUEST_TIMES+=("${RESULTS[1]}") + done + print_stats "${SETUP_TIMES[@]}" | prefix_with "fruit_setup_time" + print_stats "${REQUEST_TIMES[@]}" | prefix_with "fruit_request_time" + sed -i "s/#define MULTIPLIER .*/#define MULTIPLIER $N/" $BASEDIR/examples/benchmark/new_delete_benchmark.cpp + make new_delete_benchmark &>/dev/null + NEW_DELETE_TIMES=() + for i in $(seq 1 4) + do + NEW_DELETE_TIMES+=($(echo $NUM_ITERATIONS | ./new_delete_benchmark | awk '{print $3}')) + done + print_stats "${NEW_DELETE_TIMES[@]}" | prefix_with "new_delete_time" + ) | prefix_with $N + done + ) + ( + cd examples/compile_time_benchmark + for N in ${NUM_BINDINGS_FOR_COMPILE_TESTS[@]} + do + ( + sed -i "s/#define MULTIPLIER .*/#define MULTIPLIER $[$N/5]/" $BASEDIR/examples/compile_time_benchmark/module.cpp + COMPILE_TIMES=() + for i in $(seq 1 4) + do + COMPILE_TIMES+=($(make compile_time_benchmark 2>&1 | fgrep real | awk '{print $2}' | tr -d s | sed 's/m/*60+/' | bc)) + done + print_stats "${COMPILE_TIMES[@]}" | prefix_with "fruit_compile_time" + ) | prefix_with "$[$N * 5]" + done + ) + ) | prefix_with $compiler +done | progress $NUM_LINES diff --git a/extras/scripts/test_coverage.sh b/extras/scripts/test_coverage.sh new file mode 100755 index 0000000..25004b5 --- /dev/null +++ b/extras/scripts/test_coverage.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +rm -rf coverage +mkdir coverage +cd coverage +mkdir binaries + +COMPILE_COMMAND=(g++ -O0 -W -Wall -Werror -std=c++11 -fprofile-arcs -fno-exceptions -ftest-coverage -I../include) +FRUIT_OBJS=() + + +for s in $(cd ../src; echo *.cpp) +do + "${COMPILE_COMMAND[@]}" -c ../src/"$s" -o "src-${s/.cpp/.o}" & + FRUIT_OBJS+=("src-${s/.cpp/.o}") +done + +wait || exit 1 + +for testdir in $(find ../tests -type d) +do + for t in $(cd $testdir; ls -1 *.cpp | fgrep -v include_test.cpp) + do + fgrep -q expect-compile-error $testdir/"$t" || \ + "${COMPILE_COMMAND[@]}" $testdir/"$t" ${FRUIT_OBJS[@]} -o binaries/${t/.cpp/} & + done +done + +wait || exit 1 + +for b in binaries/* +do + ./"$b" || true +done &>/dev/null + +PROJECT_DIR="$(cd ..; echo $PWD)" + +lcov --rc lcov_branch_coverage=1 -capture --directory . --output-file all-coverage.info +lcov --rc lcov_branch_coverage=1 --base-directory "$PROJECT_DIR" \ + --extract all-coverage.info "$PROJECT_DIR/src/*" \ + --extract all-coverage.info "$PROJECT_DIR/include/*" \ + --output-file coverage.info +genhtml --branch-coverage --demangle-cpp coverage.info --output-directory html + +xdg-open html/index.html diff --git a/extras/scripts/travis_ci_install_linux.sh b/extras/scripts/travis_ci_install_linux.sh new file mode 100755 index 0000000..708e5b9 --- /dev/null +++ b/extras/scripts/travis_ci_install_linux.sh @@ -0,0 +1,4 @@ +#!/bin/bash -x + +set -e + diff --git a/extras/scripts/travis_ci_install_osx.sh b/extras/scripts/travis_ci_install_osx.sh new file mode 100755 index 0000000..bf4524b --- /dev/null +++ b/extras/scripts/travis_ci_install_osx.sh @@ -0,0 +1,48 @@ +#!/bin/bash -x + +set -e + +install_brew_package() { + if brew list -1 | grep -q "^$1\$"; then + # Package is installed, upgrade if needed + time (brew outdated "$1" || brew upgrade "$@") + else + # Package not installed yet, install. + # If there are conflicts, try overwriting the files (these are in /usr/local anyway so it should be ok). + time (brew install "$@" || brew link --overwrite gcc49) + fi +} + +time brew update + +# For md5sum +install_brew_package md5sha1sum +# For `timeout' +install_brew_package coreutils + +if [[ "${INSTALL_VALGRIND}" == "1" ]] +then + install_brew_package valgrind +fi + +which cmake &>/dev/null || install_brew_package cmake + +case "${COMPILER}" in +gcc-4.9) install_brew_package gcc@4.9 ;; +gcc-5) install_brew_package gcc@5 ;; +gcc-6) install_brew_package gcc@6 ;; +clang-default) ;; +clang-3.7) install_brew_package llvm@3.7 --with-clang --with-libcxx;; +clang-3.8) install_brew_package llvm@3.8 --with-clang --with-libcxx;; +clang-3.9) install_brew_package llvm@3.9 --with-clang --with-libcxx;; +clang-4.0) install_brew_package llvm --with-clang --with-libcxx;; +*) echo "Compiler not supported: ${COMPILER}. See travis_ci_install_osx.sh"; exit 1 ;; +esac + +time brew upgrade python +time pip3 install pytest +time pip3 install pytest-xdist +time pip3 install sh + +# This adds python-installed executables to PATH (notably py.test). +export PATH="$(brew --prefix)/bin:$PATH" diff --git a/extras/scripts/travis_yml_generator.py b/extras/scripts/travis_yml_generator.py new file mode 100755 index 0000000..514c2c4 --- /dev/null +++ b/extras/scripts/travis_yml_generator.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import yaml + +# "smoke tests" are run before other build matrix rows. +build_matrix_smoke_test_rows = [] +build_matrix_rows = [] + +def determine_compiler_kind(compiler): + if compiler.startswith('gcc'): + return 'gcc' + elif compiler.startswith('clang'): + return 'clang' + else: + raise Exception('Unexpected compiler: %s' % compiler) + +def determine_tests(asan, ubsan, smoke_tests, use_precompiled_headers_in_tests, exclude_tests, + include_only_tests): + tests = [] + has_debug_build = False + tests += ['ReleasePlain'] + if asan: + has_debug_build = True + if ubsan: + tests += ['DebugAsanUbsan'] + else: + tests += ['DebugAsan'] + if ubsan and not asan: + raise Exception('Enabling UBSan but not ASan is not currently supported.') + if not has_debug_build: + tests += ['DebugPlain'] + for smoke_test in smoke_tests: + if smoke_test not in tests: + tests += [smoke_test] + excessive_excluded_tests = set(exclude_tests) - set(tests) + if excessive_excluded_tests: + raise Exception( + 'Some tests were excluded but were not going to run anyway: %s. ' + 'Tests to run (ignoring the possible NoPch prefix): %s' + % (excessive_excluded_tests, tests)) + if include_only_tests is not None: + if exclude_tests != []: + raise Exception('Using exclude_tests and include_only_tests together is not supported.') + tests = include_only_tests + else: + tests = [test for test in tests if test not in exclude_tests] + if not use_precompiled_headers_in_tests: + tests = [test + 'NoPch' for test in tests] + return tests + +def generate_export_statements_for_env(env): + return ' '.join(['export %s=\'%s\';' % (var_name, value) for (var_name, value) in sorted(env.items())]) + +def generate_env_string_for_env(env): + return ' '.join(['%s=%s' % (var_name, value) for (var_name, value) in sorted(env.items())]) + +def add_ubuntu_tests(ubuntu_version, compiler, stl=None, asan=True, ubsan=True, + use_precompiled_headers_in_tests=True, smoke_tests=[], exclude_tests=[], include_only_tests=None): + env = { + 'UBUNTU': ubuntu_version, + 'COMPILER': compiler + } + if stl is not None: + env['STL'] = stl + compiler_kind = determine_compiler_kind(compiler) + export_statements = 'export OS=linux; ' + generate_export_statements_for_env(env=env) + test_environment_template = {'os': 'linux', 'compiler': compiler_kind, + 'install': '%s extras/scripts/travis_ci_install_linux.sh' % export_statements} + tests = determine_tests(asan, ubsan, smoke_tests, + use_precompiled_headers_in_tests=use_precompiled_headers_in_tests, + exclude_tests=exclude_tests, + include_only_tests=include_only_tests) + for test in tests: + test_environment = test_environment_template.copy() + test_environment['script'] = '%s extras/scripts/postsubmit.sh %s' % (export_statements, test) + # The TEST variable has no effect on the test run, but allows to see the test name in the Travis CI dashboard. + test_environment['env'] = generate_env_string_for_env(env) + " TEST=%s" % test + if test in smoke_tests: + build_matrix_smoke_test_rows.append(test_environment) + else: + build_matrix_rows.append(test_environment) + + +def add_osx_tests(compiler, xcode_version=None, stl=None, asan=True, ubsan=True, + use_precompiled_headers_in_tests=True, smoke_tests=[], exclude_tests=[], include_only_tests=None): + env = {'COMPILER': compiler} + if stl is not None: + env['STL'] = stl + compiler_kind = determine_compiler_kind(compiler) + export_statements = 'export OS=osx; ' + generate_export_statements_for_env(env=env) + test_environment_template = {'os': 'osx', 'compiler': compiler_kind, + 'install': '%s extras/scripts/travis_ci_install_osx.sh' % export_statements} + if xcode_version is not None: + test_environment_template['osx_image'] = 'xcode%s' % xcode_version + + tests = determine_tests(asan, ubsan, smoke_tests, + use_precompiled_headers_in_tests=use_precompiled_headers_in_tests, + exclude_tests=exclude_tests, include_only_tests=include_only_tests) + for test in tests: + test_environment = test_environment_template.copy() + test_environment['script'] = '%s extras/scripts/postsubmit.sh %s' % (export_statements, test) + # The TEST variable has no effect on the test run, but allows to see the test name in the Travis CI dashboard. + test_environment['env'] = generate_env_string_for_env(env) + " TEST=%s" % test + if test in smoke_tests: + build_matrix_smoke_test_rows.append(test_environment) + else: + build_matrix_rows.append(test_environment) + + +def add_bazel_tests(ubuntu_version, smoke_tests=[]): + env = { + 'UBUNTU': ubuntu_version, + 'COMPILER': 'bazel', + } + test = 'DebugPlain' + export_statements = 'export OS=linux; ' + generate_export_statements_for_env(env=env) + test_environment = {'os': 'linux', + 'compiler': 'gcc', + 'env': generate_env_string_for_env(env), + 'install': '%s extras/scripts/travis_ci_install_linux.sh' % export_statements, + 'script': '%s extras/scripts/postsubmit.sh %s' % (export_statements, test)} + if test in smoke_tests: + build_matrix_smoke_test_rows.append(test_environment) + else: + build_matrix_rows.append(test_environment) + +# TODO: re-enable ASan/UBSan once they work in Travis CI. ATM (as of 18 November 2017) they fail due to https://github.com/google/sanitizers/issues/837 +add_ubuntu_tests(ubuntu_version='17.10', compiler='gcc-7', asan=False, ubsan=False, smoke_tests=['DebugPlain', 'ReleasePlain']) +add_ubuntu_tests(ubuntu_version='17.10', compiler='clang-4.0', stl='libstdc++', smoke_tests=['DebugPlain', 'DebugAsanUbsan', 'ReleasePlain']) + +add_bazel_tests(ubuntu_version='16.04', smoke_tests=['DebugPlain']) + +# ASan/UBSan are disabled for all these, the analysis on later versions is better anyway. +# Also, in some combinations they wouldn't work. +add_ubuntu_tests(ubuntu_version='14.04', compiler='gcc-5', asan=False, ubsan=False) +add_ubuntu_tests(ubuntu_version='14.04', compiler='clang-3.5', stl='libstdc++', asan=False, ubsan=False) +add_ubuntu_tests(ubuntu_version='14.04', compiler='clang-3.9', stl='libstdc++', asan=False, ubsan=False) +add_ubuntu_tests(ubuntu_version='14.04', compiler='clang-3.5', stl='libc++', asan=False, ubsan=False) +add_ubuntu_tests(ubuntu_version='14.04', compiler='clang-3.9', stl='libc++', asan=False, ubsan=False) + +# Asan/Ubsan are disabled because it generates lots of warnings like: +# warning: direct access in [...] to global weak symbol guard variable for [...] means the weak symbol cannot be +# overridden at runtime. This was likely caused by different translation units being compiled with different +# visibility settings. +# and the build eventually fails or times out. +add_osx_tests(compiler='gcc-5', xcode_version='8', asan=False, ubsan=False) +add_osx_tests(compiler='gcc-6', xcode_version='8', asan=False, ubsan=False, smoke_tests=['DebugPlain']) +# ASan/UBSan are disabled because it would hit errors like: +# ld: file not found: [...]/libclang_rt.asan_osx_dynamic.dylib +# ld: file not found: [...]/libclang_rt.ubsan_osx.a +# Not sure if that's a limitation of Clang on OS X or just of the brew-provided binaries. +add_osx_tests(compiler='clang-3.7', stl='libc++', asan=False, ubsan=False) +add_osx_tests(compiler='clang-4.0', xcode_version='8', stl='libc++', smoke_tests=['DebugPlain']) + +# UBSan is disabled because AppleClang does not support -fsanitize=undefined. +add_osx_tests(compiler='clang-default', xcode_version='7.3', stl='libc++', ubsan=False) +# UBSan is disabled because AppleClang does not support -fsanitize=undefined. +add_osx_tests(compiler='clang-default', xcode_version='8.2', stl='libc++', ubsan=False, smoke_tests=['DebugPlain']) + +# ** Disabled combinations ** +# +# These fail with "'type_traits' file not found" (the <type_traits> header is missing). +# +# add_osx_tests('gcc-default', stl='libstdc++') +# add_osx_tests('clang-default', stl='libstdc++') +# add_osx_tests('clang-3.5', stl='libstdc++') +# add_osx_tests('clang-3.6', stl='libstdc++') +# +# +# The compiler complains that the 2-argument constructor of std::pair is ambiguous, even after +# adding explicit casts to the exact types of the expected overload. +# +# add_osx_tests('clang-default', stl='libc++') +# +# +# This triggers an assert error in the compiler, with the message: +# "expected to get called on an inlined function!" [...] function isMSExternInline, file Decl.cpp, line 2647. +# +# add_osx_tests('clang-3.5', stl='libc++', asan=False, ubsan=False) +# +# +# This fails with this error: +# /usr/include/c++/v1/string:1938:44: error: 'basic_string<_CharT, _Traits, _Allocator>' is missing +# exception specification 'noexcept(is_nothrow_copy_constructible<allocator_type>::value)' +# TODO: Try again every once in a while (to re-enable these once the bug in libc++ is fixed). +# +# add_ubuntu_tests(ubuntu_version='16.04', compiler='clang-3.8', stl='libc++', asan=False, ubsan=False) +# + + +yaml_file = { + 'sudo': 'required', + 'dist': 'trusty', + 'services' : ['docker'], + 'language': 'cpp', + 'branches': { + 'only': ['master'], + }, + 'matrix': { + 'fast_finish': True, + 'include': build_matrix_smoke_test_rows + build_matrix_rows, + }, +} + +class CustomDumper(yaml.SafeDumper): + def ignore_aliases(self, _data): + return True + +print('#') +print('# This file was auto-generated from extras/scripts/travis_yml_generator.py, DO NOT EDIT') +print('#') +print(yaml.dump(yaml_file, default_flow_style=False, Dumper=CustomDumper)) diff --git a/include/fruit/component.h b/include/fruit/component.h new file mode 100644 index 0000000..5fbfdc6 --- /dev/null +++ b/include/fruit/component.h @@ -0,0 +1,1050 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_H +#define FRUIT_COMPONENT_H + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/bindings.h> +#include <fruit/impl/component_functors.defn.h> +#include <fruit/impl/component_storage/component_storage.h> +#include <fruit/impl/component_storage/partial_component_storage.h> +#include <fruit/impl/meta/component.h> + +namespace fruit { + +/** + * A component (aka module) represents a collection of bindings. + * You can think of a Component object as a UML component. + * + * The parameters can be of the form <P...> or <Required<R...>, P...> where: + * * R... are the required types (types required to inject some types in P... but that are not provided by this + * Component), if any + * * P... are the types provided by this Component. + * No type can appear twice, not even once in R and once in P. + * + * See PartialComponent below for how to construct a Component. + */ +template <typename... Params> +class Component { +public: + Component(Component&&) = default; + + Component& operator=(Component&&) = delete; + Component& operator=(const Component&) = delete; + + /** + * Converts a PartialComponent to an arbitrary Component, auto-injecting the missing types (if + * any). + * This is usually called implicitly when returning a component from a function. See PartialComponent for an example. + */ + template <typename... Bindings> + Component(PartialComponent<Bindings...>&& component); + +private: + // Do not use. Use fruit::createComponent() instead. + Component() = default; + + template <typename... Types> + friend class Component; + + template <typename... Bindings> + friend class PartialComponent; + + template <typename... OtherParams> + friend class NormalizedComponent; + + template <typename... OtherParams> + friend class Injector; + + template <typename... Bindings> + friend class fruit::impl::PartialComponentStorage; + + template <typename Component, typename... Args> + friend class fruit::impl::LazyComponentImpl; + + friend struct fruit::impl::ComponentStorageEntry::LazyComponentWithNoArgs; + + template <typename Component, typename... Args> + friend class fruit::impl::ComponentInterfaceImpl; + + fruit::impl::ComponentStorage storage; + + using Comp = fruit::impl::meta::Eval<fruit::impl::meta::ConstructComponentImpl(fruit::impl::meta::Type<Params>...)>; + + using Check1 = typename fruit::impl::meta::CheckIfError<Comp>::type; + // Force instantiation of Check1. + static_assert(true || sizeof(Check1), ""); +}; + +/** + * Constructs an empty component. + * + * Example usage: + * + * fruit::Component<Foo> getFooComponent() { + * return fruit::createComponent() + * .install(getComponent1) + * .install(getComponent2) + * .bind<Foo, FooImpl>(); + * } + * + * Since types are auto-injected when needed, just converting this to the desired component can suffice in some cases, + * e.g.: + * + * fruit::Component<Foo> getFooComponent() { + * return fruit::createComponent(); + * } + * + * That works if Foo has an Inject typedef or a constructor wrapped in INJECT. + */ +PartialComponent<> createComponent(); + +/** + * A partially constructed component. + * + * Client code should never write `PartialComponent'; always start the construction of a component with + * fruit::createComponent(), and end it by casting the PartialComponent to the desired Component (often done implicitly + * by returning a PartialComponent from a function that has Component<...> as the return type). + * + * The template parameter is used to propagate information about bound types, it is purely an implementation detail; + * users of the Fruit library can pretend that this class is not templated, in no case a specific template parameter is + * required. All methods of this class return a PartialComponent, which allows to chain method invocations without + * declaring a variable of type PartialComponent. + * + * Example usage: + * + * fruit::Component<Foo> getFooComponent() { + * return fruit::createComponent() + * .install(getComponent1) + * .install(getComponent2) + * .bind<Foo, FooImpl>(); + * } + * + * Note that no variable of type PartialComponent has been declared; this class should only be used for temporary + * values. + */ +template <typename... Bindings> +class PartialComponent { +public: + PartialComponent& operator=(PartialComponent&&) = delete; + PartialComponent& operator=(const PartialComponent&) = delete; + + /** + * This tells Fruit that "the implementation of I is C". + * I must be a base class of C, and it's typically (but not necessarily) an abstract class. + * C is typically a concrete class, but it doesn't have to be: for example, if A inherits from B and B inherits from C + * you can specify bind<C, B>() and bind<B, A>(). + * + * The most common use of this is to bind the type I to the type C, for example: + * + * class MyInterface { + * public: + * virtual void foo() = 0; + * }; + * + * class MyImplementation { + * public: + * INJECT(MyImplementation()) {} + * + * void foo() { + * ... + * } + * }; + * + * fruit::Component<MyInterface> getMyInterfaceComponent() { + * return fruit::createComponent() + * .bind<MyInterface, MyImplementation>(); + * } + * + * The implementation class can be bound in any way, it doesn't need to be bound using constructor injection as above + * (although that's a very common use case). + * + * You can bind an interface to a type bound using a constant binding (see the bindInstance method that takes a const& + * for more details), however the interface will then also be considered bound with a constant binding, and you must + * declare that in the returned Component's type. For example: + * + * fruit::Component<const MyInterface> getMyInterfaceComponent() { + * static const MyImplementation my_implementation = MyImplementation(); + * return fruit::createComponent() + * .bindInstance(my_implementation) + * .bind<MyInterface, MyImplementation>(); + * } + * + * In addition to binding the type I to the type C, a `bind()` can also be used to bind a + * std::function<std::unique_ptr<I>(Args...)> to a std::function<std::unique_ptr<C>(Args...)> or a + * std::function<C(Args...)>. For example: + * + * fruit::Component<std::function<std::unique_ptr<MyInterface>(int)>> getIFactoryComponent() { + * static const std::function<MyImplementation(int)> cFactory = [](int n) { return MyImplementation(n); }; + * return fruit::createComponent() + * .bind<MyInterface, MyImplementation>() + * .bindInstance(cFactory); + * } + * + * Or alternatively you can do the same using constructor injection: + * + * class MyImplementation { + * public: + * INJECT(MyImplementation(ASSISTED(int) n)) + * : n(n) { + * } + * ... + * }; + * + * fruit::Component<std::function<std::unique_ptr<MyInterface>(int)>> getIFactoryComponent() { + * return fruit::createComponent() + * .bind<MyInterface, MyImplementation>(); + * } + * + * Fruit determines the actual binding(s) to generate based on the types you declare as provided in the returned + * Component<> type (e.g. in the last example std::function<std::unique_ptr<MyInterface>(int)> is declared as provided + * so Fruit will generate a factory binding instead of a normal MyInterface->MyImplementation binding. + * + * Fruit can also detect types that are needed for constructor/provider/factory bindings, and it will then use that + * information to determine the desired binding. For example: + * + * class MyImplementation { + * public: + * INJECT(MyImplementation(ASSISTED(int) n)) + * : n(n) { + * } + * ... + * }; + * + * class Foo { + * private: + * std::function<std::unique_ptr<MyInterface>(int)> factory; + * public: + * INJECT(Foo(std::function<std::unique_ptr<MyInterface>(int)> factory)) + * : factory(factory) { + * } + * }; + * + * fruit::Component<Foo> getIFactoryComponent() { + * return fruit::createComponent() + * // We want to bind Foo, which requires std::function<std::unique_ptr<MyInterface>(int)>. + * // So std::function<std::unique_ptr<MyInterface>(int)> will be bound to + * // std::function<std::unique_ptr<MyImplementation>(int)>, and that will be bound using constructor injection. + * .bind<MyInterface, MyImplementation>(); + * } + * + * All these cases support annotated injection, just wrap I and/or C in fruit::Annotated<> if desired. For example: + * + * struct MyAnnotation{}; + * + * fruit::Component<fruit::Annotated<MyAnnotation, MyInterface>> getMyInterfaceComponent() { + * return fruit::createComponent() + * .bind<fruit::Annotated<MyAnnotation, MyInterface>, MyImplementation>(); + * } + */ + template <typename I, typename C> + PartialComponent<fruit::impl::Bind<I, C>, Bindings...> bind(); + + /** + * Registers Signature as the constructor signature to use to inject a type. + * + * Example usage: + * + * fruit::createComponent() + * .registerConstructor<Foo(Bar*,Baz*)>() // Registers the constructor Foo::Foo(Bar*,Baz*) + * + * It's usually more convenient to use an INJECT macro or Inject typedef instead, e.g.: + * + * class Foo { + * public: + * // This also declares the constructor + * INJECT(Foo(Bar* bar, Baz* baz)); + * ... + * }; + * + * or (equivalent): + * + * class Foo { + * public: + * using Inject = Foo(Bar*, Baz*); + * Foo(Bar* bar, Baz* baz); + * ... + * }; + * + * Use registerConstructor() when you want to inject the class C in different ways in different components (just make + * sure those don't end up in the same injector, or use annotated injection to prevent the bindings from clashing), + * or when C is a third-party class that can't be modified. + * + * This supports annotated injection, just wrap the desired types (return type and/or argument types of the signature) + * with fruit::Annotated<> if desired. For example: + * + * struct Annotation1 {}; + * struct Annotation2 {}; + * + * struct Foo { + * Foo(Bar* bar) {...} + * }; + * + * fruit::Component<fruit::Annotated<Annotation1, Bar>> getBarComponent() {...} + * + * fruit::Component<fruit::Annotated<Annotation2, Foo>> getFooComponent() { + * return fruit::createComponent() + * .install(getBarComponent) + * .registerConstructor<fruit::Annotated<Annotation2, Foo>(fruit::Annotated<Annotation1, Bar*>)>(); + * } + * + * This does *not* support assisted injection, for that you should use registerFactory() instead. + * + * The allowed argument types in the signature are, for any class (or fundamental) type C: + * + * C + * C* + * C& + * const C* + * const C& + * shared_ptr<C> + * Provider<C> + * Provider<const C> + * Annotated<Annotation, C> (for any type `Annotation') + * Annotated<Annotation, C*> (for any type `Annotation') + * Annotated<Annotation, C&> (for any type `Annotation') + * Annotated<Annotation, const C*> (for any type `Annotation') + * Annotated<Annotation, const C&> (for any type `Annotation') + * Annotated<Annotation, shared_ptr<C>> (for any type `Annotation') + * Annotated<Annotation, Provider<C>> (for any type `Annotation') + * Annotated<Annotation, Provider<const C>> (for any type `Annotation') + */ + template <typename Signature> + PartialComponent<fruit::impl::RegisterConstructor<Signature>, Bindings...> registerConstructor(); + + /** + * Use this method to bind the type C to a specific instance. + * The caller must ensure that the provided reference is valid for the entire lifetime of the component and of any + * components or injectors that install this component; the caller must also ensure that the object is destroyed after + * the last components/injectors using it are destroyed. + * + * Example usage: + * + * Component<Request> getRequestComponent(Request* request) { + * return fruit::createComponent() + * .bindInstance(*request); + * } + * + * NormalizedComponent<...> normalizedComponent = ...; + * Request request; + * Injector<...> injector(normalizedComponent, + * getRequestComponent, + * request)); + * + * This should be used sparingly (you should let Fruit handle the object lifetime when possible), but in some cases it + * is necessary; for example, if a web server creates an injector to handle each request, this method can be used to + * inject the request itself as in the example above (see the Server page in the Fruit tutorial for more details). + * + * It's also possible to bind constants, see the documentation of the bindInstance() method taking a const& for + * details. + */ + template <typename C> + PartialComponent<fruit::impl::BindInstance<C, C>, Bindings...> bindInstance(C& instance); + + /** + * Similar to the previous, but binds a const&. Note that the reference must still outlive the component/injector + * as in the non-const case. + * When using this method, you must declare that the type is constant in the Component type. For example: + * + * Component<const MyExpensiveClass> getMyExpensiveClassComponent() { + * static const MyExpensiveClass my_expensive_class = createMyExpensiveClass(); + * return fruit::createComponent() + * .bindInstance(my_expensive_class); + * } + * + * Constant bindings can be used as other bindings, except that you can only inject the constant type (e.g. as a + * constructor parameter) as: + * + * C + * const C* + * const C& + * Provider<const C> + * Annotated<Annotation, C> (for any type `Annotation') + * Annotated<Annotation, const C*> (for any type `Annotation') + * Annotated<Annotation, const C&> (for any type `Annotation') + * Annotated<Annotation, Provider<const C>> (for any type `Annotation') + * + * While you can't inject it as: + * + * C* + * C& + * shared_ptr<C> + * Provider<C> + * Annotated<Annotation, C*> (for any type `Annotation') + * Annotated<Annotation, C&> (for any type `Annotation') + * Annotated<Annotation, shared_ptr<C>> (for any type `Annotation') + * Annotated<Annotation, Provider<C>> (for any type `Annotation') + */ + template <typename C> + PartialComponent<fruit::impl::BindConstInstance<C, C>, Bindings...> bindInstance(const C& instance); + + /** + * This is deleted to catch cases where the instance would likely be destroyed before the component/injectors. + */ + template <typename C> + PartialComponent<fruit::impl::BindConstInstance<C, C>, Bindings...> bindInstance(C&&) = delete; + + /** + * Similar to the first version of bindInstance(), but allows to specify an annotated type that + * will be bound to the specified value. + * For example, to bind an instance to the type fruit::Annotated<Hostname, std::string>, you can use: + * + * fruit::Component<fruit::Annotated<Hostname, std::string>> getHostnameComponent(std::string* hostname) { + * fruit::createComponent() + * .bindInstance<fruit::Annotated<Hostname, std::string>>(*hostname); + * } + */ + template <typename AnnotatedType, typename C> + PartialComponent<fruit::impl::BindInstance<AnnotatedType, C>, Bindings...> bindInstance(C& instance); + + /** + * Similar to the previous, but binds a const&. Example usage: + * + * fruit::Component<fruit::Annotated<Hostname, const std::string>> getHostnameComponent() { + * static const std::string hostname = determineHostname(); + * fruit::createComponent() + * .bindInstance<fruit::Annotated<Hostname, std::string>>(hostname); + * } + * + * See the documentation for the bindInstance() overload that takes a non-annotated const& for more details. + */ + template <typename AnnotatedType, typename C> + PartialComponent<fruit::impl::BindConstInstance<AnnotatedType, C>, Bindings...> bindInstance(const C& instance); + + /** + * This is deleted to catch cases where the instance would likely be destroyed before the component/injectors. + */ + template <typename AnnotatedType, typename C> + PartialComponent<fruit::impl::BindConstInstance<AnnotatedType, C>, Bindings...> bindInstance(C&& instance); + + /** + * Registers `provider' as a provider of C, where provider is a lambda with no captures returning either C or C* + * (prefer returning a C by value instead of allocating a C using `new C', to avoid the allocation). + * + * When injecting a C, the arguments of the provider will be injected and the provider will then be called to create + * the C instance, that will then be stored in the injector. + * + * If `provider' returns a pointer, it must be non-null; otherwise the program will abort. + * + * Example: + * + * fruit::Component<Foo> getFooComponent() { + * return fruit::createComponent() + * .install(getBarComponent) + * .install(getBazComponent) + * .registerProvider([](Bar* bar, Baz* baz) { + * Foo foo(bar, baz); + * foo.initialize(); + * return foo; + * }); + * } + * + * As in the previous example, it's not necessary to specify the type parameter, it will be inferred by the compiler. + * + * registerProvider() can't be called with a plain function, but you can write a lambda that wraps the function to + * achieve the same result. + * + * Registering stateful functors (including lambdas with captures) is NOT supported. + * However, you can write something like: + * + * struct Functor { + * Functor(int n) {...} + * MyClass operator()(Foo* foo) const {...} + * }; + * + * Component<MyClass> getMyClassComponent() { + * static const Functor aFunctor(42); + * return fruit::createComponent() + * .install(getFooComponent) + * .bindInstance(aFunctor) + * .registerProvider([](const Functor& functor, Foo* foo) { return functor(foo); }); + * } + */ + template <typename Lambda> + PartialComponent<fruit::impl::RegisterProvider<Lambda>, Bindings...> registerProvider(Lambda lambda); + + /** + * Similar to the previous version of registerProvider(), but allows to specify an annotated type + * for the provider. This allows to inject annotated types in the parameters and/or bind the + * provider to an annotated type. For example: + * + * struct MyAnnotation1 {}; + * struct MyAnnotation2 {}; + * + * Component<fruit::Annotated<Annotation1, Bar>> getBarComponent() {...} + * Component<Baz> getBazComponent() {...} + * + * fruit::Component<fruit::Annotated<Annotation2, Foo>> getFooComponent() { + * return fruit::createComponent() + * .install(getBarComponent) + * .install(getBazComponent) + * .registerProvider< + * fruit::Annotated<Annotation2, Foo>( + * fruit::Annotated<Annotation1, Bar*>, + * Baz*) + * >([](Bar* bar, Baz* baz) { + * Foo foo(bar, baz); + * foo.initialize(); + * return foo; + * }); + * } + */ + template <typename AnnotatedSignature, typename Lambda> + PartialComponent<fruit::impl::RegisterProvider<AnnotatedSignature, Lambda>, Bindings...> + registerProvider(Lambda lambda); + + /** + * Similar to bind<I, C>(), but adds a multibinding instead. + * + * Multibindings are independent from bindings; creating a binding with bind doesn't count as a multibinding, and + * adding a multibinding doesn't allow to inject the type (it only allows to retrieve multibindings through the + * getMultibindings method of the injector). + * + * Unlike bindings, where adding a the same binding twice is allowed (and ignored), adding the same multibinding + * multiple times will result in the creation of multiple "equivalent" instances, that will all be returned by + * getMultibindings(). + * + * Another difference compared with normal bindings is that this can't be used to bind a + * std::function<std::unique_ptr<I>(Args...)> to a std::function<std::unique_ptr<C>(Args...)> or a + * std::function<C(Args...)>. + * + * As bind(), this supports annotated injection, just wrap I and/or C in fruit::Annotated<> if desired. See the + * documentation of bind() for more details. + */ + template <typename I, typename C> + PartialComponent<fruit::impl::AddMultibinding<I, C>, Bindings...> addMultibinding(); + + /** + * Similar to bindInstance(), but adds a multibinding instead. + * + * Multibindings are independent from bindings; creating a binding with bindInstance doesn't count as a + * multibinding, and adding a multibinding doesn't allow to inject the type (it only allows to retrieve + * multibindings through the getMultibindings method of the injector). + * + * Unlike bindings, where adding a the same binding twice is allowed (and ignored), adding several multibindings for + * the same instance will result in duplicated values in the result of getMultibindings. + * + * Another difference compared to bindInstance() is that you can't use this to bind a const& (note that there is no + * overload of this method that takes a const&). + * + * This method adds a multibinding for C. If the object implements an interface I and you want to add a multibinding + * for that interface instead, you must cast the object to I& before passing it to this method. + * + * Note that this takes the instance by reference, not by value; it must remain valid for the entire lifetime of this + * component and of any injectors created from this component. + * + * Example use: + * + * class MyClass { + * ... + * }; + * + * fruit::Component<> getMyComponent() { + * static MyClass x = MyClass(...); + * static MyClass y = MyClass(...); + * return fruit::createComponent() + * .addInstanceMultibinding(x) + * .addInstanceMultibinding(y); + * } + * + * fruit::Injector<> injector(getMyComponent); + * // This vector contains {&x, &y}. + * const std::vector<MyClass*>& objects = injector.getMultibindings<MyClass>(); + */ + template <typename C> + PartialComponent<fruit::impl::AddInstanceMultibinding<C>, Bindings...> addInstanceMultibinding(C& instance); + + /** + * Similar to the previous version of addInstanceMultibinding(), but allows to specify an + * annotated type. + * Example use: + * + * struct MyAnnotation {}; + * + * class MyClass { + * ... + * }; + * + * fruit::Component<> getMyComponent() { + * static MyClass x = MyClass(...); + * static MyClass y = MyClass(...); + * return fruit::createComponent() + * .addInstanceMultibinding<fruit::Annotated<MyAnnotation, MyClass>>(x) + * .addInstanceMultibinding<fruit::Annotated<MyAnnotation, MyClass>>(y); + * } + * + * fruit::Injector<> injector(getMyComponent); + * // This vector contains {&x, &y}. + * const std::vector<MyClass*>& objects = injector.getMultibindings<fruit::Annotated<MyAnnotation, MyClass>>(); + */ + template <typename AnnotatedC, typename C> + PartialComponent<fruit::impl::AddInstanceMultibinding<AnnotatedC>, Bindings...> addInstanceMultibinding(C& instance); + + /** + * Equivalent to calling addInstanceMultibinding() for each elements of `instances'. + * See the documentation of addInstanceMultibinding() for more details. + * + * Note that this takes the vector by reference, not by value; the vector (and its elements) must remain valid for the + * entire lifetime of this component and of any injectors created from this component. + * + * Example use: + * + * class MyClass { + * ... + * }; + * + * fruit::Component<> getMyComponent() { + * static MyClass x = MyClass(...); + * static std::vector<MyClass> other_objects{MyClass(...), MyClass(...)}; + * return fruit::createComponent() + * .addInstanceMultibinding(x) + * .addInstanceMultibindings(other_objects); + * } + * + * fruit::Injector<> injector(getMyComponent); + * // This vector contains {&x, &(other_objects[0]), &(other_objects[1])}. + * const std::vector<MyClass*>& objects = injector.getMultibindings<MyClass>(); + */ + template <typename C> + PartialComponent<fruit::impl::AddInstanceVectorMultibindings<C>, Bindings...> + addInstanceMultibindings(std::vector<C>& instances); + + /** + * Similar to the previous version of addInstanceMultibindings(), but it allows to specify an annotated type. + * + * Example use: + * + * class MyClass { + * ... + * }; + * + * fruit::Component<> getMyComponent() { + * static MyClass x = MyClass(...); + * static std::vector<MyClass> other_objects{MyClass(...), MyClass(...)}; + * return fruit::createComponent() + * .addInstanceMultibinding<fruit::Annotated<MyAnnotation, MyClass>>(x) + * .addInstanceMultibindings<fruit::Annotated<MyAnnotation, MyClass>>(other_objects); + * } + * + * fruit::Injector<> injector(getMyComponent); + * // This vector contains {&x, &(other_objects[0]), &(other_objects[1])}. + * const std::vector<MyClass*>& objects = injector.getMultibindings<fruit::Annotated<MyAnnotation, MyClass>>(); + */ + template <typename AnnotatedC, typename C> + PartialComponent<fruit::impl::AddInstanceVectorMultibindings<AnnotatedC>, Bindings...> + addInstanceMultibindings(std::vector<C>& instances); + + /** + * Similar to registerProvider, but adds a multibinding instead. + * + * Multibindings are independent from bindings; creating a binding with registerProvider doesn't count as a + * multibinding, and adding a multibinding doesn't allow to inject the type (it only allows to retrieve multibindings + * through the getMultibindings method of the injector). + * + * Unlike bindings, where adding a the same binding twice is allowed (and ignored), adding the same multibinding + * provider multiple times will result in the creation of multiple "equivalent" instances, that will all be returned + * by getMultibindings. + * It is good practice to add the multibindings in a component that is "close" to the injector in the get*Component + * call chain, to avoid adding the same multibinding more than once. + * + * Example use: + * + * class MyClass { + * public: + * MyClass(int n) {...} + * }; + * + * fruit::Component<> getMyComponent() { + * return fruit::createComponent() + * .addMultibindingProvider([]() { return MyClass(10); }) + * .addMultibindingProvider([]() { return MyClass(10); }) + * .addMultibindingProvider([]() { return MyClass(20); }); + * } + * + * fruit::Injector<> injector(getMyComponent); + * // This vector contains {&x, &y, &z} where x and y are MyClass objects constructed with 10 and z is a MyClass + * // object constructed with 20. + * const std::vector<MyClass*>& objects = injector.getMultibindings<MyClass>(); + * + * Note that this method adds a multibinding for the type returned by the provider. If the returned object implements + * an interface I and you want to add a multibinding for that interface instead, you should cast the pointer to I* + * before returning it. + */ + template <typename Lambda> + PartialComponent<fruit::impl::AddMultibindingProvider<Lambda>, Bindings...> addMultibindingProvider(Lambda lambda); + + /** + * Similar to the previous version of addMultibindingProvider(), but allows to specify an annotated type + * for the provider. This allows to inject annotated types in the parameters and/or bind the + * provider to an annotated type. + * + * Example use: + * + * struct MyAnnotation1 {}; + * struct MyAnnotation2 {}; + * + * Component<fruit::Annotated<Annotation1, Bar>> getBarComponent() {...} + * Component<Baz> getBazComponent() {...} + * + * fruit::Component<> getFooComponent() { + * return fruit::createComponent() + * .install(getBarComponent) + * .install(getBazComponent) + * .registerMultibindingProvider< + * fruit::Annotated<Annotation2, Foo>( + * fruit::Annotated<Annotation1, Bar*>, + * Baz*) + * >([](Bar* bar, Baz* baz) { + * Foo foo(bar, baz); + * foo.initialize(); + * return foo; + * }); + * } + * + * + * fruit::Injector<> injector(getFooComponent); + * // This vector contains {&x} where x is an instance of Foo constructed using the lambda above, with injected + * // instances of Bar and Baz. + * const std::vector<MyClass*>& objects = injector.getMultibindings<fruit::Annotated<Annotation2, Foo>>(); + */ + template <typename AnnotatedSignature, typename Lambda> + PartialComponent<fruit::impl::AddMultibindingProvider<AnnotatedSignature, Lambda>, Bindings...> + addMultibindingProvider(Lambda lambda); + + /** + * Registers `factory' as a factory of C, where `factory' is a lambda with no captures returning C. + * This is typically used for assisted injection (but it can also be used if no parameters are assisted). + * + * C can be any class (or fundamental) type. If C is std::unique_ptr<T>, the factory together with a bind<I,C> in the + * same component will automatically bind the corresponding std::function that returns a std::unique_ptr<I>. + * See the documentation of bind() for more details. + * + * The returned type can't be a pointer type. If you don't want to return it by value, you should return a + * std::unique_ptr instead. + * + * Example: + * + * Component<std::function<std::unique_ptr<MyClass>(int)>> getMyClassComponent() { + * fruit::createComponent() + * .install(getFooComponent) + * .registerFactory<std::unique_ptr<MyClass>(Foo*, fruit::Assisted<int>)>( + * [](Foo* foo, int n) { + * return std::unique_ptr<MyClass>(new MyClass(foo, n)); + * }); + * } + * + * Injector<std::function<std::unique_ptr<MyClass>(int)>> injector(getMyClassComponent); + * + * std::function<std::unique_ptr<MyClass>(int)> factory(injector); + * std::unique_ptr<MyClass> x = factory(42); + * + * The parameters marked as Assisted will become parameters of the std::function (in the same order), while the others + * (e.g. Foo in the example above) will be injected. + * + * Unlike registerProvider(), where the signature is inferred, for this method the signature (including any Assisted + * annotations) must be specified explicitly, while the second template parameter is inferred. + * + * If the only thing that the factory does is to call new and the constructor of the class, it's usually more + * convenient to use an Inject typedef or INJECT macro instead, e.g. the following are equivalent to the above: + * + * class MyClass { + * public: + * using Inject = MyClass(Foo*, Assisted<int>); + * + * MyClass(Foo* foo, int n) {...} + * }; + * + * or: + * + * class MyClass { + * public: + * INJECT(MyClass(Foo* foo, ASSISTED(int) n)) {...} + * }; + * + * Use registerFactory() when you want to inject the class in different ways in different components (just make sure + * those don't end up in the same injector), or when MyClass is a third-party class that can't be modified. + * + * registerFactory() can't be called with a plain function, but you can write a lambda that wraps the function to + * achieve the same result. + * + * Registering stateful functors (including lambdas with captures) is NOT supported. + * However, you can write something like: + * + * struct Functor { + * Functor(float x) {...} + * std::unique_ptr<MyClass> operator()(Foo* foo, int n) {...} + * }; + * + * Component<std::function<std::unique_ptr<MyClass>(int)>> getMyClassComponent() { + * static const Functor aFunctor(42.0); + * return fruit::createComponent() + * ... // Bind Foo + * .bindInstance(aFunctor) + * .registerFactory< + * std::unique_ptr<MyClass>( + * Functor functor, + * Foo*, + * Assisted<int>) + * >([](Functor functor, Foo* foo, int n) { + * return functor(foo, n); + * }); + * } + */ + template <typename DecoratedSignature, typename Factory> + PartialComponent<fruit::impl::RegisterFactory<DecoratedSignature, Factory>, Bindings...> + registerFactory(Factory factory); + + /** + * Adds the bindings (and multibindings) in the Component obtained by calling fun(args...) to the current component. + * + * For example, these component functions: + * fruit::Component<Foo> getComponent1(); + * fruit::Component<Bar> getComponent2(int n, std::string s); + * + * can be combined as: + * + * fruit::Component<Foo, Bar> getFooBarComponent() { + * return fruit::createComponent() + * .install(getComponent1) + * .install(getComponent2, 5, std::string("Hello")); + * } + * + * If any `args` are provided, they must be: + * - Copy-constructible + * - Move-constructible + * - Assignable + * - Move-assignable + * - Equality comparable (i.e., operator== must be defined for two values of that type) + * - Hashable (i.e., std::hash must be defined for values of that type) + * + * Note that this only applies to `args`. E.g. in the example above `int` and `std::string` must satisfy this + * requirement (and they do), but `Foo` and `Bar` don't need to. + * + * Args and FormalArgs (if any) must be the same types; or to be precise, each type in Args must be convertible into + * the corresponding type in FormalArgs. + * + * A lambda with no captures can also be used as the first argument, for example: + * + * fruit::Component<Foo, Bar> getFooBarComponent() { + * return fruit::createComponent() + * .install([]() { return getComponent1(); }) + * .install([](int n) { return getComponent2(n, std::string("Hello")); }, 5); + * } + * + * These two install() calls are equivalent to the previous ones. + * + * As in the example, the template parameters for this method will be inferred by the compiler, it's not necessary to + * specify them explicitly. + * + * Fruit automatically de-duplicates install() calls, so they're effectively memoized (within each injector). + * For example, in this code: + * + * fruit::Component<Foo> getFooComponent() {...} + * + * fruit::Component<Bar> getBarComponent() { + * return fruit::createComponent() + * .install(getFooComponent) + * .bind<Bar, BarImpl>(); + * } + * + * fruit::Component<Baz> getBazComponent() { + * return fruit::createComponent() + * .install(getFooComponent) + * .bind<Baz, BazImpl>(); + * } + * + * fruit::Component<Bar, Baz> getBarBazComponent() { + * return fruit::createComponent() + * .install(getBarComponent) + * .install(getBazComponent); + * } + * + * fruit::Injector<Bar, Baz> injector(getBarBazComponent); + * + * + * getFooComponent() will only be called once. + * For Component functions with arguments, only one call will be done for each set of arguments, but multiple calls + * might be made if multiple sets of arguments are used. + * + * If you actually want a Component function to be called/installed multiple times (e.g. if it binds a multibinding + * and you actually want multiple multibindings to be bound) you can add a dummy argument and specify different values + * for that argument when installing the component. + */ + template <typename... OtherComponentParams, typename... FormalArgs, typename... Args> + PartialComponent<fruit::impl::InstallComponent<fruit::Component<OtherComponentParams...>(FormalArgs...)>, Bindings...> + install(fruit::Component<OtherComponentParams...> (*)(FormalArgs...), Args&&... args); + + /** + * This class is returned by PartialComponent::replace, see the documentation of that method for more information. + */ + template <typename ReplacedComponent, typename... GetReplacedComponentFormalArgs> + class PartialComponentWithReplacementInProgress { + private: + using storage_t = fruit::impl::PartialComponentStorage< + fruit::impl::PartialReplaceComponent<ReplacedComponent(GetReplacedComponentFormalArgs...)>, Bindings...>; + + public: + template <typename... FormalArgs, typename... Args> + PartialComponent<fruit::impl::ReplaceComponent<ReplacedComponent(GetReplacedComponentFormalArgs...), + ReplacedComponent(FormalArgs...)>, + Bindings...> + with(ReplacedComponent (*)(FormalArgs...), Args&&... args); + + PartialComponentWithReplacementInProgress(storage_t storage) : storage(storage) {} + + private: + storage_t storage; + + PartialComponentWithReplacementInProgress() = delete; + }; + + /** + * This allows to replace an installed Component with another one. This is useful for testing. + * For example, if you have these components: + * + * fruit::Component<MyDependency> getDependencyComponent() {...} + * + * fruit::Component<Foo> getFooComponent() { + * return fruit::createComponent() + * .install(getDependencyComponent) + * .bind<Foo, FooImpl>(); + * } + * + * fruit::Component<Bar> getBarComponent() { + * return fruit::createComponent() + * .install(getFooComponent) + * .bind<Bar, BarImpl>(); + * } + * + * When you test Bar, you might want to replace getDependencyComponent with a component that binds a fake + * MyDependency: + * + * fruit::Component<MyDependency> getFakeDependencyComponent() {...} + * + * To do so, you can define a component like: + * + * fruit::Component<Bar> getBarComponentWithFakeDependency() { + * return fruit::createComponent() + * .replace(getDependencyComponent).with(getFakeDependencyComponent) + * .install(getBarComponent); + * } + * + * This component is equivalent to: + * + * fruit::Component<Bar> getBarComponentWithFakeDependency() { + * return fruit::createComponent() + * .install(getFakeDependencyComponent) + * .bind<Foo, FooImpl>() + * .bind<Bar, BarImpl>(); + * } + * + * However this way you don't need to duplicate the bindings for Foo and Bar, and you don't even need to include them + * in the translation unit (i.e., cc/cpp file) that defines getBarComponentWithFakeDependency(). + * In codebases with many layers, this can save a lot of duplication. + * + * Note that the .replace(...).with(...) must appear *before* installing the component to which it's applied to; + * e.g., in the example above note how we install getBarComponent after the replacement in + * getBarComponentWithFakeDependency. + * If you add a replacement after the replaced component has been installed, Fruit will report an error at run-time. + * + * In the example above, the replaced and replacement component functions had no arguments, however it's also possible + * to replace component function with args. The arguments of the replaced and replacement component functions are + * independent; for example .replace(getDependencyComponentWithArgs, 15).with(myFakeComponentWithNoArgs) is allowed + * and it would replace all install(getDependencyComponentWithArgs, 15) calls with install(myFakeComponentWithNoArgs). + * + * The component types returned by the replaced and replacement components must be the same. For example, this is NOT + * allowed: + * + * fruit::Component<MyDependency, SomethingElse> getFakeDependencyComponentWithSomethingElse() {...} + * + * fruit::Component<Bar> getBarComponentWithFakeDependency() { + * return fruit::createComponent() + * .replace(getDependencyComponent).with(getFakeDependencyComponentWithSomethingElse) // error! + * .install(getBarComponent); + * } + * + * But replacing a replaced component is allowed: + * + * fruit::Component<MyDependency> getOtherFakeDependencyComponent() {...} + * + * fruit::Component<Bar> getBarComponentWithOtherFakeDependency() { + * return fruit::createComponent() + * // The two replacements can appear in any order, but they must both be before the install(). + * .replace(getFakeDependencyComponent).with(getOtherFakeDependencyComponent) + * .replace(getDependencyComponent).with(getFakeDependencyComponent) + * .install(getBarComponent); + * } + * + * Of course this is a simple example, in the real world the replacements and the install would probably come from + * other components. + * + * And note that you can also replace components that define replacements, for example: + * + * fruit::Component<> getFakeDependencyReplacementComponent() { + * return fruit::createComponent() + * .replace(getDependencyComponent).with(getFakeDependencyComponentWithSomethingElse); + * } + * + * fruit::Component<...> getComponent() { + * return fruit::createComponent() + * .replace(getFakeDependencyReplacementComponent).with(...) + * .install(...); + * } + * + * Replacements are only installed if the replaced component is installed, otherwise they are ignored. + * In the first example above, if getFooComponent didn't install getDependencyComponent, when a test creates an + * injector for getBarComponentWithFakeDependency it would not install getFakeDependencyComponent. + */ + template <typename... OtherComponentParams, typename... FormalArgs, typename... Args> + typename PartialComponent<Bindings...>::template PartialComponentWithReplacementInProgress< + fruit::Component<OtherComponentParams...>, FormalArgs...> + replace(fruit::Component<OtherComponentParams...> (*)(FormalArgs...), Args&&... args); + + ~PartialComponent(); + +private: + template <typename... OtherBindings> + friend class PartialComponent; + + template <typename... Types> + friend class Component; + + fruit::impl::PartialComponentStorage<Bindings...> storage; + + // Do not use. Use fruit::createComponent() instead. + PartialComponent() = delete; + + // Do not use. Only use PartialComponent for temporaries, and then convert it to a Component. + PartialComponent(const PartialComponent&) = delete; + PartialComponent(PartialComponent&&) = delete; + + PartialComponent(fruit::impl::PartialComponentStorage<Bindings...> storage); + + template <typename NewBinding> + using OpFor = typename fruit::impl::meta::OpForComponent<Bindings...>::template AddBinding<NewBinding>; + + friend PartialComponent<> createComponent(); +}; + +} // namespace fruit + +#include <fruit/impl/component.defn.h> + +#endif // FRUIT_COMPONENT_H diff --git a/include/fruit/fruit.h b/include/fruit/fruit.h new file mode 100644 index 0000000..ff208e7 --- /dev/null +++ b/include/fruit/fruit.h @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FRUIT_H +#define FRUIT_FRUIT_H + +// This header includes all the public Fruit headers. +// To limit the amount of included code, you might want to only include the necessary headers (if only forward +// declarations are needed you can include fruit_forward_decls.h). + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/component.h> +#include <fruit/fruit_forward_decls.h> +#include <fruit/injector.h> +#include <fruit/macro.h> +#include <fruit/normalized_component.h> +#include <fruit/provider.h> + +#endif // FRUIT_FRUIT_H diff --git a/include/fruit/fruit_forward_decls.h b/include/fruit/fruit_forward_decls.h new file mode 100644 index 0000000..af02706 --- /dev/null +++ b/include/fruit/fruit_forward_decls.h @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FRUIT_FORWARD_DECLS_H +#define FRUIT_FRUIT_FORWARD_DECLS_H + +// This header contains forward declarations of all types in the `fruit' namespace. +// Avoid writing forward declarations yourself; use this header instead. + +namespace fruit { + +/** + * This is used to group the requirements of Component. See Component for details. + * Note: this type is declared but not defined; that's by design since instances of this type are not meaningful. + */ +template <typename... Types> +struct Required; + +/** + * This is used to "annotate" T as a type that uses assisted injection. See PartialComponent for details. + * Note: this type is declared but not defined; that's by design since instances of this type are not meaningful. + */ +template <typename T> +struct Assisted; + +/** + * This is used to annotate T as a type that will be user-supplied instead of being injected. + * See PartialComponent::registerFactory for details. + */ +template <typename Annotation, typename T> +struct Annotated {}; + +template <typename... Types> +class Component; + +class EmptyPartialComponent; + +template <typename... Bindings> +class PartialComponent; + +template <typename... Types> +class NormalizedComponent; + +template <typename C> +class Provider; + +template <typename... P> +class Injector; + +} // namespace fruit + +#endif // FRUIT_FRUIT_FORWARD_DECLS_H diff --git a/include/fruit/impl/bindings.h b/include/fruit/impl/bindings.h new file mode 100644 index 0000000..c8694e5 --- /dev/null +++ b/include/fruit/impl/bindings.h @@ -0,0 +1,142 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_BINDINGS_H +#define FRUIT_BINDINGS_H + +#include <fruit/impl/meta/metaprogramming.h> + +namespace fruit { +namespace impl { + +// The types here represent individual entries added in a PartialComponent. + +/** + * Binds the base class I to the implementation C. + * I must be a base class of C. I=C is not allowed. + * I and/or C may be annotated using fruit::Annotated<>. + */ +template <typename I, typename C> +struct Bind {}; + +/** + * Registers Signature as the constructor signature to use to inject a type. + * Signature must be a valid signature and its return type must be constructible with those argument + * types. + * The arguments and the return type can be annotated using fruit::Annotated<>. + */ +template <typename Signature> +struct RegisterConstructor {}; + +/** + * Binds an instance (i.e., object) to the type C. + * AnnotatedC may be annotated using fruit::Annotated<>. + * NOTE: for this binding, the runtime binding is added in advance. + */ +template <typename AnnotatedC, typename C> +struct BindInstance {}; + +/** + * A variant of BindInstance that binds a constant reference. + */ +template <typename AnnotatedC, typename C> +struct BindConstInstance {}; + +template <typename... Params> +struct RegisterProvider; + +/** + * Registers `provider' as a provider of C, where provider is a lambda with no captures returning + * either C or C*. + */ +template <typename Lambda> +struct RegisterProvider<Lambda> {}; + +/** + * Registers `provider' as a provider of C, where provider is a lambda with no captures returning + * either C or C*. Lambda must have the signature AnnotatedSignature (ignoring annotations). + */ +template <typename AnnotatedSignature, typename Lambda> +struct RegisterProvider<Lambda, AnnotatedSignature> {}; + +/** + * Adds a multibinding for an instance (as a C&). + */ +template <typename C> +struct AddInstanceMultibinding {}; + +/** + * Adds multibindings for a vector of instances (as a std::vector<C>&). + */ +template <typename C> +struct AddInstanceVectorMultibindings {}; + +/** + * Similar to Bind<I, C>, but adds a multibinding instead. + */ +template <typename I, typename C> +struct AddMultibinding {}; + +template <typename... Params> +struct AddMultibindingProvider; + +/** + * Similar to RegisterProvider, but adds a multibinding instead. + */ +template <typename Lambda> +struct AddMultibindingProvider<Lambda> {}; + +/** + * Similar to RegisterProvider, but adds a multibinding instead. + * Lambda must have the signature AnnotatedSignature (ignoring annotations). + */ +template <typename AnnotatedSignature, typename Lambda> +struct AddMultibindingProvider<AnnotatedSignature, Lambda> {}; + +/** + * Registers `Lambda' as a factory of C, where `Lambda' is a lambda with no captures returning C. + * Lambda must have signature DecoratedSignature (ignoring any fruit::Annotated<> and + * fruit::Assisted<>). + * Lambda must return a C by value, or a std::unique_ptr<C>. + */ +template <typename DecoratedSignature, typename Lambda> +struct RegisterFactory {}; + +/** + * Adds the bindings (and multibindings) in `component' to the current component. + * OtherComponent must be of the form Component<...>. + * NOTE: for this binding, the runtime binding is added in advance. + */ +template <typename GetComponentFunction> +struct InstallComponent {}; + +/** + * An in-progress ReplaceComponent operation, where we don't have all the required information yet. + */ +template <typename GetReplacedComponent> +struct PartialReplaceComponent {}; + +/** + * Replaces install()s for a component with install()s for another one. + * The two Get*Component function signatures must return the same Component<...> type. + */ +template <typename GetReplacedComponent, typename GetReplacementComponent> +struct ReplaceComponent {}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_BINDINGS_H diff --git a/include/fruit/impl/component.defn.h b/include/fruit/impl/component.defn.h new file mode 100644 index 0000000..ce3fb90 --- /dev/null +++ b/include/fruit/impl/component.defn.h @@ -0,0 +1,315 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_DEFN_H +#define FRUIT_COMPONENT_DEFN_H + +#include <fruit/component.h> + +#include <fruit/impl/component_storage/component_storage.h> +#include <fruit/impl/injection_errors.h> + +#include <memory> + +namespace fruit { + +namespace impl { +namespace meta { +// This is a helper class used in the implementation of Component and PartialComponent. +// It's in fruit::impl::meta so that we don't need to qualify everything with fruit::impl::meta. +template <typename... PreviousBindings> +struct OpForComponent { + template <typename Comp> + using ConvertTo = Eval<Call(ReverseComposeFunctors(Id<ComponentFunctor(ConvertComponent, Comp)>, + ProcessDeferredBindings, Id<ProcessBinding(PreviousBindings)>...), + ConstructComponentImpl())>; + + template <typename Binding> + using AddBinding = + Eval<Call(ReverseComposeFunctors(Id<ProcessBinding(Binding)>, Id<ProcessBinding(PreviousBindings)>...), + ConstructComponentImpl())>; +}; +} // namespace meta +} // namespace impl + +template <typename... Params> +template <typename... Bindings> +inline Component<Params...>::Component(PartialComponent<Bindings...>&& partial_component) : storage() { + + (void)typename fruit::impl::meta::CheckIfError<Comp>::type(); + + using Op = typename fruit::impl::meta::OpForComponent<Bindings...>::template ConvertTo<Comp>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + +#ifndef FRUIT_NO_LOOP_CHECK + (void)typename fruit::impl::meta::CheckIfError< + fruit::impl::meta::Eval<fruit::impl::meta::CheckNoLoopInDeps(typename Op::Result)>>::type(); +#endif // !FRUIT_NO_LOOP_CHECK + + std::size_t num_entries = partial_component.storage.numBindings() + Op().numEntries(); + fruit::impl::FixedSizeVector<fruit::impl::ComponentStorageEntry> entries(num_entries); + + Op()(entries); + + // addBindings may modify the storage member of PartialComponent. + // Therefore, it should not be used after this operation. + partial_component.storage.addBindings(entries); + + // TODO: re-enable this check somehow. + // component.component.already_converted_to_component = true; + + FruitAssert(entries.size() == num_entries); + + storage = fruit::impl::ComponentStorage(std::move(entries)); +} + +template <typename... Bindings> +inline PartialComponent<Bindings...>::~PartialComponent() {} + +inline PartialComponent<> createComponent() { + return {{}}; +} + +template <typename... Bindings> +template <typename AnnotatedI, typename AnnotatedC> +inline PartialComponent<fruit::impl::Bind<AnnotatedI, AnnotatedC>, Bindings...> PartialComponent<Bindings...>::bind() { + using Op = OpFor<fruit::impl::Bind<AnnotatedI, AnnotatedC>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +template <typename AnnotatedSignature> +inline PartialComponent<fruit::impl::RegisterConstructor<AnnotatedSignature>, Bindings...> +PartialComponent<Bindings...>::registerConstructor() { + using Op = OpFor<fruit::impl::RegisterConstructor<AnnotatedSignature>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +template <typename C> +inline PartialComponent<fruit::impl::BindInstance<C, C>, Bindings...> +PartialComponent<Bindings...>::bindInstance(C& instance) { + using Op = OpFor<fruit::impl::BindInstance<C, C>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename C> +inline PartialComponent<fruit::impl::BindConstInstance<C, C>, Bindings...> +PartialComponent<Bindings...>::bindInstance(const C& instance) { + using Op = OpFor<fruit::impl::BindConstInstance<C, C>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename AnnotatedC, typename C> +inline PartialComponent<fruit::impl::BindInstance<AnnotatedC, C>, Bindings...> +PartialComponent<Bindings...>::bindInstance(C& instance) { + using Op = OpFor<fruit::impl::BindInstance<AnnotatedC, C>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename AnnotatedC, typename C> +inline PartialComponent<fruit::impl::BindConstInstance<AnnotatedC, C>, Bindings...> +PartialComponent<Bindings...>::bindInstance(const C& instance) { + using Op = OpFor<fruit::impl::BindConstInstance<AnnotatedC, C>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename Lambda> +inline PartialComponent<fruit::impl::RegisterProvider<Lambda>, Bindings...> +PartialComponent<Bindings...>::registerProvider(Lambda) { + using Op = OpFor<fruit::impl::RegisterProvider<Lambda>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage}}; +} + +template <typename... Bindings> +template <typename AnnotatedSignature, typename Lambda> +inline PartialComponent<fruit::impl::RegisterProvider<AnnotatedSignature, Lambda>, Bindings...> +PartialComponent<Bindings...>::registerProvider(Lambda) { + using Op = OpFor<fruit::impl::RegisterProvider<AnnotatedSignature, Lambda>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage}}; +} + +template <typename... Bindings> +template <typename AnnotatedI, typename AnnotatedC> +inline PartialComponent<fruit::impl::AddMultibinding<AnnotatedI, AnnotatedC>, Bindings...> +PartialComponent<Bindings...>::addMultibinding() { + using Op = OpFor<fruit::impl::AddMultibinding<AnnotatedI, AnnotatedC>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +template <typename C> +inline PartialComponent<fruit::impl::AddInstanceMultibinding<C>, Bindings...> +PartialComponent<Bindings...>::addInstanceMultibinding(C& instance) { + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename AnnotatedC, typename C> +inline PartialComponent<fruit::impl::AddInstanceMultibinding<AnnotatedC>, Bindings...> +PartialComponent<Bindings...>::addInstanceMultibinding(C& instance) { + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instance}}; +} + +template <typename... Bindings> +template <typename C> +inline PartialComponent<fruit::impl::AddInstanceVectorMultibindings<C>, Bindings...> +PartialComponent<Bindings...>::addInstanceMultibindings(std::vector<C>& instances) { + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return {{storage, instances}}; +} + +template <typename... Bindings> +template <typename AnnotatedC, typename C> +inline PartialComponent<fruit::impl::AddInstanceVectorMultibindings<AnnotatedC>, Bindings...> +PartialComponent<Bindings...>::addInstanceMultibindings(std::vector<C>& instances) { + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage, instances}}; +} + +template <typename... Bindings> +template <typename Lambda> +inline PartialComponent<fruit::impl::AddMultibindingProvider<Lambda>, Bindings...> +PartialComponent<Bindings...>::addMultibindingProvider(Lambda) { + using Op = OpFor<fruit::impl::AddMultibindingProvider<Lambda>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +template <typename AnnotatedSignature, typename Lambda> +inline PartialComponent<fruit::impl::AddMultibindingProvider<AnnotatedSignature, Lambda>, Bindings...> +PartialComponent<Bindings...>::addMultibindingProvider(Lambda) { + using Op = OpFor<fruit::impl::AddMultibindingProvider<AnnotatedSignature, Lambda>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +template <typename DecoratedSignature, typename Lambda> +inline PartialComponent<fruit::impl::RegisterFactory<DecoratedSignature, Lambda>, Bindings...> +PartialComponent<Bindings...>::registerFactory(Lambda) { + using Op = OpFor<fruit::impl::RegisterFactory<DecoratedSignature, Lambda>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return {{storage}}; +} + +template <typename... Bindings> +inline PartialComponent<Bindings...>::PartialComponent(fruit::impl::PartialComponentStorage<Bindings...> storage) + : storage(std::move(storage)) {} + +template <typename T> +FRUIT_ALWAYS_INLINE inline int checkAcceptableComponentInstallArg() { + // This lambda checks that the required operations on T exist. + // Note that the lambda is never actually executed. + auto checkRequirements = [](const T& constRef, T value) { + T x1(constRef); + T x2(std::move(value)); + x1 = constRef; + x2 = std::move(value); + bool b = (constRef == constRef); + std::size_t h = std::hash<T>()(constRef); + (void)x1; + (void)x2; + (void)b; + (void)h; + }; + (void)checkRequirements; + return 0; +} + +template <typename... Bindings> +template <typename... OtherComponentParams, typename... FormalArgs, typename... Args> +inline PartialComponent<fruit::impl::InstallComponent<fruit::Component<OtherComponentParams...>(FormalArgs...)>, + Bindings...> +PartialComponent<Bindings...>::install(fruit::Component<OtherComponentParams...> (*getComponent)(FormalArgs...), + Args&&... args) { + using IntCollector = int[]; + (void)IntCollector{0, checkAcceptableComponentInstallArg<FormalArgs>()...}; + + using Op = OpFor<fruit::impl::InstallComponent<fruit::Component<OtherComponentParams...>(FormalArgs...)>>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + std::tuple<FormalArgs...> args_tuple{std::forward<Args>(args)...}; + + return {{storage, getComponent, std::move(args_tuple)}}; +} + +template <typename... Bindings> +template <typename... OtherComponentParams, typename... FormalArgs, typename... Args> +inline typename PartialComponent<Bindings...>::template PartialComponentWithReplacementInProgress< + fruit::Component<OtherComponentParams...>, FormalArgs...> +PartialComponent<Bindings...>::replace(fruit::Component<OtherComponentParams...> (*getReplacedComponent)(FormalArgs...), + Args&&... args) { + using IntCollector = int[]; + (void)IntCollector{0, checkAcceptableComponentInstallArg<FormalArgs>()...}; + + std::tuple<FormalArgs...> args_tuple{std::forward<Args>(args)...}; + + return {{storage, getReplacedComponent, std::move(args_tuple)}}; +} + +template <typename... Bindings> +template <typename OtherComponent, typename... GetReplacedComponentFormalArgs> +template <typename... GetReplacementComponentFormalArgs, typename... Args> +inline PartialComponent<fruit::impl::ReplaceComponent<OtherComponent(GetReplacedComponentFormalArgs...), + OtherComponent(GetReplacementComponentFormalArgs...)>, + Bindings...> +PartialComponent<Bindings...>:: + PartialComponentWithReplacementInProgress<OtherComponent, GetReplacedComponentFormalArgs...>::with( + OtherComponent (*getReplacementComponent)(GetReplacementComponentFormalArgs...), Args&&... args) { + using IntCollector = int[]; + (void)IntCollector{0, checkAcceptableComponentInstallArg<GetReplacementComponentFormalArgs>()...}; + + std::tuple<GetReplacementComponentFormalArgs...> args_tuple{std::forward<Args>(args)...}; + + return {{storage, getReplacementComponent, std::move(args_tuple)}}; +} + +} // namespace fruit + +#endif // FRUIT_COMPONENT_DEFN_H diff --git a/include/fruit/impl/component_functors.defn.h b/include/fruit/impl/component_functors.defn.h new file mode 100644 index 0000000..c65f953 --- /dev/null +++ b/include/fruit/impl/component_functors.defn.h @@ -0,0 +1,1262 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_FUNCTORS_DEFN_H +#define FRUIT_COMPONENT_FUNCTORS_DEFN_H + +#include <fruit/component.h> + +#include <fruit/impl/injection_debug_errors.h> +#include <fruit/impl/injection_errors.h> +#include <fruit/impl/injector/injector_storage.h> + +#include <memory> + +/********************************************************************************************************************************* + This file contains functors that take a Comp and return a struct Op with the form: + struct { + using Result = Comp1; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) {...} + std::size_t numEntries() {...} + } +*********************************************************************************************************************************/ + +namespace fruit { +namespace impl { +namespace meta { + +struct GetResult { + template <typename F> + struct apply { + using type = typename F::Result; + }; +}; + +// Call(ComponentFunctor(F, Args...), Comp) +// is equivalent to: +// F(Comp, Args...) +struct ComponentFunctor { + template <typename F, typename... Args> + struct apply { + struct type { + template <typename Comp> + struct apply { + using type = F(Comp, Args...); + }; + }; + }; +}; + +struct ComponentFunctorIdentity { + template <typename Comp> + struct apply { + struct type { + using Result = Comp; + void operator()(FixedSizeVector<ComponentStorageEntry>&) {} + std::size_t numEntries() { + return 0; + } + }; + }; +}; + +struct Compose2ComponentFunctors { + template <typename F1, typename F2> + struct apply { + struct type { + template <typename Comp> + struct apply { + using Op1 = F1(Comp); + using Op2 = F2(GetResult(Op1)); + struct Op { + using Result = Eval<GetResult(Op2)>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + Eval<Op2>()(entries); + Eval<Op1>()(entries); + } + std::size_t numEntries() { + return Eval<Op1>().numEntries() + Eval<Op2>().numEntries(); + } + }; + using type = PropagateError(Op1, PropagateError(Op2, Op)); + }; + }; + }; +}; + +// ComposeFunctors(F1,..,Fn) returns a functor that executes F1,..,Fn in order (stopping at the +// first Error). +struct ComposeFunctors { + template <typename... Functors> + struct apply { + using type = Fold(Compose2ComponentFunctors, ComponentFunctorIdentity, Functors...); + }; +}; + +// ReverseComposeFunctors(T1, ..., Tn) is equivalent to ComposeFunctors(Tn, ..., T1), but it's more +// efficient when all of the following must be evaluated: +// ReverseComposeFunctors<T1> +// ReverseComposeFunctors<T2, T1> +// ReverseComposeFunctors<T3, T2, T1> +// In that case, this implementation shares many more instantiations with previous invocations +struct ReverseComposeFunctors { + template <typename... Functors> + struct apply { + using type = ComponentFunctorIdentity; + }; + + template <typename Functor> + struct apply<Functor> { + using type = Functor; + }; + + template <typename Functor, typename... Functors> + struct apply<Functor, Functors...> { + using type = Compose2ComponentFunctors(ReverseComposeFunctors(Functors...), Functor); + }; +}; + +struct EnsureProvidedType; + +struct EnsureProvidedTypes; + +// Doesn't actually bind in ComponentStorage. The binding is added later (if needed) using ProcessInterfaceBinding. +struct AddDeferredInterfaceBinding { + template <typename Comp, typename AnnotatedI, typename AnnotatedC> + struct apply { + using Comp1 = ConsComp(typename Comp::RsSuperset, typename Comp::Ps, typename Comp::NonConstRsPs, +#ifndef FRUIT_NO_LOOP_CHECK + typename Comp::Deps, +#endif + PushFront(typename Comp::InterfaceBindings, Pair<AnnotatedI, AnnotatedC>), + typename Comp::DeferredBindingFunctors); + struct Op { + // Note that we do NOT call AddProvidedType here. We'll only know the right required type + // when the binding will be used. + using Result = Eval<Comp1>; + void operator()(FixedSizeVector<ComponentStorageEntry>&) {} + std::size_t numEntries() { + return 0; + } + }; + using I = RemoveAnnotations(AnnotatedI); + using C = RemoveAnnotations(AnnotatedC); + using type = + If(IsSame(I, C), ConstructError(InterfaceBindingToSelfErrorTag, C), + If(Not(IsBaseOf(I, C)), ConstructError(NotABaseClassOfErrorTag, I, C), + If(Not(IsSame(I, NormalizeType(I))), ConstructError(NonClassTypeErrorTag, I, NormalizeUntilStable(I)), + If(Not(IsSame(C, NormalizeType(C))), + // We handle this case too, just to be on the safe side, but this should never happen. + ConstructError(NonClassTypeErrorTag, C, NormalizeUntilStable(C)), + If(IsInSet(AnnotatedI, typename Comp::Ps), ConstructError(TypeAlreadyBoundErrorTag, AnnotatedI), + If(MapContainsKey(typename Comp::InterfaceBindings, AnnotatedI), + ConstructError(TypeAlreadyBoundErrorTag, AnnotatedI), Op)))))); + }; +}; + +struct ProcessInterfaceBinding { + template <typename Comp, typename AnnotatedI, typename AnnotatedC, typename NonConstBindingRequired> + struct apply { + using R = If(NonConstBindingRequired, + AddProvidedTypeIgnoringInterfaceBindings(Comp, AnnotatedI, NonConstBindingRequired, Vector<AnnotatedC>, + Vector<AnnotatedC>), + AddProvidedTypeIgnoringInterfaceBindings(Comp, AnnotatedI, NonConstBindingRequired, Vector<AnnotatedC>, + Vector<>)); + struct ConstOp { + // This must be here (and not in AddDeferredInterfaceBinding) because the binding might be + // used to bind functors instead, so we might never need to add C to the requirements. + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + InjectorStorage::createComponentStorageEntryForConstBind<UnwrapType<AnnotatedI>, UnwrapType<AnnotatedC>>()); + }; + + std::size_t numEntries() { + return 1; + } + }; + struct NonConstOp { + // This must be here (and not in AddDeferredInterfaceBinding) because the binding might be + // used to bind functors instead, so we might never need to add C to the requirements. + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + InjectorStorage::createComponentStorageEntryForBind<UnwrapType<AnnotatedI>, UnwrapType<AnnotatedC>>()); + }; + + std::size_t numEntries() { + return 1; + } + }; + using type = PropagateError(R, If(NonConstBindingRequired, NonConstOp, ConstOp)); + }; +}; + +struct AddInterfaceMultibinding { + template <typename Comp, typename AnnotatedI, typename AnnotatedC> + struct apply { + using I = RemoveAnnotations(AnnotatedI); + using C = RemoveAnnotations(AnnotatedC); + using R = AddRequirements(Comp, Vector<AnnotatedC>, Vector<AnnotatedC>); + struct Op { + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back(InjectorStorage::createComponentStorageEntryForMultibinding<UnwrapType<AnnotatedI>, + UnwrapType<AnnotatedC>>()); + entries.push_back( + InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<UnwrapType<AnnotatedI>>()); + }; + + std::size_t numEntries() { + return 2; + } + }; + using type = If(Not(IsBaseOf(I, C)), ConstructError(NotABaseClassOfErrorTag, I, C), Op); + }; +}; + +template <typename AnnotatedSignature, typename Lambda, typename OptionalAnnotatedI> +struct PostProcessRegisterProviderHelper; + +template <typename AnnotatedSignature, typename Lambda, typename AnnotatedI> +struct PostProcessRegisterProviderHelper; + +template <typename AnnotatedSignature, typename Lambda, typename AnnotatedI> +struct PostProcessRegisterProviderHelper<AnnotatedSignature, Lambda, Type<AnnotatedI>> { + inline void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + InjectorStorage::createComponentStorageEntryForCompressedProvider<AnnotatedSignature, Lambda, AnnotatedI>()); + entries.push_back(InjectorStorage::createComponentStorageEntryForProvider<AnnotatedSignature, Lambda>()); + } + + std::size_t numEntries() { + return 2; + } +}; + +template <typename AnnotatedSignature, typename Lambda> +struct PostProcessRegisterProviderHelper<AnnotatedSignature, Lambda, None> { + inline void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back(InjectorStorage::createComponentStorageEntryForProvider<AnnotatedSignature, Lambda>()); + } + + std::size_t numEntries() { + return 1; + } +}; + +// T can't be any injectable type, it must match the return type of the provider in one of +// the registerProvider() overloads in ComponentStorage. +struct PostProcessRegisterProvider { + template <typename Comp, typename AnnotatedSignature, typename Lambda> + struct apply { + using AnnotatedC = NormalizeType(SignatureType(AnnotatedSignature)); + using OptionalAnnotatedI = FindValueInMap(typename Comp::InterfaceBindings, AnnotatedC); + struct Op { + using Result = Comp; + + using Helper = PostProcessRegisterProviderHelper<UnwrapType<AnnotatedSignature>, UnwrapType<Lambda>, + Eval<OptionalAnnotatedI>>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + Helper()(entries); + } + std::size_t numEntries() { + return Helper().numEntries(); + } + }; + using type = Op; + }; +}; + +struct PreProcessRegisterProvider { + template <typename Comp, typename AnnotatedSignature, typename Lambda> + struct apply { + using Signature = RemoveAnnotationsFromSignature(AnnotatedSignature); + using SignatureFromLambda = FunctionSignature(Lambda); + + using AnnotatedC = NormalizeType(SignatureType(AnnotatedSignature)); + using AnnotatedCDeps = NormalizeTypeVector(SignatureArgs(AnnotatedSignature)); + using R = AddProvidedType(Comp, AnnotatedC, Bool<true>, AnnotatedCDeps, + Id<NormalizedNonConstTypesIn(SignatureArgs(AnnotatedSignature))>); + using type = + If(Not(IsSame(Signature, SignatureFromLambda)), + ConstructError(AnnotatedSignatureDifferentFromLambdaSignatureErrorTag, Signature, SignatureFromLambda), + PropagateError( + CheckInjectableType(RemoveAnnotations(SignatureType(AnnotatedSignature))), + PropagateError( + CheckInjectableTypeVector(RemoveAnnotationsFromVector(AnnotatedCDeps)), + PropagateError( + CheckInjectableType(SignatureType(SignatureFromLambda)), + PropagateError( + CheckInjectableTypeVector(SignatureArgs(SignatureFromLambda)), + If(And(IsPointer(SignatureType(SignatureFromLambda)), + And(IsAbstract(RemovePointer(SignatureType(SignatureFromLambda))), + Not(HasVirtualDestructor(RemovePointer(SignatureType(SignatureFromLambda)))))), + ConstructError(ProviderReturningPointerToAbstractClassWithNoVirtualDestructorErrorTag, + RemovePointer(SignatureType(SignatureFromLambda))), + ComponentFunctorIdentity(R))))))); + }; +}; + +// The registration is actually deferred until the PartialComponent is converted to a component. +struct DeferredRegisterProviderWithAnnotations { + template <typename Comp, typename AnnotatedSignature, typename Lambda> + struct apply { + using Comp1 = AddDeferredBinding(Comp, ComponentFunctor(PostProcessRegisterProvider, AnnotatedSignature, Lambda)); + using type = PreProcessRegisterProvider(Comp1, AnnotatedSignature, Lambda); + }; +}; + +// The registration is actually deferred until the PartialComponent is converted to a component. +struct DeferredRegisterProvider { + template <typename Comp, typename Lambda> + struct apply { + using type = DeferredRegisterProviderWithAnnotations(Comp, FunctionSignature(Lambda), Lambda); + }; +}; + +// T can't be any injectable type, it must match the return type of the provider in one of +// the registerMultibindingProvider() overloads in ComponentStorage. +struct RegisterMultibindingProviderWithAnnotations { + template <typename Comp, typename AnnotatedSignature, typename Lambda> + struct apply { + using Signature = RemoveAnnotationsFromSignature(AnnotatedSignature); + using SignatureFromLambda = FunctionSignature(Lambda); + + using AnnotatedArgs = SignatureArgs(AnnotatedSignature); + using AnnotatedArgVector = NormalizeTypeVector(AnnotatedArgs); + using NonConstRequirements = NormalizedNonConstTypesIn(AnnotatedArgs); + using R = AddRequirements(Comp, AnnotatedArgVector, NonConstRequirements); + struct Op { + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + InjectorStorage::createComponentStorageEntryForMultibindingProvider<UnwrapType<AnnotatedSignature>, + UnwrapType<Lambda>>()); + entries.push_back(InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator< + UnwrapType<Eval<NormalizeType(SignatureType(AnnotatedSignature))>>>()); + } + std::size_t numEntries() { + return 2; + } + }; + using type = If( + Not(IsValidSignature(AnnotatedSignature)), ConstructError(NotASignatureErrorTag, AnnotatedSignature), + PropagateError( + CheckInjectableType(RemoveAnnotations(SignatureType(AnnotatedSignature))), + PropagateError( + CheckInjectableTypeVector(RemoveAnnotationsFromVector(SignatureArgs(AnnotatedSignature))), + PropagateError( + CheckInjectableType(SignatureType(SignatureFromLambda)), + PropagateError( + CheckInjectableTypeVector(SignatureArgs(SignatureFromLambda)), + If(IsAbstract(RemoveAnnotations(SignatureType(AnnotatedSignature))), + ConstructError(CannotConstructAbstractClassErrorTag, + RemoveAnnotations(SignatureType(AnnotatedSignature))), + If(Not(IsSame(Signature, SignatureFromLambda)), + ConstructError(AnnotatedSignatureDifferentFromLambdaSignatureErrorTag, Signature, + SignatureFromLambda), + If(And(IsPointer(SignatureType(SignatureFromLambda)), + And(IsAbstract(RemovePointer(SignatureType(SignatureFromLambda))), + Not(HasVirtualDestructor(RemovePointer(SignatureType(SignatureFromLambda)))))), + ConstructError( + MultibindingProviderReturningPointerToAbstractClassWithNoVirtualDestructorErrorTag, + RemovePointer(SignatureType(SignatureFromLambda))), + PropagateError(R, Op))))))))); + }; +}; + +// T can't be any injectable type, it must match the return type of the provider in one of +// the registerMultibindingProvider() overloads in ComponentStorage. +struct RegisterMultibindingProvider { + template <typename Comp, typename Lambda> + struct apply { + using type = RegisterMultibindingProviderWithAnnotations(Comp, FunctionSignature(Lambda), Lambda); + }; +}; + +// Non-assisted case. +template <int numAssistedBefore, int numNonAssistedBefore, typename Arg> +struct GetAssistedArg { + template <typename InjectedArgsTuple, typename UserProvidedArgsTuple> + inline Arg operator()(InjectedArgsTuple& injected_args, UserProvidedArgsTuple&) { + return std::get<numNonAssistedBefore>(injected_args); + } +}; + +// Assisted case. +template <int numAssistedBefore, int numNonAssistedBefore, typename Arg> +struct GetAssistedArg<numAssistedBefore, numNonAssistedBefore, Assisted<Arg>> { + template <typename InjectedArgsTuple, typename UserProvidedArgsTuple> + inline Arg operator()(InjectedArgsTuple&, UserProvidedArgsTuple& user_provided_args) { + return std::get<numAssistedBefore>(user_provided_args); + } +}; + +struct RegisterFactoryHelper { + + template <typename Comp, typename DecoratedSignature, typename Lambda, + // std::function<InjectedSignature> is the injected type (possibly with an Annotation<> wrapping it) + typename InjectedSignature, typename RequiredLambdaSignature, typename InjectedAnnotatedArgs, + // The types that are injected, unwrapped from any Annotation<>. + typename InjectedArgs, typename IndexSequence> + struct apply; + + template <typename Comp, typename DecoratedSignature, typename Lambda, typename NakedC, + typename... NakedUserProvidedArgs, typename... NakedAllArgs, typename... InjectedAnnotatedArgs, + typename... NakedInjectedArgs, typename... Indexes> + struct apply<Comp, DecoratedSignature, Lambda, Type<NakedC(NakedUserProvidedArgs...)>, Type<NakedC(NakedAllArgs...)>, + Vector<InjectedAnnotatedArgs...>, Vector<Type<NakedInjectedArgs>...>, Vector<Indexes...>> { + // Here we call "decorated" the types that might be wrapped in Annotated<> or Assisted<>, + // while we call "annotated" the ones that might only be wrapped in Annotated<> (but not Assisted<>). + using AnnotatedT = SignatureType(DecoratedSignature); + using T = RemoveAnnotations(AnnotatedT); + using DecoratedArgs = SignatureArgs(DecoratedSignature); + using NakedInjectedSignature = NakedC(NakedUserProvidedArgs...); + using NakedRequiredSignature = NakedC(NakedAllArgs...); + using NakedFunctor = std::function<NakedInjectedSignature>; + // This is usually the same as Functor, but this might be annotated. + using AnnotatedFunctor = CopyAnnotation(AnnotatedT, Type<NakedFunctor>); + using FunctorDeps = NormalizeTypeVector(Vector<InjectedAnnotatedArgs...>); + using FunctorNonConstDeps = NormalizedNonConstTypesIn(Vector<InjectedAnnotatedArgs...>); + using R = AddProvidedType(Comp, AnnotatedFunctor, Bool<true>, FunctorDeps, FunctorNonConstDeps); + struct Op { + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + auto function_provider = [](NakedInjectedArgs... args) { + auto injected_args = std::make_tuple(args...); + auto object_provider = [injected_args](NakedUserProvidedArgs... params) mutable { + auto user_provided_args = std::tie(params...); + // These are unused if they are 0-arg tuples. Silence the unused-variable warnings anyway. + (void)injected_args; + (void)user_provided_args; + + return LambdaInvoker::invoke<UnwrapType<Lambda>, NakedAllArgs...>( + GetAssistedArg< + Eval<NumAssistedBefore(Indexes, DecoratedArgs)>::value, + getIntValue<Indexes>() - Eval<NumAssistedBefore(Indexes, DecoratedArgs)>::value, + // Note that the Assisted<> wrapper (if any) remains, we just remove any wrapping Annotated<>. + UnwrapType<Eval<RemoveAnnotations(GetNthType(Indexes, DecoratedArgs))>>>()(injected_args, + user_provided_args)...); + }; + return NakedFunctor(object_provider); + }; + entries.push_back(InjectorStorage::createComponentStorageEntryForProvider< + UnwrapType<Eval<ConsSignatureWithVector(AnnotatedFunctor, Vector<InjectedAnnotatedArgs...>)>>, + decltype(function_provider)>()); + } + std::size_t numEntries() { + return 1; + } + }; + // The first two IsValidSignature checks are a bit of a hack, they are needed to make the F2/RealF2 split + // work in the caller (we need to allow Lambda to be a function type). + using type = If(Not(IsSame(Type<NakedRequiredSignature>, FunctionSignature(Lambda))), + ConstructError(FunctorSignatureDoesNotMatchErrorTag, Type<NakedRequiredSignature>, + FunctionSignature(Lambda)), + If(IsPointer(T), ConstructError(FactoryReturningPointerErrorTag, DecoratedSignature), + PropagateError(R, Op))); + }; +}; + +struct RegisterFactory { + template <typename Comp, typename DecoratedSignature, typename Lambda> + struct apply { + using LambdaReturnType = SignatureType(FunctionSignature(Lambda)); + using type = + If(Not(IsValidSignature(DecoratedSignature)), ConstructError(NotASignatureErrorTag, DecoratedSignature), + PropagateError( + CheckInjectableType(RemoveAnnotations(SignatureType(DecoratedSignature))), + PropagateError( + CheckInjectableTypeVector( + RemoveAnnotationsFromVector(RemoveAssisted(SignatureArgs(DecoratedSignature)))), + If(IsAbstract(RemoveAnnotations(SignatureType(DecoratedSignature))), + // We error out early in this case. Calling RegisterFactoryHelper would also produce an error, but + // it'd be + // much less user-friendly. + ConstructError(CannotConstructAbstractClassErrorTag, + RemoveAnnotations(SignatureType(DecoratedSignature))), + If(Not(Or(IsEmpty(Lambda), IsValidSignature(Lambda))), + ConstructError(LambdaWithCapturesErrorTag, Lambda), + If(Not(Or(IsTriviallyCopyable(Lambda), IsValidSignature(Lambda))), + ConstructError(NonTriviallyCopyableLambdaErrorTag, Lambda), + If(And(IsUniquePtr(LambdaReturnType), + And(IsAbstract(RemoveUniquePtr(LambdaReturnType)), + Not(HasVirtualDestructor(RemoveUniquePtr(LambdaReturnType))))), + ConstructError(RegisterFactoryForUniquePtrOfAbstractClassWithNoVirtualDestructorErrorTag, + RemoveUniquePtr(LambdaReturnType)), + RegisterFactoryHelper( + Comp, DecoratedSignature, Lambda, + InjectedSignatureForAssistedFactory(DecoratedSignature), + RequiredLambdaSignatureForAssistedFactory(DecoratedSignature), + RemoveAssisted(SignatureArgs(DecoratedSignature)), + RemoveAnnotationsFromVector(RemoveAssisted(SignatureArgs(DecoratedSignature))), + GenerateIntSequence( + VectorSize(RequiredLambdaArgsForAssistedFactory(DecoratedSignature))))))))))); + }; +}; + +struct PostProcessRegisterConstructor; + +template <typename AnnotatedSignature, typename OptionalAnnotatedI> +struct PostProcessRegisterConstructorHelper; + +template <typename AnnotatedSignature, typename AnnotatedI> +struct PostProcessRegisterConstructorHelper; + +template <typename AnnotatedSignature, typename AnnotatedI> +struct PostProcessRegisterConstructorHelper<AnnotatedSignature, Type<AnnotatedI>> { + inline void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + InjectorStorage::createComponentStorageEntryForCompressedConstructor<AnnotatedSignature, AnnotatedI>()); + entries.push_back(InjectorStorage::createComponentStorageEntryForConstructor<AnnotatedSignature>()); + } + std::size_t numEntries() { + return 2; + } +}; + +template <typename AnnotatedSignature> +struct PostProcessRegisterConstructorHelper<AnnotatedSignature, None> { + inline void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back(InjectorStorage::createComponentStorageEntryForConstructor<AnnotatedSignature>()); + } + std::size_t numEntries() { + return 1; + } +}; + +struct PostProcessRegisterConstructor { + template <typename Comp, typename AnnotatedSignature> + struct apply { + struct type { + using AnnotatedC = NormalizeType(SignatureType(AnnotatedSignature)); + using Result = Comp; + using Helper = + PostProcessRegisterConstructorHelper<UnwrapType<AnnotatedSignature>, + Eval<FindValueInMap(typename Comp::InterfaceBindings, AnnotatedC)>>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + Helper()(entries); + } + std::size_t numEntries() { + return Helper().numEntries(); + } + }; + }; +}; + +struct PreProcessRegisterConstructor { + template <typename Comp, typename AnnotatedSignature> + struct apply { + using Signature = RemoveAnnotationsFromSignature(AnnotatedSignature); + using C = SignatureType(Signature); + using Args = SignatureArgs(Signature); + using AnnotatedT = SignatureType(AnnotatedSignature); + using AnnotatedArgs = SignatureArgs(AnnotatedSignature); + using AnnotatedC = NormalizeType(AnnotatedT); + using CDeps = NormalizeTypeVector(AnnotatedArgs); + using CNonConstDeps = NormalizedNonConstTypesIn(AnnotatedArgs); + using R = AddProvidedType(Comp, AnnotatedC, Bool<true>, CDeps, CNonConstDeps); + using type = If( + Not(IsValidSignature(AnnotatedSignature)), ConstructError(NotASignatureErrorTag, AnnotatedSignature), + PropagateError(CheckInjectableType(RemoveAnnotations(C)), + PropagateError(CheckInjectableTypeVector(RemoveAnnotationsFromVector(Args)), + If(IsAbstract(RemoveAnnotations(SignatureType(AnnotatedSignature))), + ConstructError(CannotConstructAbstractClassErrorTag, + RemoveAnnotations(SignatureType(AnnotatedSignature))), + If(Not(IsConstructibleWithVector(C, Args)), + ConstructError(NoConstructorMatchingInjectSignatureErrorTag, C, Signature), + PropagateError(R, ComponentFunctorIdentity(R))))))); + }; +}; + +struct DeferredRegisterConstructor { + template <typename Comp, typename AnnotatedSignature> + struct apply { + using Comp1 = AddDeferredBinding(Comp, ComponentFunctor(PostProcessRegisterConstructor, AnnotatedSignature)); + using type = PreProcessRegisterConstructor(Comp1, AnnotatedSignature); + }; +}; + +struct RegisterInstance { + template <typename Comp, typename AnnotatedC, typename C, typename IsNonConst> + struct apply { + using R = AddProvidedType(Comp, AnnotatedC, IsNonConst, Vector<>, Vector<>); + struct Op { + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>&) {} + std::size_t numEntries() { + return 0; + } + }; + using type = PropagateError( + CheckNormalizedTypes(ConsVector(RemoveAnnotations(AnnotatedC))), + PropagateError( + CheckNormalizedTypes(ConsVector(C)), + If(Not(IsSame(C, NormalizeType(C))), ConstructError(NonClassTypeErrorTag, C, NormalizeUntilStable(C)), + If(Not(IsSame(RemoveAnnotations(AnnotatedC), NormalizeType(RemoveAnnotations(AnnotatedC)))), + ConstructError(NonClassTypeErrorTag, RemoveAnnotations(AnnotatedC), + NormalizeUntilStable(RemoveAnnotations(C))), + // The IsSame check is not redundant because IsBaseOf returns false for non-class types (e.g. int). + If(Not(Or(IsSame(RemoveAnnotations(AnnotatedC), C), IsBaseOf(RemoveAnnotations(AnnotatedC), C))), + ConstructError(TypeMismatchInBindInstanceErrorTag, RemoveAnnotations(AnnotatedC), C), + PropagateError(R, Op)))))); + }; +}; + +struct RegisterConstructorAsValueFactory { + template <typename Comp, typename DecoratedSignature, + typename RequiredSignature = Eval<RequiredLambdaSignatureForAssistedFactory(DecoratedSignature)>> + struct apply; + + template <typename Comp, typename DecoratedSignature, typename NakedT, typename... NakedArgs> + struct apply<Comp, DecoratedSignature, Type<NakedT(NakedArgs...)>> { + using RequiredSignature = Type<NakedT(NakedArgs...)>; + using Op1 = RegisterFactory(Comp, DecoratedSignature, RequiredSignature); + struct Op { + using Result = Eval<GetResult(Op1)>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + auto provider = [](NakedArgs... args) { return NakedT(std::forward<NakedArgs>(args)...); }; + using RealOp = RegisterFactory(Comp, DecoratedSignature, Type<decltype(provider)>); + FruitStaticAssert(IsSame(GetResult(Op1), GetResult(RealOp))); + Eval<RealOp>()(entries); + } + std::size_t numEntries() { +#ifdef FRUIT_EXTRA_DEBUG + auto provider = [](NakedArgs... args) { return NakedT(std::forward<NakedArgs>(args)...); }; + using RealOp = RegisterFactory(Comp, DecoratedSignature, Type<decltype(provider)>); + FruitAssert(Eval<Op1>().numEntries() == Eval<RealOp>().numEntries()); +#endif + return Eval<Op1>().numEntries(); + } + }; + using type = PropagateError(Op1, Op); + }; +}; + +struct RegisterConstructorAsUniquePtrFactory { + template <typename Comp, typename DecoratedSignature, + typename RequiredSignature = Eval<RequiredLambdaSignatureForAssistedFactory(DecoratedSignature)>> + struct apply; + + template <typename Comp, typename DecoratedSignature, typename NakedT, typename... NakedArgs> + struct apply<Comp, DecoratedSignature, Type<std::unique_ptr<NakedT>(NakedArgs...)>> { + using RequiredSignature = Type<std::unique_ptr<NakedT>(NakedArgs...)>; + using Op1 = RegisterFactory(Comp, DecoratedSignature, RequiredSignature); + struct Op { + using Result = Eval<GetResult(Op1)>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + auto provider = [](NakedArgs... args) { + return std::unique_ptr<NakedT>(new NakedT(std::forward<NakedArgs>(args)...)); + }; + using RealOp = RegisterFactory(Comp, DecoratedSignature, Type<decltype(provider)>); + FruitStaticAssert(IsSame(GetResult(Op1), GetResult(RealOp))); + Eval<RealOp>()(entries); + }; + std::size_t numEntries() { +#ifdef FRUIT_EXTRA_DEBUG + auto provider = [](NakedArgs... args) { + return std::unique_ptr<NakedT>(new NakedT(std::forward<NakedArgs>(args)...)); + }; + using RealOp = RegisterFactory(Comp, DecoratedSignature, Type<decltype(provider)>); + FruitAssert(Eval<Op1>().numEntries() == Eval<RealOp>().numEntries()); +#endif + return Eval<Op1>().numEntries(); + } + }; + + using type = PropagateError(Op1, Op); + }; +}; + +struct InstallComponent { + template <typename Comp, typename OtherComp> + struct apply { + using new_RsSuperset = SetUnion(typename OtherComp::RsSuperset, typename Comp::RsSuperset); + using new_Ps = SetUncheckedUnion(typename OtherComp::Ps, typename Comp::Ps); + using new_NonConstRsPs = SetUnion(typename OtherComp::NonConstRsPs, typename Comp::NonConstRsPs); +#ifndef FRUIT_NO_LOOP_CHECK + using new_Deps = ConcatVectors(typename OtherComp::Deps, typename Comp::Deps); +#endif + FruitStaticAssert(IsSame(typename OtherComp::InterfaceBindings, Vector<>)); + using new_InterfaceBindings = typename Comp::InterfaceBindings; + + FruitStaticAssert(IsSame(typename OtherComp::DeferredBindingFunctors, EmptyList)); + using new_DeferredBindingFunctors = typename Comp::DeferredBindingFunctors; + + using R = ConsComp(new_RsSuperset, new_Ps, new_NonConstRsPs, +#ifndef FRUIT_NO_LOOP_CHECK + new_Deps, +#endif + new_InterfaceBindings, new_DeferredBindingFunctors); + struct Op { + using Result = Eval<R>; + void operator()(FixedSizeVector<ComponentStorageEntry>&) {} + std::size_t numEntries() { + return 0; + } + }; + using InterfacePs = VectorToSetUnchecked(GetMapKeys(typename Comp::InterfaceBindings)); + using AllPs = SetUncheckedUnion(InterfacePs, typename Comp::Ps); + using DuplicateTypes = SetIntersection(typename OtherComp::Ps, AllPs); + using CompConstPs = SetDifference(typename Comp::Ps, typename Comp::NonConstRsPs); + using CompRs = SetDifference(typename Comp::RsSuperset, typename Comp::Ps); + using CompNonConstRs = SetIntersection(CompRs, typename Comp::NonConstRsPs); + + using OtherCompConstPs = SetDifference(typename OtherComp::Ps, typename OtherComp::NonConstRsPs); + using OtherCompRs = SetDifference(typename OtherComp::RsSuperset, typename OtherComp::Ps); + using OtherCompNonConstRs = SetIntersection(OtherCompRs, typename OtherComp::NonConstRsPs); + + using type = If(Not(IsDisjoint(typename OtherComp::Ps, AllPs)), + ConstructErrorWithArgVector(DuplicateTypesInComponentErrorTag, SetToVector(DuplicateTypes)), + If(Not(IsDisjoint(CompConstPs, OtherCompNonConstRs)), + ConstructError(NonConstBindingRequiredButConstBindingProvidedErrorTag, + GetArbitrarySetElement(SetIntersection(CompConstPs, OtherCompNonConstRs))), + If(Not(IsDisjoint(CompNonConstRs, OtherCompConstPs)), + ConstructError(NonConstBindingRequiredButConstBindingProvidedErrorTag, + GetArbitrarySetElement(SetIntersection(CompNonConstRs, OtherCompConstPs))), + Op))); + }; +}; + +struct InstallComponentHelper { + template <typename Comp, typename... OtherCompParams> + struct apply { + using OtherComp = ConstructComponentImpl(OtherCompParams...); + using type = InstallComponent(Comp, OtherComp); + }; +}; + +// CatchAll(PropagateError(Expr, Bool<false>), IsErrorExceptionHandler) evaluates to Bool<true> if Expr throws an error, +// and Bool<false> otherwise. +struct IsErrorExceptionHandler { + template <typename E> + struct apply { + using type = Bool<true>; + }; +}; + +struct ConvertComponent { + template <typename SourceComp, typename DestComp> + struct apply { + using SourcePs = typename SourceComp::Ps; + using DestPs = typename DestComp::Ps; + using SourceRs = SetDifference(typename SourceComp::RsSuperset, typename SourceComp::Ps); + using DestRs = SetDifference(typename DestComp::RsSuperset, typename DestComp::Ps); + using NonConstSourceRs = SetIntersection(SourceRs, typename SourceComp::NonConstRsPs); + using NonConstDestPs = SetIntersection(DestPs, typename DestComp::NonConstRsPs); + using NonConstDestRs = SetIntersection(DestRs, typename DestComp::NonConstRsPs); + + using ConstSourcePs = SetDifference(SourcePs, typename SourceComp::NonConstRsPs); + using ConstDestRs = SetDifference(DestRs, typename DestComp::NonConstRsPs); + + // We need to register: + // * All the types provided by the new component + // * All the types required by the old component + // except: + // * The ones already provided by the old component (if they have the right constness). + // * The ones required by the new one (if they have the right constness). + using ToRegister = SetUnion( + // The types that we must provide and aren't currently provided + SetDifference(SetUnion(DestPs, SourceRs), SetUnion(DestRs, SourcePs)), + // And the ones that are currently provided as const but that we need to provide as non-const + SetIntersection(SetUnion(NonConstDestPs, NonConstSourceRs), SetUnion(ConstDestRs, ConstSourcePs))); + using NonConstTypesToRegister = SetIntersection(ToRegister, SetUnion(typename SourceComp::NonConstRsPs, + typename DestComp::NonConstRsPs)); + using type = EnsureProvidedTypes(SourceComp, DestRs, NonConstDestRs, SetToVector(ToRegister), + NonConstTypesToRegister); + +// Not needed, just double-checking. +// Uses FruitStaticAssert instead of FruitDelegateCheck so that it's checked only in debug mode. +#ifdef FRUIT_EXTRA_DEBUG + FruitDelegateCheck( + If(CatchAll(PropagateError(type, PropagateError(Id<GetResult(type)>, Bool<false>)), IsErrorExceptionHandler), + // We're going to return an error soon anyway, we don't want to interfere by reporting this one. + None, CheckComponentEntails(GetResult(type), DestComp))); +#endif // FRUIT_EXTRA_DEBUG + }; +}; + +struct ProcessDeferredBindings { + template <typename Comp> + struct apply; + + template <typename RsSupersetParam, typename PsParam, typename NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + typename DepsParam, +#endif + typename InterfaceBindingsParam, typename DeferredBindingFunctors> + struct apply<Comp<RsSupersetParam, PsParam, NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + DepsParam, +#endif + InterfaceBindingsParam, DeferredBindingFunctors>> { + // Comp1 is the same as Comp, but without the DeferredBindingFunctors. + using Comp1 = ConsComp(RsSupersetParam, PsParam, NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + DepsParam, +#endif + InterfaceBindingsParam, EmptyList); + using type = Call(FoldList(DeferredBindingFunctors, Compose2ComponentFunctors, ComponentFunctorIdentity), Comp1); + }; +}; + +template <typename AnnotatedCFunctor, typename AnnotatedCUniquePtrFunctor> +struct AutoRegisterFactoryHelperErrorHandler { + template <typename E> + struct apply { + using type = E; + }; + + template <typename T> + struct apply<Error<NoBindingFoundErrorTag, T>> { + using type = If(IsSame(Type<T>, AnnotatedCFunctor), ConstructNoBindingFoundError(AnnotatedCUniquePtrFunctor), + ConstructError(NoBindingFoundErrorTag, Type<T>)); + }; + + template <typename T1, typename T2> + struct apply<Error<NoBindingFoundForAbstractClassErrorTag, T1, T2>> { + using type = If(IsSame(Type<T1>, AnnotatedCFunctor), ConstructNoBindingFoundError(AnnotatedCUniquePtrFunctor), + ConstructError(NoBindingFoundForAbstractClassErrorTag, Type<T1>, Type<T2>)); + }; +}; + +struct AutoRegisterFactoryHelper { + + // General case, no way to bind it. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename InterfaceBinding, + typename has_inject_annotation, typename is_abstract, typename C, typename AnnotatedSignature, + typename... Args> + struct apply { + using AnnotatedC = SignatureType(AnnotatedSignature); + using CFunctor = ConsStdFunction(RemoveAnnotationsFromSignature(AnnotatedSignature)); + using AnnotatedCFunctor = CopyAnnotation(AnnotatedC, CFunctor); + using type = If(IsAbstract(C), ConstructError(NoBindingFoundForAbstractClassErrorTag, AnnotatedCFunctor, C), + ConstructError(NoBindingFoundErrorTag, AnnotatedCFunctor)); + }; + + // No way to bind it (we need this specialization too to ensure that the specialization below + // is not chosen for AnnotatedC=None). + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename unused1, + typename unused2, typename NakedI, typename AnnotatedSignature, typename... Args> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, None, unused1, unused2, + Type<std::unique_ptr<NakedI>>, AnnotatedSignature, Args...> { + using AnnotatedC = SignatureType(AnnotatedSignature); + using CFunctor = ConsStdFunction(RemoveAnnotationsFromSignature(AnnotatedSignature)); + using AnnotatedCFunctor = CopyAnnotation(AnnotatedC, CFunctor); + using type = If(IsAbstract(Type<NakedI>), + ConstructError(NoBindingFoundForAbstractClassErrorTag, AnnotatedCFunctor, Type<NakedI>), + ConstructError(NoBindingFoundErrorTag, AnnotatedCFunctor)); + }; + + // AnnotatedI has an interface binding, use it and look for a factory that returns the type that AnnotatedI is bound + // to. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename AnnotatedC, + typename unused1, typename unused2, typename NakedI, typename AnnotatedSignature, typename... Args> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, AnnotatedC, unused1, unused2, + Type<std::unique_ptr<NakedI>>, AnnotatedSignature, Args...> { + using I = Type<NakedI>; + using AnnotatedI = CopyAnnotation(SignatureType(AnnotatedSignature), I); + using C = RemoveAnnotations(AnnotatedC); + using IFunctor = ConsStdFunction(ConsSignature(ConsUniquePtr(I), Args...)); + using CFunctor = ConsStdFunction(ConsSignature(ConsUniquePtr(C), Args...)); + using AnnotatedIFunctor = CopyAnnotation(AnnotatedI, IFunctor); + using AnnotatedCFunctor = CopyAnnotation(AnnotatedC, CFunctor); + + using ProvidedSignature = ConsSignature(AnnotatedIFunctor, + CopyAnnotation(AnnotatedC, ConsConstReference(CFunctor))); + using LambdaSignature = ConsSignature(IFunctor, ConsConstReference(CFunctor)); + + using F1 = ComponentFunctor(EnsureProvidedType, TargetRequirements, TargetNonConstRequirements, AnnotatedCFunctor, + Bool<false>); + using F2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, LambdaSignature); + using F3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, LambdaSignature); + using R = Call(ComposeFunctors(F1, F2, F3), Comp); + struct Op { + using Result = Eval<GetResult(R)>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + using NakedC = UnwrapType<Eval<C>>; + auto provider = [](const UnwrapType<Eval<CFunctor>>& fun) { + return UnwrapType<Eval<IFunctor>>([=](typename TypeUnwrapper<Args>::type... args) { + NakedC* c = fun(args...).release(); + NakedI* i = static_cast<NakedI*>(c); + return std::unique_ptr<NakedI>(i); + }); + }; + using RealF2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealF3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealOp = Call(ComposeFunctors(F1, RealF2, RealF3), Comp); + FruitStaticAssert(IsSame(GetResult(RealOp), GetResult(R))); + Eval<RealOp>()(entries); + } + std::size_t numEntries() { +#ifdef FRUIT_EXTRA_DEBUG + using NakedC = UnwrapType<Eval<C>>; + auto provider = [](const UnwrapType<Eval<CFunctor>>& fun) { + return UnwrapType<Eval<IFunctor>>([=](typename TypeUnwrapper<Args>::type... args) { + NakedC* c = fun(args...).release(); + NakedI* i = static_cast<NakedI*>(c); + return std::unique_ptr<NakedI>(i); + }); + }; + using RealF2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealF3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealOp = Call(ComposeFunctors(F1, RealF2, RealF3), Comp); + FruitAssert(Eval<R>().numEntries() == Eval<RealOp>().numEntries()); +#endif + return Eval<R>().numEntries(); + } + }; + using type = PropagateError(R, If(Not(HasVirtualDestructor(I)), + ConstructError(FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorErrorTag, + IFunctor, CFunctor), + Op)); + }; + + // C doesn't have an interface binding as interface, nor an INJECT annotation, and is not an abstract class. + // Bind std::function<unique_ptr<C>(Args...)> to std::function<C(Args...)> (possibly with annotations). + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename NakedC, + typename AnnotatedSignature, typename... Args> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, None, Bool<false>, Bool<false>, + Type<std::unique_ptr<NakedC>>, AnnotatedSignature, Args...> { + using C = Type<NakedC>; + using CFunctor = ConsStdFunction(ConsSignature(C, Args...)); + using CUniquePtrFunctor = ConsStdFunction(ConsSignature(ConsUniquePtr(C), Args...)); + using AnnotatedCUniquePtr = SignatureType(AnnotatedSignature); + using AnnotatedC = CopyAnnotation(AnnotatedCUniquePtr, C); + using AnnotatedCFunctor = CopyAnnotation(AnnotatedCUniquePtr, CFunctor); + using AnnotatedCUniquePtrFunctor = CopyAnnotation(AnnotatedCUniquePtr, CUniquePtrFunctor); + using AnnotatedCFunctorRef = CopyAnnotation(AnnotatedCUniquePtr, ConsConstReference(CFunctor)); + + using ProvidedSignature = ConsSignature(AnnotatedCUniquePtrFunctor, AnnotatedCFunctorRef); + using LambdaSignature = ConsSignature(CUniquePtrFunctor, ConsConstReference(CFunctor)); + + using F1 = ComponentFunctor(EnsureProvidedType, TargetRequirements, TargetNonConstRequirements, AnnotatedCFunctor, + Bool<false>); + using F2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, LambdaSignature); + using F3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, LambdaSignature); + using R = Call(ComposeFunctors(F1, F2, F3), Comp); + struct Op { + using Result = Eval<GetResult(R)>; + void operator()(FixedSizeVector<ComponentStorageEntry>& entries) { + auto provider = [](const UnwrapType<Eval<CFunctor>>& fun) { + return UnwrapType<Eval<CUniquePtrFunctor>>([=](typename TypeUnwrapper<Args>::type... args) { + NakedC* c = new NakedC(fun(args...)); + return std::unique_ptr<NakedC>(c); + }); + }; + using RealF2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealF3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealOp = Call(ComposeFunctors(F1, RealF2, RealF3), Comp); + FruitStaticAssert(IsSame(GetResult(RealOp), GetResult(R))); + Eval<RealOp>()(entries); + } + std::size_t numEntries() { +#ifdef FRUIT_EXTRA_DEBUG + auto provider = [](const UnwrapType<Eval<CFunctor>>& fun) { + return UnwrapType<Eval<CUniquePtrFunctor>>([=](typename TypeUnwrapper<Args>::type... args) { + NakedC* c = new NakedC(fun(args...)); + return std::unique_ptr<NakedC>(c); + }); + }; + using RealF2 = ComponentFunctor(PreProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealF3 = ComponentFunctor(PostProcessRegisterProvider, ProvidedSignature, Type<decltype(provider)>); + using RealOp = Call(ComposeFunctors(F1, RealF2, RealF3), Comp); + FruitAssert(Eval<R>().numEntries() == Eval<RealOp>().numEntries()); +#endif + return Eval<R>().numEntries(); + } + }; + + using ErrorHandler = + AutoRegisterFactoryHelperErrorHandler<Eval<AnnotatedCFunctor>, Eval<AnnotatedCUniquePtrFunctor>>; + + // If we are about to report a NoBindingFound/NoBindingFoundForAbstractClass error for AnnotatedCFunctor, + // report one for std::function<std::unique_ptr<C>(Args...)> instead, + // otherwise we'd report an error about a type that the user doesn't expect. + using type = PropagateError(Catch(Catch(R, NoBindingFoundErrorTag, ErrorHandler), + NoBindingFoundForAbstractClassErrorTag, ErrorHandler), + Op); + }; + + // C has an Inject typedef, use it. unique_ptr case. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename unused, + typename NakedC, typename AnnotatedSignature, typename... Args> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, None, Bool<true>, unused, + Type<std::unique_ptr<NakedC>>, AnnotatedSignature, Args...> { + using AnnotatedCUniquePtr = SignatureType(AnnotatedSignature); + using AnnotatedC = CopyAnnotation(AnnotatedCUniquePtr, RemoveUniquePtr(RemoveAnnotations(AnnotatedCUniquePtr))); + using DecoratedSignatureReturningValue = GetInjectAnnotation(AnnotatedC); + using DecoratedSignature = ConsSignatureWithVector(AnnotatedCUniquePtr, + SignatureArgs(DecoratedSignatureReturningValue)); + using DecoratedSignatureArgs = SignatureArgs(DecoratedSignature); + using ActualSignatureInInjectionTypedef = ConsSignatureWithVector(SignatureType(DecoratedSignature), + RemoveNonAssisted(DecoratedSignatureArgs)); + using NonAssistedArgs = RemoveAssisted(DecoratedSignatureArgs); + + using F1 = ComponentFunctor(RegisterConstructorAsUniquePtrFactory, DecoratedSignature); + using F2 = ComponentFunctor(EnsureProvidedTypes, TargetRequirements, TargetNonConstRequirements, + NormalizeTypeVector(NonAssistedArgs), NormalizedNonConstTypesIn(NonAssistedArgs)); + + using type = If(Not(IsSame(AnnotatedSignature, ActualSignatureInInjectionTypedef)), + ConstructError(FunctorSignatureDoesNotMatchErrorTag, AnnotatedSignature, + ActualSignatureInInjectionTypedef), + Call(ComposeFunctors(F1, F2), Comp)); + }; + + // C has an Inject typedef, use it. Value (not unique_ptr) case. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename unused, + typename NakedC, typename AnnotatedSignature, typename... Args> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, None, Bool<true>, unused, Type<NakedC>, + AnnotatedSignature, Args...> { + using AnnotatedC = SignatureType(AnnotatedSignature); + using DecoratedSignature = GetInjectAnnotation(AnnotatedC); + using DecoratedSignatureArgs = SignatureArgs(DecoratedSignature); + using ActualSignatureInInjectionTypedef = ConsSignatureWithVector(SignatureType(DecoratedSignature), + RemoveNonAssisted(DecoratedSignatureArgs)); + using NonAssistedArgs = RemoveAssisted(DecoratedSignatureArgs); + + using F1 = ComponentFunctor(RegisterConstructorAsValueFactory, DecoratedSignature); + using F2 = ComponentFunctor(EnsureProvidedTypes, TargetRequirements, TargetNonConstRequirements, + NormalizeTypeVector(NonAssistedArgs), NormalizedNonConstTypesIn(NonAssistedArgs)); + + using type = If(Not(IsSame(AnnotatedSignature, ActualSignatureInInjectionTypedef)), + ConstructError(FunctorSignatureDoesNotMatchErrorTag, AnnotatedSignature, + ActualSignatureInInjectionTypedef), + Call(ComposeFunctors(F1, F2), Comp)); + }; +}; + +struct AutoRegister { + // The types in TargetRequirements will not be auto-registered. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename AnnotatedC> + struct apply; + + // Tries to register C by looking for a typedef called Inject inside C. + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename AnnotatedC> + struct apply { + using CHasInjectAnnotation = HasInjectAnnotation(RemoveAnnotations(AnnotatedC)); + using Inject = GetInjectAnnotation(AnnotatedC); + using CRequirements = NormalizeTypeVector(SignatureArgs(Inject)); + using CNonConstRequirements = NormalizedNonConstTypesIn(SignatureArgs(Inject)); + using F = ComposeFunctors(ComponentFunctor(PreProcessRegisterConstructor, Inject), + ComponentFunctor(PostProcessRegisterConstructor, Inject), + ComponentFunctor(EnsureProvidedTypes, TargetRequirements, TargetNonConstRequirements, + CRequirements, CNonConstRequirements)); + using type = If(CHasInjectAnnotation, Call(F, Comp), ConstructNoBindingFoundError(AnnotatedC)); + }; + + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename NakedC, + typename... NakedArgs> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, Type<std::function<NakedC(NakedArgs...)>>> { + using type = AutoRegisterFactoryHelper(Comp, TargetRequirements, TargetNonConstRequirements, + FindInMap(typename Comp::InterfaceBindings, Type<NakedC>), + HasInjectAnnotation(Type<NakedC>), IsAbstract(Type<NakedC>), Type<NakedC>, + Type<NakedC(NakedArgs...)>, Id<RemoveAnnotations(Type<NakedArgs>)>...); + }; + + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename NakedC, + typename... NakedArgs> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, + Type<std::function<std::unique_ptr<NakedC>(NakedArgs...)>>> { + using type = AutoRegisterFactoryHelper(Comp, TargetRequirements, TargetNonConstRequirements, + FindInMap(typename Comp::InterfaceBindings, Type<NakedC>), + HasInjectAnnotation(Type<NakedC>), IsAbstract(Type<NakedC>), + Type<std::unique_ptr<NakedC>>, Type<std::unique_ptr<NakedC>(NakedArgs...)>, + Id<RemoveAnnotations(Type<NakedArgs>)>...); + }; + + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename Annotation, + typename NakedC, typename... NakedArgs> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, + Type<fruit::Annotated<Annotation, std::function<NakedC(NakedArgs...)>>>> { + using type = AutoRegisterFactoryHelper(Comp, TargetRequirements, TargetNonConstRequirements, + FindInMap(typename Comp::InterfaceBindings, + Type<fruit::Annotated<Annotation, NakedC>>), + HasInjectAnnotation(Type<NakedC>), IsAbstract(Type<NakedC>), Type<NakedC>, + Type<fruit::Annotated<Annotation, NakedC>(NakedArgs...)>, + Id<RemoveAnnotations(Type<NakedArgs>)>...); + }; + + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename Annotation, + typename NakedC, typename... NakedArgs> + struct apply<Comp, TargetRequirements, TargetNonConstRequirements, + Type<fruit::Annotated<Annotation, std::function<std::unique_ptr<NakedC>(NakedArgs...)>>>> { + using type = AutoRegisterFactoryHelper(Comp, TargetRequirements, TargetNonConstRequirements, + FindInMap(typename Comp::InterfaceBindings, + Type<fruit::Annotated<Annotation, NakedC>>), + HasInjectAnnotation(Type<NakedC>), IsAbstract(Type<NakedC>), + Type<std::unique_ptr<NakedC>>, + Type<fruit::Annotated<Annotation, std::unique_ptr<NakedC>>(NakedArgs...)>, + Id<RemoveAnnotations(Type<NakedArgs>)>...); + }; +}; + +template <typename AnnotatedT> +struct EnsureProvidedTypeErrorHandler { + template <typename E> + struct apply { + using type = E; + }; + + template <typename T> + struct apply<Error<NoBindingFoundErrorTag, T>> { + using type = If(IsSame(Type<T>, AnnotatedT), + ConstructError(ConstBindingDeclaredAsRequiredButNonConstBindingRequiredErrorTag, AnnotatedT), + ConstructError(NoBindingFoundErrorTag, Type<T>)); + }; + + template <typename T1, typename T2> + struct apply<Error<NoBindingFoundForAbstractClassErrorTag, T1, T2>> { + using type = If(IsSame(Type<T1>, AnnotatedT), + ConstructError(ConstBindingDeclaredAsRequiredButNonConstBindingRequiredErrorTag, AnnotatedT), + ConstructError(NoBindingFoundForAbstractClassErrorTag, Type<T1>, Type<T2>)); + }; +}; + +struct EnsureProvidedType { + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename AnnotatedT, + typename NonConstBindingRequired> + struct apply { + using AnnotatedC = NormalizeType(AnnotatedT); + using AnnotatedCImpl = FindInMap(typename Comp::InterfaceBindings, AnnotatedC); + using AutoRegisterResult = AutoRegister(Comp, TargetRequirements, TargetNonConstRequirements, AnnotatedC); + using ErrorHandler = EnsureProvidedTypeErrorHandler<AnnotatedT>; + using type = If( + IsInSet(AnnotatedC, typename Comp::Ps), + If(And(NonConstBindingRequired, Not(IsInSet(AnnotatedC, typename Comp::NonConstRsPs))), + ConstructError(NonConstBindingRequiredButConstBindingProvidedErrorTag, AnnotatedC), + ComponentFunctorIdentity(Comp)), + If(And(IsInSet(AnnotatedC, TargetRequirements), + Or(Not(NonConstBindingRequired), IsInSet(AnnotatedC, TargetNonConstRequirements))), + // The type is already in the target requirements with the desired constness, nothing to do. + ComponentFunctorIdentity(Comp), + If(Not(IsNone(AnnotatedCImpl)), + // Has an interface binding. + Call(ComposeFunctors(ComponentFunctor(ProcessInterfaceBinding, AnnotatedC, AnnotatedCImpl, + NonConstBindingRequired), + ComponentFunctor(EnsureProvidedType, TargetRequirements, TargetNonConstRequirements, + AnnotatedCImpl, NonConstBindingRequired)), + Comp), + // If we are about to report a NoBindingFound/NoBindingFoundForAbstractClass error for AnnotatedT and the + // target + // component has a Required<const T>, we can report a more specific error (rather than the usual + // "binding not found"). + If(And(NonConstBindingRequired, IsInSet(AnnotatedC, TargetRequirements)), + Catch(Catch(AutoRegisterResult, NoBindingFoundErrorTag, ErrorHandler), + NoBindingFoundForAbstractClassErrorTag, ErrorHandler), + AutoRegisterResult)))); + }; +}; + +struct EnsureProvidedTypes { + template <typename Comp, typename TargetRequirements, typename TargetNonConstRequirements, typename TypesToProvide, + typename NonConstTypesToProvide> + struct apply { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = Compose2ComponentFunctors(ComponentFunctor(EnsureProvidedType, TargetRequirements, + TargetNonConstRequirements, T, + IsInSet(T, NonConstTypesToProvide)), + CurrentResult); + }; + }; + + using type = Call(FoldVector(TypesToProvide, Helper, ComponentFunctorIdentity), Comp); + }; +}; + +struct ProcessBinding { + template <typename Binding> + struct apply; + + template <typename I, typename C> + struct apply<fruit::impl::Bind<I, C>> { + using type = ComponentFunctor(AddDeferredInterfaceBinding, Type<I>, Type<C>); + }; + + template <typename Signature> + struct apply<fruit::impl::RegisterConstructor<Signature>> { + using type = ComponentFunctor(DeferredRegisterConstructor, Type<Signature>); + }; + + template <typename AnnotatedC, typename C> + struct apply<fruit::impl::BindInstance<AnnotatedC, C>> { + using type = ComponentFunctor(RegisterInstance, Type<AnnotatedC>, Type<C>, Bool<true>); + }; + + template <typename AnnotatedC, typename C> + struct apply<fruit::impl::BindConstInstance<AnnotatedC, C>> { + using type = ComponentFunctor(RegisterInstance, Type<AnnotatedC>, Type<C>, Bool<false>); + }; + + template <typename Lambda> + struct apply<fruit::impl::RegisterProvider<Lambda>> { + using type = ComponentFunctor(DeferredRegisterProvider, Type<Lambda>); + }; + + template <typename AnnotatedSignature, typename Lambda> + struct apply<fruit::impl::RegisterProvider<AnnotatedSignature, Lambda>> { + using type = ComponentFunctor(DeferredRegisterProviderWithAnnotations, Type<AnnotatedSignature>, Type<Lambda>); + }; + + template <typename AnnotatedC> + struct apply<fruit::impl::AddInstanceMultibinding<AnnotatedC>> { + using type = ComponentFunctorIdentity; + }; + + template <typename AnnotatedC> + struct apply<fruit::impl::AddInstanceVectorMultibindings<AnnotatedC>> { + using type = ComponentFunctorIdentity; + }; + + template <typename I, typename C> + struct apply<fruit::impl::AddMultibinding<I, C>> { + using type = ComponentFunctor(AddInterfaceMultibinding, Type<I>, Type<C>); + }; + + template <typename Lambda> + struct apply<fruit::impl::AddMultibindingProvider<Lambda>> { + using type = ComponentFunctor(RegisterMultibindingProvider, Type<Lambda>); + }; + + template <typename AnnotatedSignature, typename Lambda> + struct apply<fruit::impl::AddMultibindingProvider<AnnotatedSignature, Lambda>> { + using type = ComponentFunctor(RegisterMultibindingProviderWithAnnotations, Type<AnnotatedSignature>, Type<Lambda>); + }; + + template <typename DecoratedSignature, typename Lambda> + struct apply<fruit::impl::RegisterFactory<DecoratedSignature, Lambda>> { + using type = ComponentFunctor(RegisterFactory, Type<DecoratedSignature>, Type<Lambda>); + }; + + template <typename... Params, typename... Args> + struct apply<fruit::impl::InstallComponent<fruit::Component<Params...>(Args...)>> { + using type = ComponentFunctor(InstallComponentHelper, Type<Params>...); + }; + + template <typename GetReplacedComponent, typename GetReplacementComponent> + struct apply<fruit::impl::ReplaceComponent<GetReplacedComponent, GetReplacementComponent>> { + using type = ComponentFunctorIdentity; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_COMPONENT_FUNCTORS_DEFN_H diff --git a/include/fruit/impl/component_storage/binding_deps.defn.h b/include/fruit/impl/component_storage/binding_deps.defn.h new file mode 100644 index 0000000..f7e63ef --- /dev/null +++ b/include/fruit/impl/component_storage/binding_deps.defn.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_BINDING_DEPS_DEFN_H +#define FRUIT_BINDING_DEPS_DEFN_H + +#include <fruit/impl/component_storage/binding_deps.h> + +namespace fruit { +namespace impl { + +template <typename L> +struct GetBindingDepsHelper; + +template <typename... Ts> +struct GetBindingDepsHelper<fruit::impl::meta::Vector<fruit::impl::meta::Type<Ts>...>> { + inline const BindingDeps* operator()() { + static const TypeId types[] = {getTypeId<Ts>()..., TypeId{nullptr}}; // LCOV_EXCL_BR_LINE + static const BindingDeps deps = {types, sizeof...(Ts)}; + return &deps; + } +}; + +// We specialize the "no Ts" case to avoid declaring types[] as an array of length 0. +template <> +struct GetBindingDepsHelper<fruit::impl::meta::Vector<>> { + inline const BindingDeps* operator()() { + static const TypeId types[] = {TypeId{nullptr}}; + static const BindingDeps deps = {types, 0}; + return &deps; + } +}; + +template <typename Deps> +inline const BindingDeps* getBindingDeps() { + return GetBindingDepsHelper<Deps>()(); +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_BINDING_DEPS_DEFN_H diff --git a/include/fruit/impl/component_storage/binding_deps.h b/include/fruit/impl/component_storage/binding_deps.h new file mode 100644 index 0000000..5a18a83 --- /dev/null +++ b/include/fruit/impl/component_storage/binding_deps.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_BINDING_DEPS_H +#define FRUIT_BINDING_DEPS_H + +#include <fruit/impl/util/type_info.h> + +namespace fruit { +namespace impl { + +struct BindingDeps { + // A C-style array of deps + const TypeId* deps; + + // The size of the above array. + std::size_t num_deps; +}; + +template <typename Deps> +const BindingDeps* getBindingDeps(); + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/component_storage/binding_deps.defn.h> + +#endif // FRUIT_BINDING_DEPS_H diff --git a/include/fruit/impl/component_storage/component_storage.defn.h b/include/fruit/impl/component_storage/component_storage.defn.h new file mode 100644 index 0000000..c44efb9 --- /dev/null +++ b/include/fruit/impl/component_storage/component_storage.defn.h @@ -0,0 +1,80 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_STORAGE_DEFN_H +#define FRUIT_COMPONENT_STORAGE_DEFN_H + +#include <fruit/impl/component_storage/component_storage.h> +#include <fruit/impl/component_storage/component_storage_entry.h> + +namespace fruit { +namespace impl { + +inline ComponentStorage::ComponentStorage(FixedSizeVector<ComponentStorageEntry>&& entries) + : entries(std::move(entries)) {} + +inline ComponentStorage::ComponentStorage(const ComponentStorage& other) { + *this = other; +} + +inline ComponentStorage::ComponentStorage(ComponentStorage&& other) { + *this = std::move(other); +} + +inline void ComponentStorage::destroy() { + for (ComponentStorageEntry& entry : entries) { + entry.destroy(); + } + entries.clear(); +} + +inline ComponentStorage::~ComponentStorage() { + destroy(); +} + +inline FixedSizeVector<ComponentStorageEntry> ComponentStorage::release() && { + return std::move(entries); +} + +inline std::size_t ComponentStorage::numEntries() const { + return entries.size(); +} + +inline ComponentStorage& ComponentStorage::operator=(const ComponentStorage& other) { + destroy(); + + entries = FixedSizeVector<ComponentStorageEntry>(other.entries.size()); + for (const ComponentStorageEntry& entry : other.entries) { + entries.push_back(entry.copy()); + } + + return *this; +} + +inline ComponentStorage& ComponentStorage::operator=(ComponentStorage&& other) { + entries = std::move(other.entries); + + // We don't want other to have any entries after this operation because we might otherwise end up destroying those + // ComponentStorageEntry objects twice. + other.entries.clear(); + + return *this; +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_COMPONENT_STORAGE_DEFN_H diff --git a/include/fruit/impl/component_storage/component_storage.h b/include/fruit/impl/component_storage/component_storage.h new file mode 100644 index 0000000..45e3f05 --- /dev/null +++ b/include/fruit/impl/component_storage/component_storage.h @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_STORAGE_H +#define FRUIT_COMPONENT_STORAGE_H + +#include <fruit/impl/data_structures/fixed_size_vector.h> +#include <fruit/impl/fruit_internal_forward_decls.h> + +namespace fruit { +namespace impl { + +/** + * A component where all types have to be explicitly registered, and all checks are at runtime. + * Used to implement Component<>, don't use directly. + * This merely stores the ComponentStorageEntry objects. The real processing will be done in NormalizedComponentStorage + * and InjectorStorage. + * + * This class handles the creation of bindings for types of the forms: + * - shared_ptr<C>, [const] C*, [const] C&, C (where C is an atomic type) + * - Annotated<Annotation, T> (with T of the above forms) + * - Injector<T1, ..., Tk> (with T1, ..., Tk of the above forms). + */ +class ComponentStorage { +private: + // The entries for this component storage (potentially including lazy component), *in reverse order*. + FixedSizeVector<ComponentStorageEntry> entries; + + void destroy(); + +public: + ComponentStorage() = default; + ComponentStorage(FixedSizeVector<ComponentStorageEntry>&& entries); + ComponentStorage(const ComponentStorage&); + ComponentStorage(ComponentStorage&&); + + ~ComponentStorage(); + + FixedSizeVector<ComponentStorageEntry> release() &&; + + std::size_t numEntries() const; + + ComponentStorage& operator=(const ComponentStorage&); + ComponentStorage& operator=(ComponentStorage&&); +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/component_storage/component_storage.defn.h> + +#endif // FRUIT_COMPONENT_STORAGE_H diff --git a/include/fruit/impl/component_storage/component_storage_entry.defn.h b/include/fruit/impl/component_storage/component_storage_entry.defn.h new file mode 100644 index 0000000..a9d139c --- /dev/null +++ b/include/fruit/impl/component_storage/component_storage_entry.defn.h @@ -0,0 +1,232 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_STORAGE_ENTRY_DEFN_H +#define FRUIT_COMPONENT_STORAGE_ENTRY_DEFN_H + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <fruit/impl/util/call_with_tuple.h> +#include <fruit/impl/util/hash_codes.h> + +namespace fruit { +namespace impl { + +// We use a custom method instead of a real copy constructor so that all copies are explicit (since copying is a +// fairly expensive operation). +inline ComponentStorageEntry ComponentStorageEntry::copy() const { + FruitAssert(kind != Kind::INVALID); + ComponentStorageEntry result; + switch (kind) { + case Kind::LAZY_COMPONENT_WITH_ARGS: + case Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS: + case Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + result.kind = kind; + result.type_id = type_id; + result.lazy_component_with_args = lazy_component_with_args.copy(); + break; + + default: + result = *this; + } + return result; +} + +// We use a custom method instead of a real destructor, so that we can hold these in a std::vector but still destroy +// them when desired. +inline void ComponentStorageEntry::destroy() const { + FruitAssert(kind != Kind::INVALID); + switch (kind) { + case Kind::LAZY_COMPONENT_WITH_ARGS: + case Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS: + case Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + lazy_component_with_args.destroy(); +#ifdef FRUIT_EXTRA_DEBUG + kind = Kind::INVALID; +#endif + break; + + default: + break; + } +} + +inline ComponentStorageEntry::LazyComponentWithArgs::ComponentInterface::ComponentInterface(erased_fun_t erased_fun) + : erased_fun(erased_fun) {} + +template <typename Component, typename... Args> +class ComponentInterfaceImpl : public ComponentStorageEntry::LazyComponentWithArgs::ComponentInterface { +private: + using ComponentInterface = ComponentStorageEntry::LazyComponentWithArgs::ComponentInterface; + + using fun_t = Component (*)(Args...); + std::tuple<Args...> args_tuple; + +public: + inline ComponentInterfaceImpl(fun_t fun, std::tuple<Args...> args_tuple) + : ComponentInterface(reinterpret_cast<erased_fun_t>(fun)), args_tuple(std::move(args_tuple)) {} + + inline bool + areParamsEqual(const ComponentStorageEntry::LazyComponentWithArgs::ComponentInterface& other) const final { + if (getFunTypeId() != other.getFunTypeId()) { + return false; + } + const auto& casted_other = static_cast<const ComponentInterfaceImpl<Component, Args...>&>(other); + return args_tuple == casted_other.args_tuple; + } + + inline void addBindings(entry_vector_t& entries) const final { + Component component = callWithTuple<Component, Args...>(reinterpret_cast<fun_t>(erased_fun), args_tuple); + FixedSizeVector<ComponentStorageEntry> component_entries = std::move(component.storage).release(); + entries.insert(entries.end(), component_entries.begin(), component_entries.end()); + } + + inline std::size_t hashCode() const final { + std::size_t fun_hash = std::hash<fun_t>()(reinterpret_cast<fun_t>(erased_fun)); + std::size_t args_hash = hashTuple(args_tuple); + return combineHashes(fun_hash, args_hash); + } + + inline ComponentInterface* copy() const final { + return new ComponentInterfaceImpl{reinterpret_cast<fun_t>(erased_fun), args_tuple}; + } + + inline TypeId getFunTypeId() const final { + return fruit::impl::getTypeId<Component (*)(Args...)>(); + } +}; + +template <typename Component, typename... Args> +inline ComponentStorageEntry ComponentStorageEntry::LazyComponentWithArgs::create(Component (*fun)(Args...), + std::tuple<Args...> args_tuple) { + ComponentStorageEntry result; + result.type_id = getTypeId<Component (*)(Args...)>(); + result.kind = ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS; + result.lazy_component_with_args.component = + new ComponentInterfaceImpl<Component, Args...>(fun, std::move(args_tuple)); + return result; +} + +template <typename Component, typename... Args> +inline ComponentStorageEntry +ComponentStorageEntry::LazyComponentWithArgs::createReplacedComponentEntry(Component (*fun)(Args...), + std::tuple<Args...> args_tuple) { + ComponentStorageEntry result; + result.type_id = getTypeId<Component (*)(Args...)>(); + result.kind = ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS; + result.lazy_component_with_args.component = + new ComponentInterfaceImpl<Component, Args...>(fun, std::move(args_tuple)); + return result; +} + +template <typename Component, typename... Args> +inline ComponentStorageEntry +ComponentStorageEntry::LazyComponentWithArgs::createReplacementComponentEntry(Component (*fun)(Args...), + std::tuple<Args...> args_tuple) { + ComponentStorageEntry result; + result.type_id = getTypeId<Component (*)(Args...)>(); + result.kind = ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS; + result.lazy_component_with_args.component = + new ComponentInterfaceImpl<Component, Args...>(fun, std::move(args_tuple)); + return result; +} + +inline ComponentStorageEntry::LazyComponentWithArgs ComponentStorageEntry::LazyComponentWithArgs::copy() const { + LazyComponentWithArgs result; + result.component = component->copy(); + return result; +} + +inline void ComponentStorageEntry::LazyComponentWithArgs::destroy() const { + delete component; +} + +inline bool ComponentStorageEntry::LazyComponentWithArgs::ComponentInterface:: +operator==(const ComponentInterface& other) const { + return erased_fun == other.erased_fun && areParamsEqual(other); +} + +template <typename Component> +void ComponentStorageEntry::LazyComponentWithNoArgs::addBindings(erased_fun_t erased_fun, entry_vector_t& entries) { + Component component = reinterpret_cast<Component (*)()>(erased_fun)(); + FixedSizeVector<ComponentStorageEntry> component_entries = std::move(component.storage).release(); + entries.insert(entries.end(), component_entries.begin(), component_entries.end()); +} + +template <typename Component> +inline ComponentStorageEntry ComponentStorageEntry::LazyComponentWithNoArgs::create(Component (*fun)()) { + FruitAssert(fun != nullptr); + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS; + result.type_id = getTypeId<Component (*)()>(); + result.lazy_component_with_no_args.erased_fun = reinterpret_cast<erased_fun_t>(fun); + result.lazy_component_with_no_args.add_bindings_fun = LazyComponentWithNoArgs::addBindings<Component>; + return result; +} + +template <typename Component> +inline ComponentStorageEntry +ComponentStorageEntry::LazyComponentWithNoArgs::createReplacedComponentEntry(Component (*fun)()) { + FruitAssert(fun != nullptr); + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS; + result.type_id = getTypeId<Component (*)()>(); + result.lazy_component_with_no_args.erased_fun = reinterpret_cast<erased_fun_t>(fun); + result.lazy_component_with_no_args.add_bindings_fun = LazyComponentWithNoArgs::addBindings<Component>; + return result; +} + +template <typename Component> +inline ComponentStorageEntry +ComponentStorageEntry::LazyComponentWithNoArgs::createReplacementComponentEntry(Component (*fun)()) { + FruitAssert(fun != nullptr); + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS; + result.type_id = getTypeId<Component (*)()>(); + result.lazy_component_with_no_args.erased_fun = reinterpret_cast<erased_fun_t>(fun); + result.lazy_component_with_no_args.add_bindings_fun = LazyComponentWithNoArgs::addBindings<Component>; + return result; +} + +inline bool ComponentStorageEntry::LazyComponentWithNoArgs::isValid() const { + return erased_fun != nullptr; +} + +inline bool ComponentStorageEntry::LazyComponentWithNoArgs:: +operator==(const ComponentStorageEntry::LazyComponentWithNoArgs& other) const { + if (erased_fun == other.erased_fun) { + // These must be equal in this case, no need to compare them. + FruitAssert(add_bindings_fun == other.add_bindings_fun); + return true; + } else { + // type_id and add_bindings_fun may or may not be different from the ones in `other`. + return false; + } +} + +inline void ComponentStorageEntry::LazyComponentWithNoArgs::addBindings(entry_vector_t& entries) const { + FruitAssert(isValid()); + add_bindings_fun(erased_fun, entries); +} + +inline std::size_t ComponentStorageEntry::LazyComponentWithNoArgs::hashCode() const { + // We only need to hash this field (for the same reason that we only compare this field in operator==). + return std::hash<erased_fun_t>()(erased_fun); +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_COMPONENT_STORAGE_ENTRY_DEFN_H diff --git a/include/fruit/impl/component_storage/component_storage_entry.h b/include/fruit/impl/component_storage/component_storage_entry.h new file mode 100644 index 0000000..a320b49 --- /dev/null +++ b/include/fruit/impl/component_storage/component_storage_entry.h @@ -0,0 +1,337 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_COMPONENT_STORAGE_ENTRY_H +#define FRUIT_COMPONENT_STORAGE_ENTRY_H + +#include <fruit/impl/component_storage/binding_deps.h> +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/semistatic_graph.h> +#include <fruit/impl/fruit_internal_forward_decls.h> + +namespace fruit { +namespace impl { + +/** + * This represents a generic entry in ComponentStorage. + * We use a single POD (this struct) to represent any binding so that ComponentStorage can hold a single vector, instead + * of having to hold multiple vectors (each of which potentially requiring allocation/deallocation when a + * ComponentStorage is constructed/destroyed). + * This way each ComponentStorage can hold a single vector and do a single allocation. + */ +struct ComponentStorageEntry { + enum class Kind { +#ifdef FRUIT_EXTRA_DEBUG + INVALID, +#endif + BINDING_FOR_CONSTRUCTED_OBJECT, + BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION, + BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION, + BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION, + COMPRESSED_BINDING, + MULTIBINDING_FOR_CONSTRUCTED_OBJECT, + MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION, + MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION, + // This is not an actual binding, it's an "addendum" to multibinding bindings that specifies how the multibinding + // vector can be created. Unlike real multibinding entries, this *can* be deduped. + MULTIBINDING_VECTOR_CREATOR, + + LAZY_COMPONENT_WITH_NO_ARGS, + LAZY_COMPONENT_WITH_ARGS, + + // Component replacements are stored as a REPLACEMENT_LAZY_COMPONENT_* entry followed by a REPLACED_LAZY_COMPONENT_* + // entry. Note that the args are independent: e.g. a component with args can be replaced by a component with no + // args. This also means that the type_id of the two entries can be different (since it's the type_id of the + // function signature rather than just of the Component<...>). + REPLACED_LAZY_COMPONENT_WITH_NO_ARGS, + REPLACED_LAZY_COMPONENT_WITH_ARGS, + REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS, + REPLACEMENT_LAZY_COMPONENT_WITH_ARGS, + + // These markers are used in expandLazyComponents(), see the comments there for details. + COMPONENT_WITH_ARGS_END_MARKER, + COMPONENT_WITHOUT_ARGS_END_MARKER, + }; + +#ifdef FRUIT_EXTRA_DEBUG + mutable +#endif + Kind kind; + + // This is usually the TypeId for the bound type, except: + // * when kind==COMPRESSED_BINDING, this is the interface's TypeId + // * when kind==*LAZY_COMPONENT_*, this is the TypeId of the + // Component<...>-returning function. + TypeId type_id; + + /** + * This represents an entry in ComponentStorage for a binding (not a multibinding) that holds an already-constructed + * object. + */ + struct BindingForConstructedObject { + using object_ptr_t = const void*; + + // The already-constructed object. We do *not* own this, this object must outlive the injector. + // This is a const pointer because in some cases it might be a const binding. + // We can cast this to a non-const pointer when we're sure that the original binding was for a non-const reference. + object_ptr_t object_ptr; + +#ifdef FRUIT_EXTRA_DEBUG + bool is_nonconst; +#endif + }; + + /** + * This represents an entry in ComponentStorage for a binding (not a multibinding) that holds an object that needs to + * be constructed (potentially after injecting any dependencies). + */ + struct BindingForObjectToConstruct { + // This is a const pointer because this might be a const binding. If not, we'll cast this back to a non-const + // pointer when we need to. + using object_t = const void*; + using create_t = object_t (*)(InjectorStorage&, SemistaticGraph<TypeId, NormalizedBinding>::node_iterator); + + // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr). + // Once the object is constructed (at injection time), the injector owns that object. + create_t create; + + // The type IDs that this type depends on. + const BindingDeps* deps; + +#ifdef FRUIT_EXTRA_DEBUG + bool is_nonconst; +#endif + }; + + /** + * This represents an entry in ComponentStorage for a multibinding that holds an already-constructed + * object. + */ + struct MultibindingForConstructedObject { + using object_ptr_t = void*; + + // The already-constructed object. We do *not* own this, this object must outlive the injector. + object_ptr_t object_ptr; + }; + + /** + * This represents an entry in ComponentStorage for a multibinding that holds an object that needs to + * be constructed (potentially after injecting any dependencies). + */ + struct MultibindingForObjectToConstruct { + + using object_t = void*; + using create_t = object_t (*)(InjectorStorage&); + + // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr). + // Once the object is constructed (at injection time), the injector owns that object. + create_t create; + + // The type IDs that this type depends on. + const BindingDeps* deps; + }; + + /** + * This is not an actual binding, it's an "addendum" to multibinding bindings that specifies how the multibinding + * vector can be created. Unlike real multibinding entries, this *can* be deduped. + */ + struct MultibindingVectorCreator { + + using get_multibindings_vector_t = std::shared_ptr<char> (*)(InjectorStorage&); + + // Returns the std::vector<T*> of instances, or nullptr if none. + // Caches the result in the `v' member of NormalizedMultibindingData. + get_multibindings_vector_t get_multibindings_vector; + }; + + // A CompressedBinding with interface_id==getTypeId<I>() and class_id==getTypeId<C>() means that if: + // * C is not exposed by the component + // * I is the only node that depends on C + // * There are no multibindings that directly depend on C + // The BindingData for C is BindingForObjectToConstruct( + // Then, taken create1, needs_reallocation such that the ComponentStorageEntry for c_type_id is + // BindingForObjectToConstruct(createC, deps, needs_allocation), we can remove the binding for I and C and replace + // them + // with just a binding for I, with BindingForObjectToConstruct(create, deps, needs_allocation). + struct CompressedBinding { + + using create_t = BindingForObjectToConstruct::create_t; + + // TypeId for the implementation. + TypeId c_type_id; + + // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr). + // Once the object is constructed (at injection time), the injector owns that object. + create_t create; + }; + + /** + * This represents an entry in ComponentStorage for a lazy component with no arguments. + */ + struct LazyComponentWithNoArgs { + // An arbitrary function type, used as type for the field `erased_fun`. + // Note that we can't use void* here, since data pointers might not have the same size as function pointers. + using erased_fun_t = void (*)(); + + // The function that will be invoked to create the Component. + // Here we don't know the type, it's only known at construction time. + erased_fun_t erased_fun; + + using entry_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + + // The function that allows to add this component's bindings to the given ComponentStorage. + using add_bindings_fun_t = void (*)(erased_fun_t, entry_vector_t&); + add_bindings_fun_t add_bindings_fun; + + template <typename Component> + static void addBindings(erased_fun_t erased_fun, entry_vector_t& entries); + + template <typename Component> + static ComponentStorageEntry create(Component (*fun)()); + + template <typename Component> + static ComponentStorageEntry createReplacedComponentEntry(Component (*fun)()); + + template <typename Component> + static ComponentStorageEntry createReplacementComponentEntry(Component (*fun)()); + + bool operator==(const LazyComponentWithNoArgs&) const; + + void addBindings(entry_vector_t& entries) const; + + std::size_t hashCode() const; + + bool isValid() const; + }; + + /** + * This represents an entry in ComponentStorage for a lazy component with arguments. + */ + struct LazyComponentWithArgs { + class ComponentInterface { + public: + // An arbitrary function type, used as type for the field `erased_fun`. + // Note that we can't use void* here, since data pointers might not have the same size as function pointers. + using erased_fun_t = void (*)(); + + // The function that will be invoked to create the Component. + // Here we don't know the type, it's only known to the LazyComponent implementation. + // We store this here instead of in the LazyComponent implementation so that we can do a quick comparison on the + // pointer without virtual calls (and we can then do the rest of the comparison via virtual call if needed). + erased_fun_t erased_fun; + + using entry_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + + ComponentInterface(erased_fun_t erased_fun); + + virtual ~ComponentInterface() = default; + + // Checks if *this and other are equal, assuming that this->fun and other.fun are equal. + virtual bool areParamsEqual(const ComponentInterface& other) const = 0; + + bool operator==(const ComponentInterface& other) const; + + virtual void addBindings(entry_vector_t& component_storage_entries) const = 0; + virtual std::size_t hashCode() const = 0; + virtual ComponentInterface* copy() const = 0; + + /** + * Returns the type ID of the real `fun` object stored by the implementation. + * We use this instead of the `typeid` operator so that we don't require RTTI. + */ + virtual TypeId getFunTypeId() const = 0; + }; + + template <typename Component, typename... Args> + static ComponentStorageEntry create(Component (*fun)(Args...), std::tuple<Args...> args_tuple); + + template <typename Component, typename... Args> + static ComponentStorageEntry createReplacedComponentEntry(Component (*fun)(Args...), + std::tuple<Args...> args_tuple); + + template <typename Component, typename... Args> + static ComponentStorageEntry createReplacementComponentEntry(Component (*fun)(Args...), + std::tuple<Args...> args_tuple); + + LazyComponentWithArgs(LazyComponentWithArgs&&) = default; + LazyComponentWithArgs& operator=(LazyComponentWithArgs&&) = default; + + // Note: we must allow these (and use the default implementations) since this class is used in a union so it must be + // a POD. However when we need a real object we must call the other constructor above, and when we need a copy we + // must + // call copy() explicitly. + LazyComponentWithArgs() = default; // LCOV_EXCL_LINE + LazyComponentWithArgs(const LazyComponentWithArgs&) = default; + LazyComponentWithArgs& operator=(const LazyComponentWithArgs&) = default; + + LazyComponentWithArgs copy() const; + void destroy() const; + + ComponentInterface* component; + }; + + union { + // Valid iff kind is BINDING_FOR_CONSTRUCTED_OBJECT. + BindingForConstructedObject binding_for_constructed_object; + + // Valid iff kind is BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_[NO_]ALLOCATION + // or BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION. + BindingForObjectToConstruct binding_for_object_to_construct; + + // Valid iff kind is MULTIBINDING_FOR_CONSTRUCTED_OBJECT. + MultibindingForConstructedObject multibinding_for_constructed_object; + + // Valid iff kind is MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_[NO_]ALLOCATION. + MultibindingForObjectToConstruct multibinding_for_object_to_construct; + + // Valid iff kind is MULTIBINDING_VECTOR_CREATOR. + MultibindingVectorCreator multibinding_vector_creator; + + // Valid iff kind is COMPRESSED_BINDING. + CompressedBinding compressed_binding; + + // Valid iff kind is LAZY_COMPONENT_WITH_NO_ARGS, REPLACED_LAZY_COMPONENT_WITH_NO_ARGS or + // REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS. + LazyComponentWithNoArgs lazy_component_with_no_args; + + // Valid iff kind is LAZY_COMPONENT_WITH_ARGS, REPLACED_LAZY_COMPONENT_WITH_ARGS or + // REPLACEMENT_LAZY_COMPONENT_WITH_ARGS. + LazyComponentWithArgs lazy_component_with_args; + }; + + // We use a custom method instead of a real copy constructor so that all copies are explicit (since copying is a + // fairly expensive operation). + ComponentStorageEntry copy() const; + + // We use a custom method instead of a real destructor, so that we can hold these in a std::vector but still destroy + // them when desired. + void destroy() const; +}; + +// We can't have this assert in debug mode because we add debug-only fields that increase the size. +#ifndef FRUIT_EXTRA_DEBUG +// This is not required for correctness, but 4 64-bit words should be enough to hold this object, if not we'd end up +// using more memory/CPU than expected. +static_assert(sizeof(ComponentStorageEntry) <= 4 * sizeof(std::uint64_t), + "Error: a ComponentStorageEntry doesn't fit in 32 bytes as we expected"); +#endif + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/component_storage/component_storage_entry.defn.h> + +#endif // FRUIT_COMPONENT_STORAGE_ENTRY_H diff --git a/include/fruit/impl/component_storage/partial_component_storage.defn.h b/include/fruit/impl/component_storage/partial_component_storage.defn.h new file mode 100644 index 0000000..09e0c66 --- /dev/null +++ b/include/fruit/impl/component_storage/partial_component_storage.defn.h @@ -0,0 +1,472 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PARTIAL_COMPONENT_STORAGE_DEFN_H +#define FRUIT_PARTIAL_COMPONENT_STORAGE_DEFN_H + +#include <fruit/impl/component_storage/partial_component_storage.h> + +#include <fruit/impl/bindings.h> +#include <fruit/impl/injector/injector_storage.h> +#include <fruit/impl/util/call_with_tuple.h> +#include <fruit/impl/util/type_info.h> +#include <utility> + +namespace fruit { +namespace impl { + +template <> +class PartialComponentStorage<> { +public: + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + (void)entries; + } + + std::size_t numBindings() const { + return 0; + } +}; + +template <typename I, typename C, typename... PreviousBindings> +class PartialComponentStorage<Bind<I, C>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename Signature, typename... PreviousBindings> +class PartialComponentStorage<RegisterConstructor<Signature>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename C, typename C1, typename... PreviousBindings> +class PartialComponentStorage<BindInstance<C, C1>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(InjectorStorage::createComponentStorageEntryForBindInstance<C, C>(instance)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename C, typename C1, typename... PreviousBindings> +class PartialComponentStorage<BindConstInstance<C, C1>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + const C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, const C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(InjectorStorage::createComponentStorageEntryForBindConstInstance<C, C>(instance)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename C, typename Annotation, typename C1, typename... PreviousBindings> +class PartialComponentStorage<BindInstance<fruit::Annotated<Annotation, C>, C1>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back( + InjectorStorage::createComponentStorageEntryForBindInstance<fruit::Annotated<Annotation, C>, C>(instance)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename C, typename Annotation, typename C1, typename... PreviousBindings> +class PartialComponentStorage<BindConstInstance<fruit::Annotated<Annotation, C>, C1>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + const C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, const C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back( + InjectorStorage::createComponentStorageEntryForBindConstInstance<fruit::Annotated<Annotation, C>, C>(instance)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename... Params, typename... PreviousBindings> +class PartialComponentStorage<RegisterProvider<Params...>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename C, typename... PreviousBindings> +class PartialComponentStorage<AddInstanceMultibinding<C>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(InjectorStorage::createComponentStorageEntryForInstanceMultibinding<C, C>(instance)); + entries.push_back(InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<C>()); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 2; + } +}; + +template <typename C, typename Annotation, typename... PreviousBindings> +class PartialComponentStorage<AddInstanceMultibinding<fruit::Annotated<Annotation, C>>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + C& instance; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance) + : previous_storage(previous_storage), instance(instance) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back( + InjectorStorage::createComponentStorageEntryForInstanceMultibinding<fruit::Annotated<Annotation, C>, C>( + instance)); + entries.push_back( + InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<fruit::Annotated<Annotation, C>>()); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 2; + } +}; + +template <typename C, typename... PreviousBindings> +class PartialComponentStorage<AddInstanceVectorMultibindings<C>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + std::vector<C>& instances; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, std::vector<C>& instances) + : previous_storage(previous_storage), instances(instances) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + for (auto i = instances.rbegin(), i_end = instances.rend(); i != i_end; ++i) { + // TODO: consider optimizing this so that we need just 1 MULTIBINDING_VECTOR_CREATOR entry (removing the + // assumption that each multibinding entry is always preceded by that). + entries.push_back(InjectorStorage::createComponentStorageEntryForInstanceMultibinding<C, C>(*i)); + entries.push_back(InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<C>()); + } + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + instances.size() * 2; + } +}; + +template <typename C, typename Annotation, typename... PreviousBindings> +class PartialComponentStorage<AddInstanceVectorMultibindings<fruit::Annotated<Annotation, C>>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + std::vector<C>& instances; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, std::vector<C>& instances) + : previous_storage(previous_storage), instances(instances) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + for (auto i = instances.rbegin(), i_end = instances.rend(); i != i_end; ++i) { + // TODO: consider optimizing this so that we need just 1 MULTIBINDING_VECTOR_CREATOR entry (removing the + // assumption that each multibinding entry is always preceded by that). + entries.push_back( + InjectorStorage::createComponentStorageEntryForInstanceMultibinding<fruit::Annotated<Annotation, C>, C>(*i)); + entries.push_back( + InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<fruit::Annotated<Annotation, C>>()); + } + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + instances.size() * 2; + } +}; + +template <typename I, typename C, typename... PreviousBindings> +class PartialComponentStorage<AddMultibinding<I, C>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename... Params, typename... PreviousBindings> +class PartialComponentStorage<AddMultibindingProvider<Params...>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename DecoratedSignature, typename Lambda, typename... PreviousBindings> +class PartialComponentStorage<RegisterFactory<DecoratedSignature, Lambda>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) + : previous_storage(previous_storage) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings(); + } +}; + +template <typename OtherComponent, typename... PreviousBindings> +class PartialComponentStorage<InstallComponent<OtherComponent()>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + OtherComponent (*fun)(); + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, OtherComponent (*fun1)(), + std::tuple<>) + : previous_storage(previous_storage), fun(fun1) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::create(fun)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename OtherComponent, typename... Args, typename... PreviousBindings> +class PartialComponentStorage<InstallComponent<OtherComponent(Args...)>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + OtherComponent (*fun)(Args...); + std::tuple<Args...> args_tuple; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, + OtherComponent (*fun1)(Args...), std::tuple<Args...> args_tuple) + : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back(ComponentStorageEntry::LazyComponentWithArgs::create(fun, std::move(args_tuple))); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename OtherComponent, typename... PreviousBindings> +class PartialComponentStorage<PartialReplaceComponent<OtherComponent()>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + OtherComponent (*fun)(); + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, OtherComponent (*fun1)(), + std::tuple<>) + : previous_storage(previous_storage), fun(fun1) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::createReplacedComponentEntry(fun)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename OtherComponent, typename... ReplacedFunArgs, typename... PreviousBindings> +class PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...> { +private: + PartialComponentStorage<PreviousBindings...>& previous_storage; + OtherComponent (*fun)(ReplacedFunArgs...); + std::tuple<ReplacedFunArgs...> args_tuple; + +public: + PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, + OtherComponent (*fun1)(ReplacedFunArgs...), std::tuple<ReplacedFunArgs...> args_tuple) + : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + ComponentStorageEntry::LazyComponentWithArgs::createReplacedComponentEntry(fun, std::move(args_tuple))); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename OtherComponent, typename... PreviousBindings, typename... ReplacedFunArgs> +class PartialComponentStorage<ReplaceComponent<OtherComponent(ReplacedFunArgs...), OtherComponent()>, + PreviousBindings...> { +private: + using previous_storage_t = + PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...>; + + previous_storage_t& previous_storage; + OtherComponent (*fun)(); + +public: + PartialComponentStorage(previous_storage_t& previous_storage, OtherComponent (*fun1)(), std::tuple<>) + : previous_storage(previous_storage), fun(fun1) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const { + entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::createReplacementComponentEntry(fun)); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +template <typename OtherComponent, typename... ReplacedFunArgs, typename... ReplacementFunArgs, + typename... PreviousBindings> +class PartialComponentStorage< + ReplaceComponent<OtherComponent(ReplacedFunArgs...), OtherComponent(ReplacementFunArgs...)>, PreviousBindings...> { +private: + using previous_storage_t = + PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...>; + + previous_storage_t& previous_storage; + OtherComponent (*fun)(ReplacementFunArgs...); + std::tuple<ReplacementFunArgs...> args_tuple; + +public: + PartialComponentStorage(previous_storage_t& previous_storage, OtherComponent (*fun1)(ReplacementFunArgs...), + std::tuple<ReplacementFunArgs...> args_tuple) + : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {} + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) { + entries.push_back( + ComponentStorageEntry::LazyComponentWithArgs::createReplacementComponentEntry(fun, std::move(args_tuple))); + previous_storage.addBindings(entries); + } + + std::size_t numBindings() const { + return previous_storage.numBindings() + 1; + } +}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_PARTIAL_COMPONENT_STORAGE_DEFN_H diff --git a/include/fruit/impl/component_storage/partial_component_storage.h b/include/fruit/impl/component_storage/partial_component_storage.h new file mode 100644 index 0000000..7d7b6f3 --- /dev/null +++ b/include/fruit/impl/component_storage/partial_component_storage.h @@ -0,0 +1,49 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PARTIAL_COMPONENT_STORAGE_H +#define FRUIT_PARTIAL_COMPONENT_STORAGE_H + +#include <cstddef> + +namespace fruit { +namespace impl { + +/** + * This class stores the data in a PartialComponent<Bindings...>. + * Instead of dynamically-allocating space for the elements (and then having to move the storage from each + * PartialComponent class to the next) we only store a reference to the previous PartialComponent's storage + * and the data needed for the binding (if any). + * We rely on the fact that the previous PartialComponent objects will only be destroyed after the current + * one. + */ +template <typename... Bindings> +class PartialComponentStorage; /* { +All specializations support the following methods: + + void addBindings(FixedSizeVector<ComponentStorageEntry>& entries); + std::size_t numBindings(); +};*/ + +template <typename... Bindings> +class PartialComponentStorage {}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/component_storage/partial_component_storage.defn.h> + +#endif // FRUIT_PARTIAL_COMPONENT_STORAGE_H diff --git a/include/fruit/impl/data_structures/arena_allocator.defn.h b/include/fruit/impl/data_structures/arena_allocator.defn.h new file mode 100644 index 0000000..51a0adb --- /dev/null +++ b/include/fruit/impl/data_structures/arena_allocator.defn.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_ARENA_ALLOCATOR_DEFN_H +#define FRUIT_ARENA_ALLOCATOR_DEFN_H + +#include <fruit/impl/data_structures/arena_allocator.h> + +namespace fruit { +namespace impl { + +template <typename T> +inline ArenaAllocator<T>::ArenaAllocator(MemoryPool& memory_pool) : pool(&memory_pool) {} + +template <typename T> +template <typename U> +inline ArenaAllocator<T>::ArenaAllocator(const ArenaAllocator<U>& other) : pool(other.pool) {} + +template <typename T> +inline T* ArenaAllocator<T>::allocate(std::size_t n) { + return pool->allocate<T>(n); +} + +template <typename T> +void ArenaAllocator<T>::deallocate(T*, std::size_t) { + // Intentionally empty, the memory will be deallocated when the MemoryPool is destroyed. +} + +template <class T, class U> +bool operator==(const ArenaAllocator<T>& x, const ArenaAllocator<U>& y) { + return x.pool == y.pool; +} + +template <class T, class U> +bool operator!=(const ArenaAllocator<T>& x, const ArenaAllocator<U>& y) { + return x.pool != y.pool; +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_ARENA_ALLOCATOR_DEFN_H diff --git a/include/fruit/impl/data_structures/arena_allocator.h b/include/fruit/impl/data_structures/arena_allocator.h new file mode 100644 index 0000000..3ba0521 --- /dev/null +++ b/include/fruit/impl/data_structures/arena_allocator.h @@ -0,0 +1,76 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_ARENA_ALLOCATOR_H +#define FRUIT_ARENA_ALLOCATOR_H + +#include <fruit/impl/data_structures/memory_pool.h> + +namespace fruit { +namespace impl { + +/** + * An allocator that allocates memory in pages and only de-allocates memory on destruction. + * This is useful in code that needs many short-lived allocations. + * Each ArenaAllocator object should only be accessed by a single thread. + */ +template <typename T> +class ArenaAllocator { +private: + MemoryPool* pool; + + template <class U> + friend class ArenaAllocator; + + template <class U, class V> + friend bool operator==(const ArenaAllocator<U>& x, const ArenaAllocator<V>& y); + + template <class U, class V> + friend bool operator!=(const ArenaAllocator<U>& x, const ArenaAllocator<V>& y); + +public: + using value_type = T; + + template <typename U> + struct rebind { + using other = ArenaAllocator<U>; + }; + + /** + * Constructs an arena allocator using the specified memory pool. + * The MemoryPool object must outlive all allocators constructed with it and all allocated objects. + */ + ArenaAllocator(MemoryPool& memory_pool); + + template <typename U> + ArenaAllocator(const ArenaAllocator<U>&); + + T* allocate(std::size_t n); + void deallocate(T* p, std::size_t); +}; + +template <class T, class U> +bool operator==(const ArenaAllocator<T>&, const ArenaAllocator<U>&); + +template <class T, class U> +bool operator!=(const ArenaAllocator<T>&, const ArenaAllocator<U>&); + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/arena_allocator.defn.h> + +#endif // FRUIT_ARENA_ALLOCATOR_H diff --git a/include/fruit/impl/data_structures/fixed_size_allocator.defn.h b/include/fruit/impl/data_structures/fixed_size_allocator.defn.h new file mode 100644 index 0000000..383b737 --- /dev/null +++ b/include/fruit/impl/data_structures/fixed_size_allocator.defn.h @@ -0,0 +1,138 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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/LITENSE-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 TONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRUIT_FIXED_SIZE_ALLOTATOR_DEFN_H +#define FRUIT_FIXED_SIZE_ALLOTATOR_DEFN_H + +#include <fruit/impl/fruit_assert.h> + +#include <cassert> + +#ifdef FRUIT_EXTRA_DEBUG +#include <iostream> +#endif + +// Redundant, but makes KDevelop happy. +#include <fruit/impl/data_structures/fixed_size_allocator.h> + +namespace fruit { +namespace impl { + +template <typename C> +void FixedSizeAllocator::destroyObject(void* p) { + C* cPtr = reinterpret_cast<C*>(p); + cPtr->C::~C(); +} + +template <typename C> +void FixedSizeAllocator::destroyExternalObject(void* p) { + C* cPtr = reinterpret_cast<C*>(p); + delete cPtr; // LCOV_EXCL_BR_LINE +} + +inline void FixedSizeAllocator::FixedSizeAllocatorData::addType(TypeId typeId) { +#ifdef FRUIT_EXTRA_DEBUG + types[typeId]++; +#endif + if (!typeId.type_info->isTriviallyDestructible()) { + num_types_to_destroy++; + } + total_size += maximumRequiredSpace(typeId); +} + +inline void FixedSizeAllocator::FixedSizeAllocatorData::addExternallyAllocatedType(TypeId typeId) { + (void)typeId; + num_types_to_destroy++; +} + +inline std::size_t FixedSizeAllocator::FixedSizeAllocatorData::maximumRequiredSpace(TypeId type) { + return type.type_info->alignment() + type.type_info->size() - 1; +} + +template <typename AnnotatedT, typename... Args> +FRUIT_ALWAYS_INLINE inline fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<AnnotatedT>)>>* +FixedSizeAllocator::constructObject(Args&&... args) { + using T = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<AnnotatedT>)>>; + + char* p = storage_last_used; + size_t misalignment = std::uintptr_t(p) % alignof(T); +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(remaining_types[getTypeId<AnnotatedT>()] != 0); + remaining_types[getTypeId<AnnotatedT>()]--; +#endif + p += alignof(T) - misalignment; + FruitAssert(std::uintptr_t(p) % alignof(T) == 0); + T* x = reinterpret_cast<T*>(p); + storage_last_used = p + sizeof(T) - 1; + + // This runs arbitrary code (T's constructor), which might end up calling + // constructObject recursively. We must make sure all invariants are satisfied before + // calling this. + new (x) T(std::forward<Args>(args)...); // LCOV_EXCL_BR_LINE + + // We still run this later though, since if T's constructor throws we don't want to + // destruct this object in FixedSizeAllocator's destructor. + if (!std::is_trivially_destructible<T>::value) { + on_destruction.push_back(std::pair<destroy_t, void*>{destroyObject<T>, x}); + } + return x; +} + +template <typename T> +inline void FixedSizeAllocator::registerExternallyAllocatedObject(T* p) { + on_destruction.push_back(std::pair<destroy_t, void*>{destroyExternalObject<T>, p}); +} + +inline FixedSizeAllocator::FixedSizeAllocator(FixedSizeAllocatorData allocator_data) + : on_destruction(allocator_data.num_types_to_destroy) { + // The +1 is because we waste the first byte (storage_last_used points to the beginning of storage). + storage_begin = new char[allocator_data.total_size + 1]; + storage_last_used = storage_begin; +#ifdef FRUIT_EXTRA_DEBUG + remaining_types = allocator_data.types; + std::cerr << "Constructing allocator for types:"; + for (auto x : remaining_types) { + std::cerr << " " << x.first; + } + std::cerr << std::endl; +#endif +} + +inline FixedSizeAllocator::FixedSizeAllocator(FixedSizeAllocator&& x) : FixedSizeAllocator() { + std::swap(storage_begin, x.storage_begin); + std::swap(storage_last_used, x.storage_last_used); + std::swap(on_destruction, x.on_destruction); +#ifdef FRUIT_EXTRA_DEBUG + std::swap(remaining_types, x.remaining_types); +#endif +} + +inline FixedSizeAllocator& FixedSizeAllocator::operator=(FixedSizeAllocator&& x) { + std::swap(storage_begin, x.storage_begin); + std::swap(storage_last_used, x.storage_last_used); + std::swap(on_destruction, x.on_destruction); +#ifdef FRUIT_EXTRA_DEBUG + std::swap(remaining_types, x.remaining_types); +#endif + return *this; +} + +} // namespace fruit +} // namespace impl + +#endif // FRUIT_FIXED_SIZE_ALLOTATOR_DEFN_H diff --git a/include/fruit/impl/data_structures/fixed_size_allocator.h b/include/fruit/impl/data_structures/fixed_size_allocator.h new file mode 100644 index 0000000..fa0480c --- /dev/null +++ b/include/fruit/impl/data_structures/fixed_size_allocator.h @@ -0,0 +1,120 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FIXED_SIZE_ALLOCATOR_H +#define FRUIT_FIXED_SIZE_ALLOCATOR_H + +#include <fruit/impl/data_structures/fixed_size_vector.h> +#include <fruit/impl/meta/component.h> +#include <fruit/impl/util/type_info.h> + +#ifdef FRUIT_EXTRA_DEBUG +#include <unordered_map> +#endif + +namespace fruit { +namespace impl { + +/** + * An allocator where the maximum total size is fixed at construction, and all memory is retained until the allocator + * object itself is destructed. + */ +class FixedSizeAllocator { +public: + using destroy_t = void (*)(void*); + +private: + // A pointer to the last used byte in the allocated memory chunk starting at storage_begin. + char* storage_last_used = nullptr; + + // The chunk of memory that will be used for all allocations. + char* storage_begin = nullptr; + +#ifdef FRUIT_EXTRA_DEBUG + std::unordered_map<TypeId, std::size_t> remaining_types; +#endif + + // This vector contains the destroy operations that have to be performed at destruction, and + // the pointers that they must be invoked with. Allows destruction in the correct order. + // These must be called in reverse order. + FixedSizeVector<std::pair<destroy_t, void*>> on_destruction; + + // Destroys an object previously created using constructObject(). + template <typename C> + static void destroyObject(void* p); + + // Calls delete on an object previously allocated using new. + template <typename C> + static void destroyExternalObject(void* p); + +public: + // Data used to construct an allocator for a fixed set of types. + class FixedSizeAllocatorData { + private: + std::size_t total_size = 0; + std::size_t num_types_to_destroy = 0; +#ifdef FRUIT_EXTRA_DEBUG + std::unordered_map<TypeId, std::size_t> types; +#endif + + static std::size_t maximumRequiredSpace(TypeId type); + + friend class FixedSizeAllocator; + + public: + // Adds 1 `typeId' to the type set. Multiple copies of the same type are allowed. + // Each call to this method allows 1 constructObject<T>(...) call on the resulting allocator. + void addType(TypeId typeId); + + // Each call to this method with getTypeId<T>() allows 1 registerExternallyAllocatedType<T>(...) call on the + // resulting + // allocator. + void addExternallyAllocatedType(TypeId typeId); + }; + + // Constructs an empty allocator (no allocations are allowed). + FixedSizeAllocator() = default; + + // Constructs an allocator for the type set in FixedSizeAllocatorData. + FixedSizeAllocator(FixedSizeAllocatorData allocator_data); + + FixedSizeAllocator(FixedSizeAllocator&&); + FixedSizeAllocator& operator=(FixedSizeAllocator&&); + + FixedSizeAllocator(const FixedSizeAllocator&) = delete; + FixedSizeAllocator& operator=(const FixedSizeAllocator&) = delete; + + // On destruction, all objects allocated with constructObject() and all externally-allocated objects registered with + // registerExternallyAllocatedObject() are destroyed. + ~FixedSizeAllocator(); + + // Allocates an object of type T, constructing it with the specified arguments. Similar to: + // new C(args...) + template <typename AnnotatedT, typename... Args> + fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<AnnotatedT>)>>* + constructObject(Args&&... args); + + template <typename T> + void registerExternallyAllocatedObject(T* p); +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/fixed_size_allocator.defn.h> + +#endif // FRUIT_FIXED_SIZE_ALLOCATOR_H diff --git a/include/fruit/impl/data_structures/fixed_size_vector.defn.h b/include/fruit/impl/data_structures/fixed_size_vector.defn.h new file mode 100644 index 0000000..79d0c97 --- /dev/null +++ b/include/fruit/impl/data_structures/fixed_size_vector.defn.h @@ -0,0 +1,139 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FIXED_SIZE_VECTOR_DEFN_H +#define FRUIT_FIXED_SIZE_VECTOR_DEFN_H + +#include <fruit/impl/data_structures/fixed_size_vector.h> + +#include <fruit/impl/fruit_assert.h> + +#include <cassert> +#include <cstring> +#include <utility> + +namespace fruit { +namespace impl { + +template <typename T, typename Allocator> +inline FixedSizeVector<T, Allocator>::FixedSizeVector(std::size_t capacity, Allocator allocator) + : capacity(capacity), allocator(allocator) { + if (capacity == 0) { // LCOV_EXCL_BR_LINE + v_begin = 0; + } else { + v_begin = allocator.allocate(capacity); + } + v_end = v_begin; +} + +template <typename T, typename Allocator> +inline FixedSizeVector<T, Allocator>::~FixedSizeVector() { + clear(); + if (capacity != 0) { + allocator.deallocate(v_begin, capacity); + } +} + +template <typename T, typename Allocator> +inline FixedSizeVector<T, Allocator>::FixedSizeVector(FixedSizeVector&& other) : FixedSizeVector() { + swap(other); +} + +template <typename T, typename Allocator> +inline FixedSizeVector<T, Allocator>& FixedSizeVector<T, Allocator>::operator=(FixedSizeVector&& other) { + swap(other); + return *this; +} + +template <typename T, typename Allocator> +inline std::size_t FixedSizeVector<T, Allocator>::size() const { + return end() - begin(); +} + +template <typename T, typename Allocator> +inline T& FixedSizeVector<T, Allocator>::operator[](std::size_t i) { + FruitAssert(begin() + i < end()); + return begin()[i]; +} + +template <typename T, typename Allocator> +inline const T& FixedSizeVector<T, Allocator>::operator[](std::size_t i) const { + FruitAssert(begin() + i < end()); + return begin()[i]; +} + +template <typename T, typename Allocator> +inline void FixedSizeVector<T, Allocator>::swap(FixedSizeVector& x) { + std::swap(v_end, x.v_end); + std::swap(v_begin, x.v_begin); + std::swap(capacity, x.capacity); +} + +template <typename T, typename Allocator> +inline void FixedSizeVector<T, Allocator>::push_back(T x) { +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(v_end != v_begin + capacity); +#endif + new (v_end) T(x); // LCOV_EXCL_BR_LINE + ++v_end; +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(v_end <= v_begin + capacity); +#endif +} + +// This method is covered by tests, even though lcov doesn't detect that. +template <typename T, typename Allocator> +inline T* FixedSizeVector<T, Allocator>::data() { + return v_begin; +} + +template <typename T, typename Allocator> +inline T* FixedSizeVector<T, Allocator>::begin() { + return v_begin; +} + +template <typename T, typename Allocator> +inline T* FixedSizeVector<T, Allocator>::end() { + return v_end; +} + +template <typename T, typename Allocator> +inline const T* FixedSizeVector<T, Allocator>::data() const { + return v_begin; +} + +template <typename T, typename Allocator> +inline const T* FixedSizeVector<T, Allocator>::begin() const { + return v_begin; +} + +template <typename T, typename Allocator> +inline const T* FixedSizeVector<T, Allocator>::end() const { + return v_end; +} + +template <typename T, typename Allocator> +inline void FixedSizeVector<T, Allocator>::clear() { + for (T* p = v_begin; p != v_end; ++p) { + p->~T(); + } + v_end = v_begin; +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_FIXED_SIZE_VECTOR_DEFN_H diff --git a/include/fruit/impl/data_structures/fixed_size_vector.h b/include/fruit/impl/data_structures/fixed_size_vector.h new file mode 100644 index 0000000..7450bf1 --- /dev/null +++ b/include/fruit/impl/data_structures/fixed_size_vector.h @@ -0,0 +1,88 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FIXED_SIZE_VECTOR_H +#define FRUIT_FIXED_SIZE_VECTOR_H + +#include <cstdlib> +#include <memory> + +namespace fruit { +namespace impl { + +/** + * Similar to std::vector<T>, but the capacity is fixed at construction time, and no reallocations ever happen. + * The type T must be trivially copyable. + */ +template <typename T, typename Allocator = std::allocator<T>> +class FixedSizeVector { +private: + // This is not yet implemented in libstdc++ (the STL implementation) shipped with GCC (checked until version 4.9.1). + // static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable."); + + // v_end is before v_begin here, because it's the most commonly accessed field. + T* v_end; + T* v_begin; + + std::size_t capacity; + Allocator allocator; + +public: + using iterator = T*; + using const_iterator = const T*; + + FixedSizeVector(std::size_t capacity = 0, Allocator allocator = Allocator()); + // Creates a vector with the specified size (and equal capacity) initialized with the specified value. + FixedSizeVector(std::size_t size, const T& value, Allocator allocator = Allocator()); + ~FixedSizeVector(); + + // Copy construction is not allowed, you need to specify the capacity in order to construct the copy. + FixedSizeVector(const FixedSizeVector& other) = delete; + FixedSizeVector(const FixedSizeVector& other, std::size_t capacity); + + FixedSizeVector(FixedSizeVector&& other); + + FixedSizeVector& operator=(const FixedSizeVector& other) = delete; + FixedSizeVector& operator=(FixedSizeVector&& other); + + std::size_t size() const; + + T& operator[](std::size_t i); + const T& operator[](std::size_t i) const; + + // This yields undefined behavior (instead of reallocating) if the vector's capacity is exceeded. + void push_back(T x); + + void swap(FixedSizeVector& x); + + // Removes all elements, so size() becomes 0 (but maintains the capacity). + void clear(); + + T* data(); + iterator begin(); + iterator end(); + + const T* data() const; + const_iterator begin() const; + const_iterator end() const; +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/fixed_size_vector.defn.h> + +#endif // FRUIT_FIXED_SIZE_VECTOR_H diff --git a/include/fruit/impl/data_structures/fixed_size_vector.templates.h b/include/fruit/impl/data_structures/fixed_size_vector.templates.h new file mode 100644 index 0000000..5ce3959 --- /dev/null +++ b/include/fruit/impl/data_structures/fixed_size_vector.templates.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FIXED_SIZE_VECTOR_TEMPLATES_H +#define FRUIT_FIXED_SIZE_VECTOR_TEMPLATES_H + +#ifndef IN_FRUIT_CPP_FILE +#error "Fruit .template.h file included in non-cpp file." +#endif + +#include <fruit/impl/data_structures/fixed_size_vector.h> + +#include <fruit/impl/fruit_assert.h> + +namespace fruit { +namespace impl { + +template <typename T, typename Allocator> +FixedSizeVector<T, Allocator>::FixedSizeVector(const FixedSizeVector& other, std::size_t capacity) + : FixedSizeVector(capacity, other.allocator) { + FruitAssert(other.size() <= capacity); + // This is not just an optimization, we also want to make sure that other.capacity (and therefore + // also this.capacity) is >0, or we'd pass nullptr to memcpy (although with a size of 0). + if (other.size() != 0) { + FruitAssert(v_begin != nullptr); + FruitAssert(other.v_begin != nullptr); + std::memcpy(v_begin, other.v_begin, other.size() * sizeof(T)); + } + v_end = v_begin + other.size(); +} + +template <typename T, typename Allocator> +FixedSizeVector<T, Allocator>::FixedSizeVector(std::size_t size, const T& value, Allocator allocator) + : FixedSizeVector(size, allocator) { + for (std::size_t i = 0; i < size; ++i) { + push_back(value); + } +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_FIXED_SIZE_VECTOR_TEMPLATES_H diff --git a/include/fruit/impl/data_structures/memory_pool.defn.h b/include/fruit/impl/data_structures/memory_pool.defn.h new file mode 100644 index 0000000..4d892d4 --- /dev/null +++ b/include/fruit/impl/data_structures/memory_pool.defn.h @@ -0,0 +1,97 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_MEMORY_POOL_DEFN_H +#define FRUIT_MEMORY_POOL_DEFN_H + +#include <fruit/impl/data_structures/memory_pool.h> +#include <fruit/impl/fruit-config.h> +#include <fruit/impl/fruit_assert.h> + +#include <cstdint> + +namespace fruit { +namespace impl { + +inline MemoryPool::MemoryPool() : first_free(nullptr), capacity(0) {} + +inline MemoryPool::MemoryPool(MemoryPool&& other) + : allocated_chunks(std::move(other.allocated_chunks)), first_free(other.first_free), capacity(other.capacity) { + // This is to be sure that we don't double-deallocate. + other.allocated_chunks.clear(); +} + +inline MemoryPool& MemoryPool::operator=(MemoryPool&& other) { + destroy(); + + allocated_chunks = std::move(other.allocated_chunks); + first_free = other.first_free; + capacity = other.capacity; + + // This is to be sure that we don't double-deallocate. + other.allocated_chunks.clear(); + + return *this; +} + +inline MemoryPool::~MemoryPool() { + destroy(); +} + +template <typename T> +FRUIT_ALWAYS_INLINE inline T* MemoryPool::allocate(std::size_t n) { +#ifdef FRUIT_DISABLE_ARENA_ALLOCATION + void* p = operator new(n * sizeof(T)); + allocated_chunks.push_back(p); + return static_cast<T*>(p); +#else + + if (n == 0) { + n = 1; + } + std::size_t misalignment = std::uintptr_t(first_free) % alignof(T); + std::size_t padding = alignof(T) - (sizeof(T) % alignof(T)); + std::size_t required_space = n * (sizeof(T) + padding); + std::size_t required_space_in_chunk = required_space + (alignof(T) - misalignment); + if (required_space_in_chunk > capacity) { + // This is to make sure that the push_back below won't throw. + if (allocated_chunks.size() == allocated_chunks.capacity()) { + allocated_chunks.reserve(1 + 2 * allocated_chunks.size()); + } + void* p; + if (required_space > CHUNK_SIZE) { + p = operator new(required_space); // LCOV_EXCL_BR_LINE + } else { + p = operator new(CHUNK_SIZE); + first_free = static_cast<char*>(p) + required_space; + capacity = CHUNK_SIZE - required_space; + } + allocated_chunks.push_back(p); + return static_cast<T*>(p); + } else { + FruitAssert(first_free != nullptr); + void* p = first_free + misalignment; + first_free += required_space_in_chunk; + capacity -= required_space_in_chunk; + return static_cast<T*>(p); + } +#endif +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_MEMORY_POOL_DEFN_H diff --git a/include/fruit/impl/data_structures/memory_pool.h b/include/fruit/impl/data_structures/memory_pool.h new file mode 100644 index 0000000..05e4eff --- /dev/null +++ b/include/fruit/impl/data_structures/memory_pool.h @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_MEMORY_POOL_H +#define FRUIT_MEMORY_POOL_H + +#include <vector> + +namespace fruit { +namespace impl { + +/** + * A pool of memory that never shrinks and is only deallocated on destruction. + * See also ArenaAllocator, an Allocator backed by a MemoryPool object. + */ +class MemoryPool { +private: + // 4KB - 64B. + // We don't use the full 4KB because malloc also needs to store some metadata for each block, and we want + // malloc to request <=4KB from the OS. + constexpr static const std::size_t CHUNK_SIZE = 4 * 1024 - 64; + + std::vector<void*> allocated_chunks; + // The memory block [first_free, first_free + capacity) is available for allocation + char* first_free; + std::size_t capacity; + + void destroy(); + +public: + MemoryPool(); + + MemoryPool(const MemoryPool&) = delete; + MemoryPool(MemoryPool&&); + MemoryPool& operator=(const MemoryPool&) = delete; + MemoryPool& operator=(MemoryPool&&); + ~MemoryPool(); + + /** + * Returns a chunk of memory that can hold n T objects. + * Note that this does *not* construct any T objects at that location. + */ + template <typename T> + T* allocate(std::size_t n); +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/memory_pool.defn.h> + +#endif // FRUIT_MEMORY_POOL_H diff --git a/include/fruit/impl/data_structures/packed_pointer_and_bool.defn.h b/include/fruit/impl/data_structures/packed_pointer_and_bool.defn.h new file mode 100644 index 0000000..e5365fa --- /dev/null +++ b/include/fruit/impl/data_structures/packed_pointer_and_bool.defn.h @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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/LITENSE-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 TONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRUIT_PACKED_POINTER_AND_BOOL_DEFN_H +#define FRUIT_PACKED_POINTER_AND_BOOL_DEFN_H + +#include <fruit/impl/data_structures/packed_pointer_and_bool.h> + +namespace fruit { +namespace impl { + +template <typename T> +inline std::uintptr_t PackedPointerAndBool<T>::encode(T* p, bool b) { + return reinterpret_cast<std::uintptr_t>(p) | std::uintptr_t(b); +} + +template <typename T> +inline T* PackedPointerAndBool<T>::decodePointer(std::uintptr_t value) { + return reinterpret_cast<T*>(value & ~std::uintptr_t(1)); +} + +template <typename T> +inline bool PackedPointerAndBool<T>::decodeBool(std::uintptr_t value) { + return value & 1; +} + +template <typename T> +inline PackedPointerAndBool<T>::PackedPointerAndBool(T* p, bool b) { + value = encode(p, b); +} + +template <typename T> +inline T* PackedPointerAndBool<T>::getPointer() const { + return decodePointer(value); +} + +template <typename T> +inline void PackedPointerAndBool<T>::setPointer(T* p) { + value = encode(p, decodeBool(value)); +} + +template <typename T> +inline bool PackedPointerAndBool<T>::getBool() const { + return decodeBool(value); +} + +template <typename T> +inline void PackedPointerAndBool<T>::setBool(bool b) { + value = encode(decodePointer(value), b); +} + +template <typename T> +inline bool PackedPointerAndBool<T>::operator==(const PackedPointerAndBool<T>& other) const { + return value == other.value; +} + +template <typename T> +inline bool PackedPointerAndBool<T>::operator!=(const PackedPointerAndBool<T>& other) const { + return value != other.value; +} + +} // namespace fruit +} // namespace impl + +#endif // FRUIT_PACKED_POINTER_AND_BOOL_DEFN_H diff --git a/include/fruit/impl/data_structures/packed_pointer_and_bool.h b/include/fruit/impl/data_structures/packed_pointer_and_bool.h new file mode 100644 index 0000000..8ac84de --- /dev/null +++ b/include/fruit/impl/data_structures/packed_pointer_and_bool.h @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PACKED_POINTER_AND_BOOL_H +#define FRUIT_PACKED_POINTER_AND_BOOL_H + +namespace fruit { +namespace impl { + +/** + * This class stores a T* and a bool in the space of a pointer. + * alignof(T) must be at least 2. + */ +template <typename T> +class PackedPointerAndBool { +private: + static_assert(alignof(T) >= 2, "alignof(T) must be at least 2 for the packing to be possible"); + + std::uintptr_t value; + + static std::uintptr_t encode(T* p, bool b); + static T* decodePointer(std::uintptr_t value); + static bool decodeBool(std::uintptr_t value); + +public: + PackedPointerAndBool(T* p, bool b); + + PackedPointerAndBool() = default; + + PackedPointerAndBool(const PackedPointerAndBool&) = default; + PackedPointerAndBool(PackedPointerAndBool&&) = default; + + PackedPointerAndBool& operator=(const PackedPointerAndBool&) = default; + PackedPointerAndBool& operator=(PackedPointerAndBool&&) = default; + + T* getPointer() const; + void setPointer(T* p); + + bool getBool() const; + void setBool(bool b); + + bool operator==(const PackedPointerAndBool<T>& other) const; + bool operator!=(const PackedPointerAndBool<T>& other) const; +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/packed_pointer_and_bool.defn.h> + +#endif // FRUIT_PACKED_POINTER_AND_BOOL_H diff --git a/include/fruit/impl/data_structures/semistatic_graph.defn.h b/include/fruit/impl/data_structures/semistatic_graph.defn.h new file mode 100644 index 0000000..76db0b7 --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_graph.defn.h @@ -0,0 +1,198 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_GRAPH_DEFN_H +#define SEMISTATIC_GRAPH_DEFN_H + +#include <fruit/impl/data_structures/semistatic_graph.h> + +namespace fruit { +namespace impl { + +inline bool SemistaticGraphInternalNodeId::operator==(const SemistaticGraphInternalNodeId& x) const { + return id == x.id; +} + +inline bool SemistaticGraphInternalNodeId::operator<(const SemistaticGraphInternalNodeId& x) const { + return id < x.id; +} + +template <typename NodeId, typename Node> +inline SemistaticGraph<NodeId, Node>::node_iterator::node_iterator(NodeData* itr) : itr(itr) {} + +template <typename NodeId, typename Node> +inline Node& SemistaticGraph<NodeId, Node>::node_iterator::getNode() { + FruitAssert(itr->edges_begin != 1); + return itr->node; +} + +template <typename NodeId, typename Node> +inline bool SemistaticGraph<NodeId, Node>::node_iterator::isTerminal() { + FruitAssert(itr->edges_begin != 1); + return itr->edges_begin == 0; +} + +template <typename NodeId, typename Node> +inline void SemistaticGraph<NodeId, Node>::node_iterator::setTerminal() { + FruitAssert(itr->edges_begin != 1); + itr->edges_begin = 0; +} + +template <typename NodeId, typename Node> +inline bool SemistaticGraph<NodeId, Node>::node_iterator::operator==(const node_iterator& other) const { + return itr == other.itr; +} + +template <typename NodeId, typename Node> +inline SemistaticGraph<NodeId, Node>::const_node_iterator::const_node_iterator(const NodeData* itr) : itr(itr) {} + +template<typename NodeId, typename Node> +inline SemistaticGraph<NodeId, Node>::const_node_iterator::const_node_iterator(node_iterator itr) : itr(itr.itr) {} + +template <typename NodeId, typename Node> +inline const Node& SemistaticGraph<NodeId, Node>::const_node_iterator::getNode() { + FruitAssert(itr->edges_begin != 1); + return itr->node; +} + +template <typename NodeId, typename Node> +inline bool SemistaticGraph<NodeId, Node>::const_node_iterator::isTerminal() { + FruitAssert(itr->edges_begin != 1); + return itr->edges_begin == 0; +} + +template <typename NodeId, typename Node> +inline bool SemistaticGraph<NodeId, Node>::const_node_iterator::operator==(const const_node_iterator& other) const { + return itr == other.itr; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::edge_iterator +SemistaticGraph<NodeId, Node>::node_iterator::neighborsBegin() { + FruitAssert(itr->edges_begin != 0); + FruitAssert(itr->edges_begin != 1); + return edge_iterator{reinterpret_cast<InternalNodeId*>(itr->edges_begin)}; +} + +template <typename NodeId, typename Node> +inline SemistaticGraph<NodeId, Node>::edge_iterator::edge_iterator(InternalNodeId* itr) : itr(itr) {} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator +SemistaticGraph<NodeId, Node>::edge_iterator::getNodeIterator(node_iterator nodes_begin) { + return node_iterator{nodeAtId(nodes_begin.itr, *itr)}; +} + +template <typename NodeId, typename Node> +inline void SemistaticGraph<NodeId, Node>::edge_iterator::operator++() { + ++itr; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator +SemistaticGraph<NodeId, Node>::edge_iterator::getNodeIterator(std::size_t i, node_iterator nodes_begin) { + itr += i; + return getNodeIterator(nodes_begin); +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator SemistaticGraph<NodeId, Node>::begin() { + return node_iterator{nodes.begin()}; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator SemistaticGraph<NodeId, Node>::end() { + return node_iterator{nodes.end()}; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::const_node_iterator SemistaticGraph<NodeId, Node>::end() const { + return const_node_iterator{nodes.end()}; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator SemistaticGraph<NodeId, Node>::at(NodeId nodeId) { + InternalNodeId internalNodeId = node_index_map.at(nodeId); + return node_iterator{nodeAtId(internalNodeId)}; +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::const_node_iterator +SemistaticGraph<NodeId, Node>::find(NodeId nodeId) const { + const InternalNodeId* internalNodeIdPtr = node_index_map.find(nodeId); + if (internalNodeIdPtr == nullptr) { + return const_node_iterator{nodes.end()}; + } else { + const NodeData* p = nodeAtId(*internalNodeIdPtr); + if (p->edges_begin == 1) { + return const_node_iterator{nodes.end()}; + } + return const_node_iterator{p}; + } +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::node_iterator SemistaticGraph<NodeId, Node>::find(NodeId nodeId) { + const InternalNodeId* internalNodeIdPtr = node_index_map.find(nodeId); + if (internalNodeIdPtr == nullptr) { + return node_iterator{nodes.end()}; + } else { + NodeData* p = nodeAtId(*internalNodeIdPtr); + if (p->edges_begin == 1) { + return node_iterator{nodes.end()}; + } + return node_iterator{p}; + } +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::NodeData* +SemistaticGraph<NodeId, Node>::nodeAtId(InternalNodeId internalNodeId) { + return nodeAtId(nodes.data(), internalNodeId); +} + +template <typename NodeId, typename Node> +inline const typename SemistaticGraph<NodeId, Node>::NodeData* +SemistaticGraph<NodeId, Node>::nodeAtId(InternalNodeId internalNodeId) const { + return nodeAtId(nodes.data(), internalNodeId); +} + +template <typename NodeId, typename Node> +inline typename SemistaticGraph<NodeId, Node>::NodeData* +SemistaticGraph<NodeId, Node>::nodeAtId(NodeData* nodes_begin, InternalNodeId internalNodeId) { + FruitAssert(internalNodeId.id % sizeof(NodeData) == 0); + NodeData* p = reinterpret_cast<NodeData*>(reinterpret_cast<char*>(nodes_begin) + internalNodeId.id); + // The code above is faster (the compiler doesn't have to worry about internalNodeId.id%sizeof(NodeData), that we know + // to be 0). + FruitAssert(p == nodes_begin + internalNodeId.id / sizeof(NodeData)); + return p; +} + +template <typename NodeId, typename Node> +inline const typename SemistaticGraph<NodeId, Node>::NodeData* +SemistaticGraph<NodeId, Node>::nodeAtId(const NodeData* nodes_begin, InternalNodeId internalNodeId) { + FruitAssert(internalNodeId.id % sizeof(NodeData) == 0); + const NodeData* p = reinterpret_cast<const NodeData*>(reinterpret_cast<const char*>(nodes_begin) + internalNodeId.id); + // The code above is faster (the compiler doesn't have to worry about internalNodeId.id%sizeof(NodeData), that we know + // to be 0). + FruitAssert(p == nodes_begin + internalNodeId.id / sizeof(NodeData)); + return p; +} + +} // namespace impl +} // namespace fruit + +#endif // SEMISTATIC_GRAPH_INLINES_H diff --git a/include/fruit/impl/data_structures/semistatic_graph.h b/include/fruit/impl/data_structures/semistatic_graph.h new file mode 100644 index 0000000..b48f1cf --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_graph.h @@ -0,0 +1,243 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_GRAPH_H +#define SEMISTATIC_GRAPH_H + +#include "memory_pool.h" +#include <fruit/impl/data_structures/semistatic_map.h> + +#ifdef FRUIT_EXTRA_DEBUG +#include <iostream> +#endif + +namespace fruit { +namespace impl { + +// The alignas ensures that a SemistaticGraphInternalNodeId* always has 0 in the low-order bit. +struct alignas(2) alignas(alignof(std::size_t)) SemistaticGraphInternalNodeId { + // This stores the index in the vector times sizeof(NodeData). + std::size_t id; + + bool operator==(const SemistaticGraphInternalNodeId& x) const; + bool operator<(const SemistaticGraphInternalNodeId& x) const; +}; + +/** + * A direct graph implementation where most of the graph is fixed at construction time, but a few nodes and edges can be + * added + * later. + * + * Also, nodes can either be normal nodes or terminal nodes. Terminal nodes can't have outgoing edges. Note that a node + * with no + * outgoing edges may or may not be marked as terminal. + * + * While insertion of elements after construction is supported, inserting or changing the neighbors of more than O(1) + * nodes + * after construction will raise the cost of any further operations to more than O(1). + * + * Even though adding nodes/edges after construction is inefficient, it is efficient to turn non-terminal nodes into + * terminal ones + * (and therefore removing all the outgoing edges from the node) after construction. + * + * NodeId and Node must be default constructible and trivially copyable. + */ +template <typename NodeId, typename Node> +class SemistaticGraph { +private: + using InternalNodeId = SemistaticGraphInternalNodeId; + + // The node data for nodeId is in nodes[node_index_map.at(nodeId)/sizeof(NodeData)]. + // To avoid hash table lookups, the edges in edges_storage are stored as indexes of `nodes' instead of as NodeIds. + // node_index_map contains all known NodeIds, including ones known only due to an outgoing edge ending there from + // another node. + SemistaticMap<NodeId, InternalNodeId> node_index_map; + + struct NodeData { +#ifdef FRUIT_EXTRA_DEBUG + NodeId key; +#endif + + public: + // If edges_begin==0, this is a terminal node. + // If edges_begin==1, this node doesn't exist, it's just referenced by another node. + // Otherwise, reinterpret_cast<InternalNodeId*>(edges_begin) is the beginning of the edges range. + std::uintptr_t edges_begin; + + // An explicit "public" specifier here prevents the compiler from reordering the fields. + // We want the edges_begin field to be stored first because that's what we're going to branch on. + public: + Node node; + }; + + std::size_t first_unused_index; + + FixedSizeVector<NodeData> nodes; + + // Stores vectors of edges as contiguous chunks of node IDs. + // The NodeData elements in `nodes' contain indexes into this vector (stored as already multiplied by + // sizeof(NodeData)). + // The first element is unused. + FixedSizeVector<InternalNodeId> edges_storage; + +#ifdef FRUIT_EXTRA_DEBUG + template <typename NodeIter> + void printGraph(NodeIter first, NodeIter last); +#endif + + NodeData* nodeAtId(InternalNodeId internalNodeId); + const NodeData* nodeAtId(InternalNodeId internalNodeId) const; + + static NodeData* nodeAtId(NodeData* nodes_begin, InternalNodeId internalNodeId); + static const NodeData* nodeAtId(const NodeData* nodes_begin, InternalNodeId internalNodeId); + +public: + class edge_iterator; + + class node_iterator { + private: + NodeData* itr; + + friend class SemistaticGraph<NodeId, Node>; + + node_iterator(NodeData* itr); + + public: + Node& getNode(); + + bool isTerminal(); + + // Turns the node into a terminal node, also removing all the deps. + void setTerminal(); + + // Assumes !isTerminal(). + // neighborsEnd() is NOT provided/stored for efficiency, the client code is expected to know the number of + // neighbors. + edge_iterator neighborsBegin(); + + bool operator==(const node_iterator&) const; + }; + + class const_node_iterator { + private: + const NodeData* itr; + + friend class SemistaticGraph<NodeId, Node>; + + const_node_iterator(const NodeData* itr); + + public: + const_node_iterator(node_iterator itr); + + const Node& getNode(); + + bool isTerminal(); + + bool operator==(const const_node_iterator&) const; + }; + + + class edge_iterator { + private: + // Iterator on edges_storage. + InternalNodeId* itr; + + friend class SemistaticGraph<NodeId, Node>; + friend class SemistaticGraph<NodeId, Node>::node_iterator; + + edge_iterator(InternalNodeId* itr); + + public: + // getNodeIterator(graph.nodes.begin()) returns the first neighbor. + node_iterator getNodeIterator(node_iterator nodes_begin); + + void operator++(); + + // Equivalent to i times operator++ followed by getNodeIterator(nodes_begin). + node_iterator getNodeIterator(std::size_t i, node_iterator nodes_begin); + }; + + // Constructs an *invalid* graph (as if this graph was just moved from). + SemistaticGraph() = default; + + /** + * A value x obtained dereferencing a NodeIter::value_type must support the following operations: + * - x.getId(), returning a NodeId + * - x.getValue(), returning a Node + * - x.isTerminal(), returning a bool + * - x.getEdgesBegin() and x.getEdgesEnd(), that if !x.isTerminal() define a range of values of type NodeId + * (the outgoing edges). + * + * This constructor is *not* defined in semistatic_graph.templates.h, but only in semistatic_graph.cc. + * All instantiations must have a matching instantiation in semistatic_graph.cc. + * + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + template <typename NodeIter> + SemistaticGraph(NodeIter first, NodeIter last, MemoryPool& memory_pool); + + SemistaticGraph(SemistaticGraph&&) = default; + SemistaticGraph(const SemistaticGraph&) = delete; + + /** + * Creates a copy of x with the additional nodes in [first, last). The requirements on NodeIter as the same as for the + * 2-arg + * constructor. + * The nodes in [first, last) must NOT be already in x, but can be neighbors of nodes in x. + * The new graph will share data with `x', so must be destroyed before `x' is destroyed. + * Also, after this is called, `x' must not be modified until this object has been destroyed. + * + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + template <typename NodeIter> + SemistaticGraph(const SemistaticGraph& x, NodeIter first, NodeIter last, MemoryPool& memory_pool); + + ~SemistaticGraph(); + + SemistaticGraph& operator=(const SemistaticGraph&) = delete; + SemistaticGraph& operator=(SemistaticGraph&&) = default; + + // The result is unspecified. The only guarantee is that it's the right value to pass to edge_iterator's + // getNodeIterator() methods. + node_iterator begin(); + + node_iterator end(); + const_node_iterator end() const; + + // Precondition: `nodeId' must exist in the graph. + // Unlike std::map::at(), this yields undefined behavior if the precondition isn't satisfied (instead of throwing). + node_iterator at(NodeId nodeId); + + // Prefer using at() when possible, this is slightly slower. + // Returns end() if the node ID was not found. + node_iterator find(NodeId nodeId); + const_node_iterator find(NodeId nodeId) const; + +#ifdef FRUIT_EXTRA_DEBUG + // Emits a runtime error if some node was not created but there is an edge pointing to it. + void checkFullyConstructed(); +#endif +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/semistatic_graph.defn.h> + +// semistatic_graph.templates.h is not included here to limit the transitive includes. Include it explicitly (in .cpp +// files). + +#endif // SEMISTATIC_GRAPH_H diff --git a/include/fruit/impl/data_structures/semistatic_graph.templates.h b/include/fruit/impl/data_structures/semistatic_graph.templates.h new file mode 100644 index 0000000..e55be4c --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_graph.templates.h @@ -0,0 +1,228 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_GRAPH_TEMPLATES_H +#define SEMISTATIC_GRAPH_TEMPLATES_H + +#ifndef IN_FRUIT_CPP_FILE +#error "Fruit .template.h file included in non-cpp file." +#endif + +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/fixed_size_vector.templates.h> +#include <fruit/impl/data_structures/memory_pool.h> +#include <fruit/impl/data_structures/semistatic_graph.h> +#include <fruit/impl/data_structures/semistatic_map.templates.h> +#include <fruit/impl/util/hash_helpers.h> + +#ifdef FRUIT_EXTRA_DEBUG +#include <iostream> +#endif + +namespace fruit { +namespace impl { + +template <typename Iter, std::size_t index_increment> +struct indexing_iterator { + Iter iter; + std::size_t index; + + void operator++() { + ++iter; + index += index_increment; + } + + auto operator*() -> decltype(std::make_pair(*iter, SemistaticGraphInternalNodeId{index})) { + return std::make_pair(*iter, SemistaticGraphInternalNodeId{index}); + } +}; + +#ifdef FRUIT_EXTRA_DEBUG +template <typename NodeId, typename Node> +template <typename NodeIter> +void SemistaticGraph<NodeId, Node>::printGraph(NodeIter first, NodeIter last) { + std::cerr << "Constructed injection graph with nodes:" << std::endl; + for (NodeIter i = first; i != last; ++i) { + std::cerr << i->getId() << " -> "; + if (i->isTerminal()) { + std::cerr << "(none, terminal)"; + } else { + std::cerr << "{"; + for (auto j = i->getEdgesBegin(); j != i->getEdgesEnd(); ++j) { + if (j != i->getEdgesBegin()) { + std::cerr << ", "; + } + std::cerr << *j; + } + std::cerr << "}"; + } + std::cerr << std::endl; + } + std::cerr << std::endl; +} +#endif // FRUIT_EXTRA_DEBUG + +template <typename NodeId, typename Node> +template <typename NodeIter> +SemistaticGraph<NodeId, Node>::SemistaticGraph(NodeIter first, NodeIter last, MemoryPool& memory_pool) { + std::size_t num_edges = 0; + // Step 1: assign IDs to all nodes, fill node_index_map and set first_unused_index. + HashSetWithArenaAllocator<NodeId> node_ids = createHashSetWithArenaAllocator<NodeId>(last - first, memory_pool); + for (NodeIter i = first; i != last; ++i) { + node_ids.insert(i->getId()); + if (!i->isTerminal()) { + for (auto j = i->getEdgesBegin(); j != i->getEdgesEnd(); ++j) { + node_ids.insert(*j); + ++num_edges; + } + } + } + + using itr_t = typename HashSetWithArenaAllocator<NodeId>::iterator; + node_index_map = SemistaticMap<NodeId, InternalNodeId>( + indexing_iterator<itr_t, sizeof(NodeData)>{node_ids.begin(), 0}, node_ids.size(), memory_pool); + + first_unused_index = node_ids.size(); + + // Step 2: fill `nodes' and edges_storage. + + // Note that not all of these will be assigned in the loop below. + nodes = FixedSizeVector<NodeData>(first_unused_index, NodeData{ +#ifdef FRUIT_EXTRA_DEBUG + NodeId(), +#endif + 1, Node()}); + + // edges_storage[0] is unused, that's the reason for the +1 + edges_storage = FixedSizeVector<InternalNodeId>(num_edges + 1); + edges_storage.push_back(InternalNodeId()); + + for (NodeIter i = first; i != last; ++i) { + NodeData& nodeData = *nodeAtId(node_index_map.at(i->getId())); + nodeData.node = i->getValue(); + if (i->isTerminal()) { + nodeData.edges_begin = 0; + } else { + nodeData.edges_begin = reinterpret_cast<std::uintptr_t>(edges_storage.data() + edges_storage.size()); + for (auto j = i->getEdgesBegin(); j != i->getEdgesEnd(); ++j) { + InternalNodeId other_node_id = node_index_map.at(*j); + edges_storage.push_back(other_node_id); + } + } + } + +#ifdef FRUIT_EXTRA_DEBUG + printGraph(first, last); +#endif +} + +template <typename NodeId, typename Node> +template <typename NodeIter> +SemistaticGraph<NodeId, Node>::SemistaticGraph(const SemistaticGraph& x, NodeIter first, NodeIter last, + MemoryPool& memory_pool) + : first_unused_index(x.first_unused_index) { + + // TODO: The code below is very similar to the other constructor, extract the common parts in separate functions. + + std::size_t num_new_edges = 0; + + // Step 1: assign IDs to new nodes, fill `node_index_map' and update `first_unused_index'. + + // Step 1a: collect all new node IDs. + using node_ids_elem_t = std::pair<NodeId, InternalNodeId>; + using node_ids_t = std::vector<node_ids_elem_t, ArenaAllocator<node_ids_elem_t>>; + node_ids_t node_ids = node_ids_t(ArenaAllocator<node_ids_elem_t>(memory_pool)); + for (NodeIter i = first; i != last; ++i) { + if (x.node_index_map.find(i->getId()) == nullptr) { + node_ids.push_back(std::make_pair(i->getId(), InternalNodeId())); + } + if (!i->isTerminal()) { + for (auto j = i->getEdgesBegin(); j != i->getEdgesEnd(); ++j) { + if (x.node_index_map.find(*j) == nullptr) { + node_ids.push_back(std::make_pair(*j, InternalNodeId())); + } + ++num_new_edges; + } + } + } + + // Step 1b: remove duplicates. + std::sort(node_ids.begin(), node_ids.end()); + node_ids.erase(std::unique(node_ids.begin(), node_ids.end()), node_ids.end()); + + // Step 1c: assign new IDs. + for (auto& p : node_ids) { + p.second = InternalNodeId{first_unused_index * sizeof(NodeData)}; + ++first_unused_index; + } + + // Step 1d: actually populate node_index_map. + node_index_map = SemistaticMap<NodeId, InternalNodeId>(x.node_index_map, std::move(node_ids)); + + // Step 2: fill `nodes' and `edges_storage' + nodes = FixedSizeVector<NodeData>(x.nodes, first_unused_index); + // Note that the loop below does not necessarily assign all of these. + for (std::size_t i = x.nodes.size(); i < first_unused_index; ++i) { + nodes.push_back(NodeData{ +#ifdef FRUIT_EXTRA_DEBUG + NodeId(), +#endif + 1, Node()}); + } + + // edges_storage[0] is unused, that's the reason for the +1 + edges_storage = FixedSizeVector<InternalNodeId>(num_new_edges + 1); + edges_storage.push_back(InternalNodeId()); + + for (NodeIter i = first; i != last; ++i) { + NodeData& nodeData = *nodeAtId(node_index_map.at(i->getId())); + nodeData.node = i->getValue(); + if (i->isTerminal()) { + nodeData.edges_begin = 0; + } else { + nodeData.edges_begin = reinterpret_cast<std::uintptr_t>(edges_storage.data() + edges_storage.size()); + for (auto j = i->getEdgesBegin(); j != i->getEdgesEnd(); ++j) { + InternalNodeId otherNodeId = node_index_map.at(*j); + edges_storage.push_back(otherNodeId); + } + } + } + +#ifdef FRUIT_EXTRA_DEBUG + printGraph(first, last); +#endif +} + +#ifdef FRUIT_EXTRA_DEBUG +template <typename NodeId, typename Node> +void SemistaticGraph<NodeId, Node>::checkFullyConstructed() { + for (NodeData& data : nodes) { + if (data.edges_begin == 1) { + std::cerr << "Fruit bug: the dependency graph was not fully constructed." << std::endl; + abort(); + } + } +} +#endif // !NDEBUG + +// This is here so that we don't have to include fixed_size_vector.templates.h in fruit.h. +template <typename NodeId, typename Node> +SemistaticGraph<NodeId, Node>::~SemistaticGraph() {} + +} // namespace impl +} // namespace fruit + +#endif // SEMISTATIC_GRAPH_TEMPLATES_H diff --git a/include/fruit/impl/data_structures/semistatic_map.defn.h b/include/fruit/impl/data_structures/semistatic_map.defn.h new file mode 100644 index 0000000..7170f23 --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_map.defn.h @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_MAP_DEFN_H +#define SEMISTATIC_MAP_DEFN_H + +#include <fruit/impl/data_structures/semistatic_map.h> + +namespace fruit { +namespace impl { + +template <typename Key, typename Value> +inline SemistaticMap<Key, Value>::HashFunction::HashFunction() : a(0), shift(0) {} + +template <typename Key, typename Value> +inline typename SemistaticMap<Key, Value>::Unsigned SemistaticMap<Key, Value>::HashFunction::hash(Unsigned x) const { + return (Unsigned)(a * x) >> shift; +} + +template <typename Key, typename Value> +inline typename SemistaticMap<Key, Value>::Unsigned SemistaticMap<Key, Value>::hash(const Key& key) const { + return hash_function.hash(std::hash<typename std::remove_cv<Key>::type>()(key)); +} + +} // namespace impl +} // namespace fruit + +#endif // SEMISTATIC_MAP_DEFN_H diff --git a/include/fruit/impl/data_structures/semistatic_map.h b/include/fruit/impl/data_structures/semistatic_map.h new file mode 100644 index 0000000..aa5bf3a --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_map.h @@ -0,0 +1,130 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_MAP_H +#define SEMISTATIC_MAP_H + +#include <fruit/impl/data_structures/fixed_size_vector.h> + +#include "arena_allocator.h" +#include "memory_pool.h" +#include <climits> +#include <cstdint> +#include <limits> +#include <vector> + +namespace fruit { +namespace impl { + +/** + * Provides a subset of the interface of std::map, and also has these additional assumptions: + * - Key must be default constructible and trivially copyable + * - Value must be default constructible and trivially copyable + * + * Also, while insertion of elements after construction is supported, inserting more than O(1) elements + * after construction will raise the cost of any further lookups to more than O(1). + */ +template <typename Key, typename Value> +class SemistaticMap { +private: + using Unsigned = std::uintptr_t; + using NumBits = unsigned char; + using value_type = std::pair<Key, Value>; + + static constexpr unsigned char beta = 4; + + static_assert( + std::numeric_limits<NumBits>::max() >= sizeof(Unsigned) * CHAR_BIT, + "An unsigned char is not enough to contain the number of bits in your platform. Please report this issue."); + + struct HashFunction { + Unsigned a; + NumBits shift; // shift==(sizeof(Unsigned)*CHAR_BIT - num_bits) + + HashFunction(); + + Unsigned hash(Unsigned x) const; + }; + + static NumBits pickNumBits(std::size_t n); + + struct CandidateValuesRange { + value_type* begin; + value_type* end; + }; + + HashFunction hash_function; + // Given a key x, if p=lookup_table[hash_function.hash(x)] the candidate places for x are [p.first, p.second). These + // pointers + // point to the values[] vector, but it might be either the one of this object or the one of an object that was + // shallow-copied + // into this one. + FixedSizeVector<CandidateValuesRange> lookup_table; + FixedSizeVector<value_type> values; + + Unsigned hash(const Key& key) const; + + // Inserts a range [elems_begin, elems_end) of new (key,value) pairs with hash h. The keys must not exist in the map. + // Before calling this, ensure that the capacity of `values' is sufficient to contain the new values without + // re-allocating. + void insert(std::size_t h, const value_type* elems_begin, const value_type* elems_end); + +public: + // Constructs an *invalid* map (as if this map was just moved from). + SemistaticMap() = default; + + /** + * Iter must be a forward iterator with value type std::pair<Key, Value>. + * + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + template <typename Iter> + SemistaticMap(Iter begin, std::size_t num_values, MemoryPool& memory_pool); + + // Creates a shallow copy of `map' with the additional elements in new_elements. + // The keys in new_elements must be unique and must not be present in `map'. + // The new map will share data with `map', so must be destroyed before `map' is destroyed. + // NOTE: If more than O(1) elements are added, calls to at() and find() on the result will *not* be O(1). + // This is O(new_elements.size()*log(new_elements.size())). + SemistaticMap(const SemistaticMap<Key, Value>& map, + std::vector<value_type, ArenaAllocator<value_type>>&& new_elements); + + SemistaticMap(SemistaticMap&&) = default; + SemistaticMap(const SemistaticMap&) = delete; + + ~SemistaticMap(); + + SemistaticMap& operator=(SemistaticMap&&) = default; + SemistaticMap& operator=(const SemistaticMap&) = delete; + + // Precondition: `key' must exist in the map. + // Unlike std::map::at(), this yields undefined behavior if the precondition isn't satisfied (instead of throwing). + const Value& at(Key key) const; + + // Prefer using at() when possible, this is slightly slower. + // Returns nullptr if the key was not found. + const Value* find(Key key) const; +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/data_structures/semistatic_map.defn.h> + +// semistatic_map.templates.h is NOT included here to reduce the transitive includes. Include it when needed (in .cpp +// files). + +#endif // SEMISTATIC_MAP_H diff --git a/include/fruit/impl/data_structures/semistatic_map.templates.h b/include/fruit/impl/data_structures/semistatic_map.templates.h new file mode 100644 index 0000000..f9034c3 --- /dev/null +++ b/include/fruit/impl/data_structures/semistatic_map.templates.h @@ -0,0 +1,200 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 SEMISTATIC_MAP_TEMPLATES_H +#define SEMISTATIC_MAP_TEMPLATES_H + +#ifndef IN_FRUIT_CPP_FILE +#error "Fruit .template.h file included in non-cpp file." +#endif + +#include <algorithm> +#include <cassert> +#include <chrono> +#include <random> +#include <utility> +// This include is not necessary for GCC/Clang, but it's necessary for MSVC. +#include <numeric> + +#include <fruit/impl/data_structures/semistatic_map.h> + +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/fixed_size_vector.templates.h> +#include <fruit/impl/fruit_assert.h> + +namespace fruit { +namespace impl { + +template <typename Key, typename Value> +template <typename Iter> +SemistaticMap<Key, Value>::SemistaticMap(Iter values_begin, std::size_t num_values, MemoryPool& memory_pool) { + NumBits num_bits = pickNumBits(num_values); + std::size_t num_buckets = size_t(1) << num_bits; + + FixedSizeVector<Unsigned, ArenaAllocator<Unsigned>> count(num_buckets, 0, ArenaAllocator<Unsigned>(memory_pool)); + + hash_function.shift = (sizeof(Unsigned) * CHAR_BIT - num_bits); + + // The cast is a no-op in some systems (e.g. GCC and Clang under Linux 64bit) but it's needed in other systems (e.g. + // MSVC). + unsigned seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count(); + std::default_random_engine random_generator(seed); + std::uniform_int_distribution<Unsigned> random_distribution; + + while (1) { + hash_function.a = random_distribution(random_generator); + + Iter itr = values_begin; + for (std::size_t i = 0; i < num_values; ++i, ++itr) { + Unsigned& this_count = count[hash((*itr).first)]; + ++this_count; + if (this_count == beta) { + goto pick_another; + } + } + break; + + pick_another: + for (std::size_t i = 0; i < num_buckets; ++i) { + count[i] = 0; + } + } + + values = FixedSizeVector<value_type>(num_values, value_type()); + + std::partial_sum(count.begin(), count.end(), count.begin()); + lookup_table = FixedSizeVector<CandidateValuesRange>(count.size()); + for (Unsigned n : count) { + lookup_table.push_back(CandidateValuesRange{values.data() + n, values.data() + n}); + } + + // At this point lookup_table[h] is the number of keys in [first, last) that have a hash <=h. + // Note that even though we ensure this after construction, it is not maintained by insert() so it's not an invariant. + + Iter itr = values_begin; + for (std::size_t i = 0; i < num_values; ++i, ++itr) { + value_type*& first_value_ptr = lookup_table[hash((*itr).first)].begin; + --first_value_ptr; + FruitAssert(values.data() <= first_value_ptr); + FruitAssert(first_value_ptr < values.data() + values.size()); + *first_value_ptr = *itr; + } +} + +template <typename Key, typename Value> +SemistaticMap<Key, Value>::SemistaticMap(const SemistaticMap<Key, Value>& map, + std::vector<value_type, ArenaAllocator<value_type>>&& new_elements) + : hash_function(map.hash_function), lookup_table(map.lookup_table, map.lookup_table.size()) { + + // Sort by hash. + std::sort(new_elements.begin(), new_elements.end(), + [this](const value_type& x, const value_type& y) { return hash(x.first) < hash(y.first); }); + + std::size_t num_additional_values = new_elements.size(); + // Add the space needed to store copies of the old buckets. + for (auto itr = new_elements.begin(), itr_end = new_elements.end(); itr != itr_end; /* no increment */) { + Unsigned h = hash(itr->first); + auto p = map.lookup_table[h]; + num_additional_values += (p.end - p.begin); + for (; itr != itr_end && hash(itr->first) == h; ++itr) { + } + } + + values = FixedSizeVector<value_type>(num_additional_values); + + // Now actually perform the insertions. + + if (new_elements.empty()) { + // This is to workaround a bug in the STL shipped with GCC <4.8.2, where calling data() on an + // empty vector causes undefined behavior (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59829). + return; + } + for (value_type *itr = new_elements.data(), *itr_end = new_elements.data() + new_elements.size(); itr != itr_end; + /* no increment */) { + Unsigned h = hash(itr->first); + auto p = map.lookup_table[h]; + num_additional_values += (p.end - p.begin); + value_type* first = itr; + for (; itr != itr_end && hash(itr->first) == h; ++itr) { + } + value_type* last = itr; + insert(h, first, last); + } +} + +template <typename Key, typename Value> +void SemistaticMap<Key, Value>::insert(std::size_t h, const value_type* elems_begin, const value_type* elems_end) { + + value_type* old_bucket_begin = lookup_table[h].begin; + value_type* old_bucket_end = lookup_table[h].end; + + lookup_table[h].begin = values.data() + values.size(); + + // Step 1: re-insert all keys with the same hash at the end (if any). + for (value_type* p = old_bucket_begin; p != old_bucket_end; ++p) { + values.push_back(*p); + } + + // Step 2: also insert the new keys and values + for (auto itr = elems_begin; itr != elems_end; ++itr) { + values.push_back(*itr); + } + + lookup_table[h].end = values.data() + values.size(); + + // The old sequence is no longer pointed to by any index in the lookup table, but recompacting the vectors would be + // too slow. +} + +template <typename Key, typename Value> +const Value& SemistaticMap<Key, Value>::at(Key key) const { + Unsigned h = hash(key); + for (const value_type* p = lookup_table[h].begin; /* p!=lookup_table[h].end but no need to check */; ++p) { + FruitAssert(p != lookup_table[h].end); + if (p->first == key) { + return p->second; + } + } +} + +template <typename Key, typename Value> +const Value* SemistaticMap<Key, Value>::find(Key key) const { + Unsigned h = hash(key); + for (const value_type *p = lookup_table[h].begin, *p_end = lookup_table[h].end; p != p_end; ++p) { + if (p->first == key) { + return &(p->second); + } + } + return nullptr; +} + +template <typename Key, typename Value> +typename SemistaticMap<Key, Value>::NumBits SemistaticMap<Key, Value>::pickNumBits(std::size_t n) { + NumBits result = 1; + while ((std::size_t(1) << result) < n) { + ++result; + } + return result; +} + +// This is here so that we don't have to include fixed_size_vector.templates.h in fruit.h. +template <typename Key, typename Value> +SemistaticMap<Key, Value>::~SemistaticMap() {} + +} // namespace impl +} // namespace fruit + +#endif // SEMISTATIC_MAP_TEMPLATES_H diff --git a/include/fruit/impl/fruit-config.h b/include/fruit/impl/fruit-config.h new file mode 100644 index 0000000..f9c3b28 --- /dev/null +++ b/include/fruit/impl/fruit-config.h @@ -0,0 +1,90 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_CONFIG_H +#define FRUIT_CONFIG_H + +#include <fruit/impl/fruit-config-base.h> + +#if FRUIT_HAS_STD_IS_TRIVIALLY_COPYABLE +#if FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) \ + (std::is_trivially_copyable<T>::value || (std::is_empty<T>::value && std::is_trivially_copy_constructible<T>::value)) +#else // !FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) (std::is_trivially_copyable<T>::value) +#endif // FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#elif FRUIT_HAS_IS_TRIVIALLY_COPYABLE +#if FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) \ + (__is_trivially_copyable(T) || (std::is_empty<T>::value && std::is_trivially_copy_constructible<T>::value)) +#else // !FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) (__is_trivially_copyable(T)) +#endif // FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#elif FRUIT_HAS_HAS_TRIVIAL_COPY +// The compiler doesn't support __is_trivially_copyable (nor is std::is_trivially_copyable +// supported by the library). We use this check as a proxy, but it's not exactly the same thing. +#if FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) \ + (__has_trivial_copy(T) || (std::is_empty<T>::value && std::is_trivially_copy_constructible<T>::value)) +#else // !FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) (__has_trivial_copy(T)) +#endif // FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#else +// We use the standard one, but most likely it won't work. +#if FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) \ + (std::is_trivially_copyable<T>::value || (std::is_empty<T>::value && std::is_trivially_copy_constructible<T>::value)) +#else // !FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#define FRUIT_IS_TRIVIALLY_COPYABLE(T) (std::is_trivially_copyable<T>::value) +#endif // FRUIT_HAS_STD_IS_TRIVIALLY_COPY_CONSTRUCTIBLE +#endif + +#if FRUIT_HAS_ALWAYS_INLINE_ATTRIBUTE +#define FRUIT_ALWAYS_INLINE __attribute__((always_inline)) +#elif FRUIT_HAS_FORCEINLINE +#define FRUIT_ALWAYS_INLINE __forceinline +#else +#define FRUIT_ALWAYS_INLINE +#endif + +#if FRUIT_HAS_GCC_ATTRIBUTE_DEPRECATED +#define FRUIT_DEPRECATED_DECLARATION(...) __VA_ARGS__ __attribute__((deprecated)) +// Marking the declaration is enough. +#define FRUIT_DEPRECATED_DEFINITION(...) __VA_ARGS__ +#elif FRUIT_HAS_DECLSPEC_DEPRECATED +#define FRUIT_DEPRECATED_DECLARATION(...) __declspec(deprecated) __VA_ARGS__ +#define FRUIT_DEPRECATED_DEFINITION(...) __declspec(deprecated) __VA_ARGS__ +// We use this only if the above two are not supported, because some compilers "support" this syntax (i.e., it compiles) +// but they just ignore the attribute. +#elif FRUIT_HAS_ATTRIBUTE_DEPRECATED +#define FRUIT_DEPRECATED_DECLARATION(...) [[deprecated]] __VA_ARGS__ +#define FRUIT_DEPRECATED_DEFINITION(...) [[deprecated]] __VA_ARGS__ +#else +#define FRUIT_DEPRECATED_DECLARATION(...) __VA_ARGS__ +#define FRUIT_DEPRECATED_DEFINITION(...) __VA_ARGS__ +#endif + +#if FRUIT_HAS_MSVC_ASSUME +#define FRUIT_UNREACHABLE \ + FruitAssert(false); \ + __assume(0) +#elif FRUIT_HAS_BUILTIN_UNREACHABLE +#define FRUIT_UNREACHABLE \ + FruitAssert(false); \ + __builtin_unreachable() +#endif + +#endif // FRUIT_CONFIG_H diff --git a/include/fruit/impl/fruit_assert.h b/include/fruit/impl/fruit_assert.h new file mode 100644 index 0000000..b2b4b0d --- /dev/null +++ b/include/fruit/impl/fruit_assert.h @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_ASSERT_H +#define FRUIT_ASSERT_H + +#ifdef FRUIT_EXTRA_DEBUG +#include <cassert> +// Usage: FruitStaticAssert(MetaExpr) +#define FruitStaticAssert(...) static_assert(fruit::impl::meta::Eval<__VA_ARGS__>::value, "") +#define FruitAssert(...) assert(__VA_ARGS__) + +#else +// We still define this, otherwise some compilers (e.g. Clang 3.9) would complain that there's a stray ';' when this is +// used inside a struct/class definition. +#define FruitStaticAssert(...) static_assert(true, "") +#define FruitAssert(...) + +#endif + +#define FruitDelegateCheck(...) static_assert(true || sizeof(fruit::impl::meta::Eval<__VA_ARGS__>), "") +#define FruitDisplayErrorForType(...) static_assert(false && sizeof(fruit::impl::meta::Eval<__VA_ARGS__>), "") + +#endif // FRUIT_ASSERT_H diff --git a/include/fruit/impl/fruit_internal_forward_decls.h b/include/fruit/impl/fruit_internal_forward_decls.h new file mode 100644 index 0000000..4883470 --- /dev/null +++ b/include/fruit/impl/fruit_internal_forward_decls.h @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_FRUIT_INTERNAL_FORWARD_DECLS_H +#define FRUIT_FRUIT_INTERNAL_FORWARD_DECLS_H + +#include <memory> +#include <vector> + +namespace fruit { +namespace impl { + +class ComponentStorage; +class NormalizedComponentStorage; +class InjectorStorage; +struct TypeId; +struct ComponentStorageEntry; +struct NormalizedBinding; +struct NormalizedMultibinding; +struct NormalizedMultibindingSet; +struct InjectorAccessorForTests; + +template <typename Component, typename... Args> +class LazyComponentImpl; + +namespace meta { +template <typename... PreviousBindings> +struct OpForComponent; +} + +} // namespace impl + +} // namespace fruit + +#endif // FRUIT_FRUIT_INTERNAL_FORWARD_DECLS_H diff --git a/include/fruit/impl/injection_debug_errors.h b/include/fruit/impl/injection_debug_errors.h new file mode 100644 index 0000000..3efdc5e --- /dev/null +++ b/include/fruit/impl/injection_debug_errors.h @@ -0,0 +1,110 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTION_DEBUG_ERRORS +#define FRUIT_INJECTION_DEBUG_ERRORS + +#include <fruit/impl/injection_errors.h> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename... MissingProvides> +struct ComponentDoesNotEntailDueToProvidesError { + static_assert(AlwaysFalse<MissingProvides...>::value, ""); +}; + +struct ComponentDoesNotEntailDueToProvidesErrorTag { + template <typename... MissingProvides> + using apply = ComponentDoesNotEntailDueToProvidesError<MissingProvides...>; +}; + +template <typename... MissingInterfaceBindings> +struct ComponentDoesNotEntailDueToInterfaceBindingsError { + static_assert(AlwaysFalse<MissingInterfaceBindings...>::value, ""); +}; + +struct ComponentDoesNotEntailDueToInterfaceBindingsErrorTag { + template <typename... MissingInterfaceBindings> + using apply = ComponentDoesNotEntailDueToInterfaceBindingsError<MissingInterfaceBindings...>; +}; + +template <typename... AdditionalRequirements> +struct ComponentDoesNotEntailDueToRequirementsError { + static_assert(AlwaysFalse<AdditionalRequirements...>::value, ""); +}; + +struct ComponentDoesNotEntailDueToRequirementsErrorTag { + template <typename... AdditionalRequirements> + using apply = ComponentDoesNotEntailDueToProvidesError<AdditionalRequirements...>; +}; + +template <typename Deps, typename CandidateEntailedDeps> +struct ComponentDoesNotEntailDueToIncompatibleDepsError { + static_assert(AlwaysFalse<Deps>::value, ""); +}; + +struct ComponentDoesNotEntailDueToIncompatibleDepsErrorTag { + template <typename Deps, typename CandidateEntailedDeps> + using apply = ComponentDoesNotEntailDueToIncompatibleDepsError<Deps, CandidateEntailedDeps>; +}; + +template <typename... RequirementsWithConstMismatch> +struct ComponentDoesNotEntailDueToDifferentConstnessOfRequirementsError { + static_assert(AlwaysFalse<RequirementsWithConstMismatch...>::value, ""); +}; + +struct ComponentDoesNotEntailDueToDifferentConstnessOfRequirementsErrorTag { + template <typename... RequirementsWithConstMismatch> + using apply = ComponentDoesNotEntailDueToDifferentConstnessOfRequirementsError<RequirementsWithConstMismatch...>; +}; + +template <typename... ProvidesWithConstMismatch> +struct ComponentDoesNotEntailDueToDifferentConstnessOfProvidesError { + static_assert(AlwaysFalse<ProvidesWithConstMismatch...>::value, ""); +}; + +struct ComponentDoesNotEntailDueToDifferentConstnessOfProvidesErrorTag { + template <typename... ProvidesWithConstMismatch> + using apply = ComponentDoesNotEntailDueToDifferentConstnessOfProvidesError<ProvidesWithConstMismatch...>; +}; + +template <typename ProofTh, typename ForestThs> +struct ProofNotEntailedByForestBecauseThNotFoundError { + static_assert(AlwaysFalse<ProofTh>::value, ""); +}; + +struct ProofNotEntailedByForestBecauseThNotFoundErrorTag { + template <typename ProofTh, typename ForestThs> + using apply = ProofNotEntailedByForestBecauseThNotFoundError<ProofTh, ForestThs>; +}; + +template <typename ForestHps, typename ProofHps, typename Difference> +struct ProofNotEntailedByForestBecauseHpsNotASubsetError { + static_assert(AlwaysFalse<ForestHps>::value, ""); +}; + +struct ProofNotEntailedByForestBecauseHpsNotASubsetErrorTag { + template <typename ForestHps, typename ProofHps, typename Difference> + using apply = ProofNotEntailedByForestBecauseHpsNotASubsetError<ForestHps, ProofHps, Difference>; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_INJECTION_DEBUG_ERRORS diff --git a/include/fruit/impl/injection_errors.h b/include/fruit/impl/injection_errors.h new file mode 100644 index 0000000..06a0344 --- /dev/null +++ b/include/fruit/impl/injection_errors.h @@ -0,0 +1,577 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTION_ERRORS +#define FRUIT_INJECTION_ERRORS + +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/meta/set.h> + +namespace fruit { +namespace impl { + +template <typename... Ts> +struct AlwaysFalse { + static constexpr bool value = false; +}; + +template <typename T> +struct NoBindingFoundError { + static_assert(AlwaysFalse<T>::value, "No explicit binding nor C::Inject definition was found for T."); +}; + +template <typename T, typename C> +struct NoBindingFoundForAbstractClassError { + static_assert(AlwaysFalse<T>::value, + "No explicit binding was found for T, and note that C is an abstract class (so Fruit can't auto-inject " + "this type, " + "even if it has an Inject typedef or an INJECT annotation that will be ignored)."); +}; + +template <typename... Ts> +struct RepeatedTypesError { + static_assert(AlwaysFalse<Ts...>::value, + "A type was specified more than once. Requirements and provided types should be unique."); +}; + +template <typename... TypesInLoop> +struct SelfLoopError { + static_assert(AlwaysFalse<TypesInLoop...>::value, + "Found a loop in the dependencies! The types in TypesInLoop all depend on the next, and the " + "last one depends on the first."); +}; + +template <typename T, typename C> +struct NonClassTypeError { + static_assert(AlwaysFalse<T>::value, "A non-class type T was specified. Use C instead."); +}; + +template <typename AnnotatedT, typename T> +struct AnnotatedTypeError { + static_assert(AlwaysFalse<T>::value, "An annotated type was specified where a non-annotated type was expected."); +}; + +template <typename C> +struct TypeAlreadyBoundError { + static_assert(AlwaysFalse<C>::value, "Trying to bind C but it is already bound."); +}; + +template <typename RequiredSignature, typename SignatureInInjectTypedef> +struct RequiredFactoryWithDifferentSignatureError { + static_assert(AlwaysFalse<RequiredSignature>::value, + "The required C factory doesn't have the same signature as the Inject annotation in C."); +}; + +template <typename Signature, typename SignatureInLambda> +struct AnnotatedSignatureDifferentFromLambdaSignatureError { + static_assert(AlwaysFalse<Signature>::value, + "The annotated signature specified is not the same as the lambda's signature (after removing " + "annotations)."); +}; + +template <typename... DuplicatedTypes> +struct DuplicateTypesInComponentError { + static_assert(AlwaysFalse<DuplicatedTypes...>::value, + "The installed component provides some types that are already provided by the current " + "component."); +}; + +template <typename... Requirements> +struct InjectorWithRequirementsError { + static_assert(AlwaysFalse<Requirements...>::value, + "Injectors can't have requirements. If you want Fruit to try auto-resolving the requirements " + "in the injector's scope, cast the component to a component with no requirements before " + "constructing the injector with it."); +}; + +template <typename C, typename CandidateSignature> +struct InjectTypedefNotASignatureError { + static_assert(AlwaysFalse<C>::value, "C::Inject should be a typedef to a signature, e.g. C(int)"); +}; + +template <typename C, typename SignatureReturnType> +struct InjectTypedefForWrongClassError { + static_assert(AlwaysFalse<C>::value, + "C::Inject is a signature, but does not return a C. Maybe the class C has no Inject typedef " + "and inherited the base class' one? If that's not the case, make sure it returns just C, not " + "C* or other types."); +}; + +template <typename C> +struct InjectTypedefWithAnnotationError { + static_assert(AlwaysFalse<C>::value, + "C::Inject is a signature that returns an annotated type. The annotation must be removed, " + "Fruit will deduce the correct annotation based on how the required binding."); +}; + +template <typename CandidateSignature> +struct NotASignatureError { + static_assert(AlwaysFalse<CandidateSignature>::value, + "CandidateSignature was specified as parameter, but it's not a signature. Signatures are of " + "the form MyClass(int, float)."); +}; + +template <typename CandidateLambda> +struct NotALambdaError { + static_assert(AlwaysFalse<CandidateLambda>::value, + "CandidateLambda was specified as parameter, but it's not a lambda."); +}; + +template <typename Signature> +struct ConstructorDoesNotExistError { + static_assert(AlwaysFalse<Signature>::value, "The specified constructor does not exist."); +}; + +template <typename I, typename C> +struct NotABaseClassOfError { + static_assert(AlwaysFalse<I>::value, "I is not a base class of C."); +}; + +template <typename ProviderType> +struct FunctorUsedAsProviderError { + static_assert(AlwaysFalse<ProviderType>::value, + "A stateful lambda or a non-lambda functor was used as provider. Only functions and stateless " + "lambdas can be used as providers."); +}; + +template <typename... ComponentRequirements> +struct ComponentWithRequirementsInInjectorError { + static_assert(AlwaysFalse<ComponentRequirements...>::value, + "When using the two-argument constructor of Injector, the component used as second parameter " + "must not have requirements (while the normalized component can), but the specified component " + "requires ComponentRequirements."); +}; + +template <typename... UnsatisfiedRequirements> +struct UnsatisfiedRequirementsInNormalizedComponentError { + static_assert(AlwaysFalse<UnsatisfiedRequirements...>::value, + "The requirements in UnsatisfiedRequirements are required by the NormalizedComponent but are " + "not provided by the Component (second parameter of the Injector constructor)."); +}; + +template <typename... TypesNotProvided> +struct TypesInInjectorNotProvidedError { + static_assert(AlwaysFalse<TypesNotProvided...>::value, + "The types in TypesNotProvided are declared as provided by the injector, but none of the two " + "components passed to the Injector constructor provides them."); +}; + +template <typename... TypesProvidedAsConstOnly> +struct TypesInInjectorProvidedAsConstOnlyError { + static_assert( + AlwaysFalse<TypesProvidedAsConstOnly...>::value, + "The types in TypesProvidedAsConstOnly are declared as non-const provided types by the injector, but the " + "components passed to the Injector constructor provide them as const only. You should mark them as const in the " + "injector (e.g., switching from Injector<T> to Injector<const T>) or mark them as non-const in the " + "Component/NormalizedComponent (e.g. switching from [Normalized]Component<const T> to " + "[Normalized]Component<T>)."); +}; + +template <typename T> +struct TypeNotProvidedError { + static_assert(AlwaysFalse<T>::value, + "Trying to get an instance of T, but it is not provided by this Provider/Injector."); +}; + +template <typename T> +struct TypeProvidedAsConstOnlyError { + static_assert( + AlwaysFalse<T>::value, + "Trying to get an instance of T, but it is only provided as a constant by this Provider/Injector and a non-const " + "pointer/reference/Provider was requested. You should either switch to injecting a const value (e.g. switching " + "from" + " injecting T*, T&, std::unique_ptr<T> or Provider<T> to injecting a T, const T*, const T& or Provider<const T>) " + "or get the value from an Injector/Provider that provides it as a non-const type (e.g. switching from calling " + "get " + "on an Injector<const T> or on a Provider<const T> to calling get on an Injector<T> or a Provider<T>)."); +}; + +template <typename T> +struct NonConstBindingRequiredButConstBindingProvidedError { + static_assert( + AlwaysFalse<T>::value, + "The type T was provided as constant, however one of the constructors/providers/factories in this component " + "requires it as a non-constant (or this Component declares it as a non-const provided/required type). " + "If you want to only have a const binding for this type, you should change the places that use the type to " + "inject " + "a constant value (e.g. T, const T*, const T& and Provider<const T> are ok while you should avoid injecting T*, " + "T&," + " std::unique_ptr<T> and Provider<T>) and if the type is in Component<...> make sure that it's marked as const " + "there" + " (e.g. Component<const T> and Component<Required<const T>> are ok while Component<T> and Component<Required<T>> " + "are " + "not. " + "On the other hand, if you want to have a non-const binding for this type, you should switch to a non-const " + "bindInstance (if you're binding an instance) or changing any installed component functions to declare the type " + "as " + "non-const, e.g. Component<T> or Component<Required<T>> instead of Component<const T> and " + "Component<Required<const T>>."); +}; + +template <typename C, typename InjectSignature> +struct NoConstructorMatchingInjectSignatureError { + static_assert(AlwaysFalse<C>::value, + "C contains an Inject typedef but it's not constructible with the specified types"); +}; + +template <typename ExpectedSignature, typename FunctorSignature> +struct FunctorSignatureDoesNotMatchError { + static_assert(AlwaysFalse<ExpectedSignature>::value, + "Unexpected functor signature (it should be the same as ExpectedSignature minus any Assisted " + "types)."); +}; + +template <typename Signature> +struct FactoryReturningPointerError { + static_assert(AlwaysFalse<Signature>::value, + "The specified factory returns a pointer. This is not supported; return a value or an " + "std::unique_ptr instead."); +}; + +template <typename Lambda> +struct LambdaWithCapturesError { + // It's not guaranteed by the standard, but it's reasonable to expect lambdas with no captures + // to be empty. This is always the case in GCC and Clang, but is not guaranteed to work in all + // conforming C++ compilers. If this error happens for a lambda with no captures, please file a + // bug at https://github.com/google/fruit/issues and indicate the compiler (with version) that + // you're using. + static_assert(AlwaysFalse<Lambda>::value, "Only lambdas with no captures are supported."); +}; + +template <typename Lambda> +struct NonTriviallyCopyableLambdaError { + // It's not guaranteed by the standard, but it's reasonable to expect lambdas with no captures + // to be trivially copyable. This is always the case in GCC and Clang, but is not guaranteed to + // work in all conforming C++ compilers. If this error happens for a lambda with no captures, + // please file a bug at https://github.com/google/fruit/issues and indicate the compiler (with + // version) that you're using. + static_assert(AlwaysFalse<Lambda>::value, + "Only trivially copyable lambdas are supported. Make sure that your lambda has no captures."); +}; + +template <typename C> +struct CannotConstructAbstractClassError { + static_assert(AlwaysFalse<C>::value, "The specified class can't be constructed because it's an abstract class."); +}; + +template <typename C> +struct InterfaceBindingToSelfError { + static_assert(AlwaysFalse<C>::value, + "The type C was bound to itself. If this was intentional, to \"tell Fruit to inject the type" + " C\", this binding is unnecessary, just remove it. bind<I,C>() is to tell Fruit about" + " base-derived class relationships."); +}; + +template <typename TypeParameter, typename TypeOfValue> +struct TypeMismatchInBindInstanceError { + static_assert(AlwaysFalse<TypeParameter>::value, + "A type parameter was specified in bindInstance() but it doesn't match the value type" + " (even after removing the fruit::Annotation<>, if any). Please change the type parameter" + " to be the same as the type of the value (or a subclass)."); +}; + +template <typename RequiredType> +struct RequiredTypesInComponentArgumentsError { + static_assert(AlwaysFalse<RequiredType>::value, + "A Required<...> type was passed as a non-first template parameter to fruit::Component or " + "fruit::NormalizedComponent. " + "All required types (if any) should be passed together as a single Required<> type passed as the first " + "type argument of fruit::Component (and fruit::NormalizedComponent). For example, write " + "fruit::Component<fruit::Required<Foo, Bar>, Baz> instead of " + "fruit::Component<fruit::Required<Foo>, fruit::Required<Bar>, Baz>."); +}; + +template <typename T> +struct NonInjectableTypeError { + static_assert( + AlwaysFalse<T>::value, + "The type T is not injectable. Injectable types are of the form X, X*, X&, const X, const X*, const X&, " + "std::shared_ptr<X>, or Provider<X> where X is a fundamental type (excluding void), a class, a struct or " + "an enum."); +}; + +template <typename T> +struct ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError { + static_assert( + AlwaysFalse<T>::value, + "The type T was declared as a const Required type in the returned Component, however a non-const binding is " + "required. You should either change all the usages of this type so that they no longer require a non-const " + "binding " + "(i.e., you shouldn't inject T*, T& or std::shared_ptr<T>) or you should remove the 'const' in the type of the " + "returned Component, e.g. changing fruit::Component<fruit::Required<const T, ...>, ...> to " + "fruit::Component<fruit::Required<T, ...>, ...>."); +}; + +template <typename T> +struct ProviderReturningPointerToAbstractClassWithNoVirtualDestructorError { + static_assert( + AlwaysFalse<T>::value, + "registerProvider() was called with a lambda that returns a pointer to T, but T is an abstract class with no " + "virtual destructor so when the injector is deleted Fruit will be unable to call the right destructor (the one " + "of " + "the concrete class that was then casted to T). You must either add a virtual destructor to T or change the " + "registerProvider() call to return a pointer to the concrete class (and then add a bind<T, TImpl>() so that T is " + "bound)."); +}; + +template <typename T> +struct MultibindingProviderReturningPointerToAbstractClassWithNoVirtualDestructorError { + static_assert( + AlwaysFalse<T>::value, + "registerMultibindingProvider() was called with a lambda that returns a pointer to T, but T is an abstract class " + "with no virtual destructor so when the injector is deleted Fruit will be unable to call the right destructor " + "(the " + "one of the concrete class that was then casted to T). You must add a virtual destructor to T or replace the " + "registerMultibindingProvider() with a registerProvider() for the concrete class and an addMultibinding() for T. " + "Note that with the latter, if you end up with multiple addMultibinding() calls for the same concrete class, " + "there will be only one instance of the concrete class in the injector, not one per addMultibdinding() call; if " + "you want separate instances you might want to use annotated injection for the concrete class (so that there's " + "one " + "instance per annotation)."); +}; + +template <typename T> +struct RegisterFactoryForUniquePtrOfAbstractClassWithNoVirtualDestructorError { + static_assert(AlwaysFalse<T>::value, + "registerFactory() was called with a lambda that returns a std::unique_ptr<T>, but T is an abstract " + "class with no " + "virtual destructor so when the returned std::unique_ptr<T> object is deleted the wrong destructor " + "will be called " + "(T's destructor instead of the one of the concrete class that was then casted to T). You must add a " + "virtual destructor to T."); +}; + +template <typename BaseFactory, typename DerivedFactory> +struct FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorError { + static_assert( + AlwaysFalse<BaseFactory>::value, + "Fruit was trying to bind BaseFactory to DerivedFactory but the return type of BaseFactory is a std::unique_ptr " + "of " + "a class with no virtual destructor, so when the std::unique_ptr object is destroyed the wrong destructor would " + "be " + "called (the one in the base class instead of the derived class). To avoid this, you must add a virtual " + "destructor to the base class."); +}; + +struct LambdaWithCapturesErrorTag { + template <typename Lambda> + using apply = LambdaWithCapturesError<Lambda>; +}; + +struct NonTriviallyCopyableLambdaErrorTag { + template <typename Lambda> + using apply = NonTriviallyCopyableLambdaError<Lambda>; +}; + +struct FactoryReturningPointerErrorTag { + template <typename Signature> + using apply = FactoryReturningPointerError<Signature>; +}; + +struct NoBindingFoundErrorTag { + template <typename T> + using apply = NoBindingFoundError<T>; +}; + +struct RepeatedTypesErrorTag { + template <typename... Ts> + using apply = RepeatedTypesError<Ts...>; +}; + +struct SelfLoopErrorTag { + template <typename... TypesInLoop> + using apply = SelfLoopError<TypesInLoop...>; +}; + +struct NonClassTypeErrorTag { + template <typename T, typename C> + using apply = NonClassTypeError<T, C>; +}; + +struct AnnotatedTypeErrorTag { + template <typename T, typename C> + using apply = AnnotatedTypeError<T, C>; +}; + +struct TypeAlreadyBoundErrorTag { + template <typename C> + using apply = TypeAlreadyBoundError<C>; +}; + +struct RequiredFactoryWithDifferentSignatureErrorTag { + template <typename RequiredSignature, typename SignatureInInjectTypedef> + using apply = RequiredFactoryWithDifferentSignatureError<RequiredSignature, SignatureInInjectTypedef>; +}; + +struct AnnotatedSignatureDifferentFromLambdaSignatureErrorTag { + template <typename Signature, typename SignatureInLambda> + using apply = AnnotatedSignatureDifferentFromLambdaSignatureError<Signature, SignatureInLambda>; +}; + +struct DuplicateTypesInComponentErrorTag { + template <typename... DuplicatedTypes> + using apply = DuplicateTypesInComponentError<DuplicatedTypes...>; +}; + +struct InjectorWithRequirementsErrorTag { + template <typename... Requirements> + using apply = InjectorWithRequirementsError<Requirements...>; +}; + +struct ComponentWithRequirementsInInjectorErrorTag { + template <typename... ComponentRequirements> + using apply = ComponentWithRequirementsInInjectorError<ComponentRequirements...>; +}; + +struct InjectTypedefNotASignatureErrorTag { + template <typename C, typename TypeInInjectTypedef> + using apply = InjectTypedefNotASignatureError<C, TypeInInjectTypedef>; +}; + +struct InjectTypedefForWrongClassErrorTag { + template <typename C, typename ReturnTypeOfInjectTypedef> + using apply = InjectTypedefForWrongClassError<C, ReturnTypeOfInjectTypedef>; +}; + +struct InjectTypedefWithAnnotationErrorTag { + template <typename C> + using apply = InjectTypedefWithAnnotationError<C>; +}; + +struct UnsatisfiedRequirementsInNormalizedComponentErrorTag { + template <typename... UnsatisfiedRequirements> + using apply = UnsatisfiedRequirementsInNormalizedComponentError<UnsatisfiedRequirements...>; +}; + +struct TypesInInjectorNotProvidedErrorTag { + template <typename... TypesNotProvided> + using apply = TypesInInjectorNotProvidedError<TypesNotProvided...>; +}; + +struct TypesInInjectorProvidedAsConstOnlyErrorTag { + template <typename... TypesProvidedAsConstOnly> + using apply = TypesInInjectorProvidedAsConstOnlyError<TypesProvidedAsConstOnly...>; +}; + +struct FunctorUsedAsProviderErrorTag { + template <typename ProviderType> + using apply = FunctorUsedAsProviderError<ProviderType>; +}; + +struct ConstructorDoesNotExistErrorTag { + template <typename Signature> + using apply = ConstructorDoesNotExistError<Signature>; +}; + +struct NotABaseClassOfErrorTag { + template <typename I, typename C> + using apply = NotABaseClassOfError<I, C>; +}; + +struct NotASignatureErrorTag { + template <typename CandidateSignature> + using apply = NotASignatureError<CandidateSignature>; +}; + +struct NotALambdaErrorTag { + template <typename CandidateLambda> + using apply = NotALambdaError<CandidateLambda>; +}; + +struct TypeNotProvidedErrorTag { + template <typename T> + using apply = TypeNotProvidedError<T>; +}; + +struct TypeProvidedAsConstOnlyErrorTag { + template <typename T> + using apply = TypeProvidedAsConstOnlyError<T>; +}; + +struct NonConstBindingRequiredButConstBindingProvidedErrorTag { + template <typename T> + using apply = NonConstBindingRequiredButConstBindingProvidedError<T>; +}; + +struct NoConstructorMatchingInjectSignatureErrorTag { + template <typename C, typename InjectSignature> + using apply = NoConstructorMatchingInjectSignatureError<C, InjectSignature>; +}; + +struct FunctorSignatureDoesNotMatchErrorTag { + template <typename ExpectedSignature, typename FunctorSignature> + using apply = FunctorSignatureDoesNotMatchError<ExpectedSignature, FunctorSignature>; +}; + +struct CannotConstructAbstractClassErrorTag { + template <typename C> + using apply = CannotConstructAbstractClassError<C>; +}; + +struct NoBindingFoundForAbstractClassErrorTag { + template <typename T, typename C> + using apply = NoBindingFoundForAbstractClassError<T, C>; +}; + +struct InterfaceBindingToSelfErrorTag { + template <typename C> + using apply = InterfaceBindingToSelfError<C>; +}; + +struct TypeMismatchInBindInstanceErrorTag { + template <typename TypeParameter, typename TypeOfValue> + using apply = TypeMismatchInBindInstanceError<TypeParameter, TypeOfValue>; +}; + +struct RequiredTypesInComponentArgumentsErrorTag { + template <typename RequiredType> + using apply = RequiredTypesInComponentArgumentsError<RequiredType>; +}; + +struct NonInjectableTypeErrorTag { + template <typename T> + using apply = NonInjectableTypeError<T>; +}; + +struct ConstBindingDeclaredAsRequiredButNonConstBindingRequiredErrorTag { + template <typename T> + using apply = ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<T>; +}; + +struct ProviderReturningPointerToAbstractClassWithNoVirtualDestructorErrorTag { + template <typename T> + using apply = ProviderReturningPointerToAbstractClassWithNoVirtualDestructorError<T>; +}; + +struct MultibindingProviderReturningPointerToAbstractClassWithNoVirtualDestructorErrorTag { + template <typename T> + using apply = MultibindingProviderReturningPointerToAbstractClassWithNoVirtualDestructorError<T>; +}; + +struct RegisterFactoryForUniquePtrOfAbstractClassWithNoVirtualDestructorErrorTag { + template <typename T> + using apply = RegisterFactoryForUniquePtrOfAbstractClassWithNoVirtualDestructorError<T>; +}; + +struct FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorErrorTag { + template <typename BaseFactory, typename DerivedFactory> + using apply = FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorError<BaseFactory, DerivedFactory>; +}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_INJECTION_ERRORS diff --git a/include/fruit/impl/injector.defn.h b/include/fruit/impl/injector.defn.h new file mode 100644 index 0000000..2072db5 --- /dev/null +++ b/include/fruit/impl/injector.defn.h @@ -0,0 +1,155 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_DEFN_H +#define FRUIT_INJECTOR_DEFN_H + +#include <fruit/component.h> + +// Redundant, but makes KDevelop happy. +#include <fruit/injector.h> + +namespace fruit { + +template <typename... P> +template <typename... FormalArgs, typename... Args> +inline Injector<P...>::Injector(Component<P...> (*getComponent)(FormalArgs...), Args&&... args) { + Component<P...> component = fruit::createComponent().install(getComponent, std::forward<Args>(args)...); + + fruit::impl::MemoryPool memory_pool; + using exposed_types_t = std::vector<fruit::impl::TypeId, fruit::impl::ArenaAllocator<fruit::impl::TypeId>>; + exposed_types_t exposed_types = + exposed_types_t(std::initializer_list<fruit::impl::TypeId>{fruit::impl::getTypeId<P>()...}, + fruit::impl::ArenaAllocator<fruit::impl::TypeId>(memory_pool)); + storage = std::unique_ptr<fruit::impl::InjectorStorage>( + new fruit::impl::InjectorStorage(std::move(component.storage), exposed_types, memory_pool)); +} + +namespace impl { +namespace meta { + +template <typename... P> +struct InjectorImplHelper { + + // This performs all checks needed in the constructor of Injector that takes NormalizedComponent. + template <typename NormalizedComp, typename Comp> + struct CheckConstructionFromNormalizedComponent { + using Op = InstallComponent(Comp, NormalizedComp); + + // The calculation of MergedComp will also do some checks, e.g. multiple bindings for the same type. + using MergedComp = GetResult(Op); + + using TypesNotProvided = SetDifference(RemoveConstFromTypes(Vector<Type<P>...>), GetComponentPs(MergedComp)); + using MergedCompRs = SetDifference(GetComponentRsSuperset(MergedComp), GetComponentPs(MergedComp)); + + using type = Eval<If( + Not(IsEmptySet(GetComponentRsSuperset(Comp))), + ConstructErrorWithArgVector(ComponentWithRequirementsInInjectorErrorTag, + SetToVector(GetComponentRsSuperset(Comp))), + If(Not(IsEmptySet(MergedCompRs)), + ConstructErrorWithArgVector(UnsatisfiedRequirementsInNormalizedComponentErrorTag, SetToVector(MergedCompRs)), + If(Not(IsContained(VectorToSetUnchecked(RemoveConstFromTypes(Vector<Type<P>...>)), + GetComponentPs(MergedComp))), + ConstructErrorWithArgVector(TypesInInjectorNotProvidedErrorTag, SetToVector(TypesNotProvided)), + If(Not(IsContained(VectorToSetUnchecked(RemoveConstTypes(Vector<Type<P>...>)), + GetComponentNonConstRsPs(MergedComp))), + ConstructErrorWithArgVector( + TypesInInjectorProvidedAsConstOnlyErrorTag, + SetToVector(SetDifference(VectorToSetUnchecked(RemoveConstTypes(Vector<Type<P>...>)), + GetComponentNonConstRsPs(MergedComp)))), + None))))>; + }; + + template <typename T> + struct CheckGet { + using Comp = ConstructComponentImpl(Type<P>...); + + using type = Eval<PropagateError(CheckInjectableType(RemoveAnnotations(Type<T>)), + If(Not(IsInSet(NormalizeType(Type<T>), GetComponentPs(Comp))), + ConstructError(TypeNotProvidedErrorTag, Type<T>), + If(And(TypeInjectionRequiresNonConstBinding(Type<T>), + Not(IsInSet(NormalizeType(Type<T>), GetComponentNonConstRsPs(Comp)))), + ConstructError(TypeProvidedAsConstOnlyErrorTag, Type<T>), None)))>; + }; +}; + +} // namespace meta +} // namespace impl + +template <typename... P> +template <typename... NormalizedComponentParams, typename... ComponentParams, typename... FormalArgs, typename... Args> +inline Injector<P...>::Injector(const NormalizedComponent<NormalizedComponentParams...>& normalized_component, + Component<ComponentParams...> (*getComponent)(FormalArgs...), Args&&... args) { + Component<ComponentParams...> component = fruit::createComponent().install(getComponent, std::forward<Args>(args)...); + + fruit::impl::MemoryPool memory_pool; + storage = std::unique_ptr<fruit::impl::InjectorStorage>(new fruit::impl::InjectorStorage( + *(normalized_component.storage.storage), std::move(component.storage), memory_pool)); + + using NormalizedComp = + fruit::impl::meta::ConstructComponentImpl(fruit::impl::meta::Type<NormalizedComponentParams>...); + using Comp1 = fruit::impl::meta::ConstructComponentImpl(fruit::impl::meta::Type<ComponentParams>...); + // We don't check whether the construction of NormalizedComp or Comp resulted in errors here; if they did, the + // instantiation + // of NormalizedComponent<NormalizedComponentParams...> or Component<ComponentParams...> would have resulted in an + // error already. + + using E = typename fruit::impl::meta::InjectorImplHelper<P...>::template CheckConstructionFromNormalizedComponent< + NormalizedComp, Comp1>::type; + (void)typename fruit::impl::meta::CheckIfError<E>::type(); +} + +template <typename... P> +template <typename T> +inline typename Injector<P...>::template RemoveAnnotations<T> Injector<P...>::get() { + + using E = typename fruit::impl::meta::InjectorImplHelper<P...>::template CheckGet<T>::type; + (void)typename fruit::impl::meta::CheckIfError<E>::type(); + return storage->template get<T>(); +} + +template <typename... P> +template <typename T> +inline Injector<P...>::operator T() { + return get<T>(); +} + +template <typename... P> +template <typename AnnotatedC> +inline const std::vector<typename fruit::Injector<P...>::template RemoveAnnotationsHelper<AnnotatedC>::type*>& +Injector<P...>::getMultibindings() { + + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<AnnotatedC>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + + return storage->template getMultibindings<AnnotatedC>(); +} + +template <typename... P> +FRUIT_DEPRECATED_DEFINITION(inline void Injector<P...>::eagerlyInjectAll()) { + // Eagerly inject normal bindings. + void* unused[] = {reinterpret_cast<void*>( + storage->template get<fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::AddPointerInAnnotatedType(fruit::impl::meta::Type<P>)>>>())...}; + (void)unused; + + storage->eagerlyInjectMultibindings(); +} + +} // namespace fruit + +#endif // FRUIT_INJECTOR_DEFN_H diff --git a/include/fruit/impl/injector/injector_accessor_for_tests.defn.h b/include/fruit/impl/injector/injector_accessor_for_tests.defn.h new file mode 100644 index 0000000..c74c930 --- /dev/null +++ b/include/fruit/impl/injector/injector_accessor_for_tests.defn.h @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_DEFN_H +#define FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_DEFN_H + +#include <fruit/impl/injector/injector_accessor_for_tests.h> + +namespace fruit { +namespace impl { + +template <typename AnnotatedC, typename... Params> +const typename fruit::Injector<Params...>::template RemoveAnnotations<AnnotatedC>* +InjectorAccessorForTests::unsafeGet(fruit::Injector<Params...>& injector) { + using Op = fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<AnnotatedC>>)>; + (void)typename fruit::impl::meta::CheckIfError<Op>::type(); + return injector.storage->template unsafeGet<AnnotatedC>(); +} +} +} + +#endif // FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_DEFN_H diff --git a/include/fruit/impl/injector/injector_accessor_for_tests.h b/include/fruit/impl/injector/injector_accessor_for_tests.h new file mode 100644 index 0000000..b2c106c --- /dev/null +++ b/include/fruit/impl/injector/injector_accessor_for_tests.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_H +#define FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_H + +#include <fruit/fruit.h> + +namespace fruit { +namespace impl { + +/** + * A class used to access Injector's internals in Fruit's own tests. Note that this is *not* meant to be used outside + * of Fruit. + */ +struct InjectorAccessorForTests { + + /** + * If C was bound (directly or indirectly) in the component used to create this injector, returns a pointer to the + * instance of C (constructing it if necessary). Otherwise returns nullptr. + * + * This supports annotated injection, just use Annotated<Annotation, C> instead of just C. + * With a non-annotated parameter C, this returns a C*. + * With an annotated parameter C=Annotated<Annotation, SomeClass>, this returns a const SomeClass*. + * + * Note that this doesn't trigger auto-bindings: so even if the constructor of C was visible to some get*Component + * function (or to the place where unsafeGet is called), in order to successfully get an instance with this method + * you need all the following to be true: + * * C was explicitly bound in a component, or C was a dependency (direct or indirect) of a type that was explicitly + * bound + * * C was not bound to any interface (note however that if C was bound to I, you can do unsafeGet<I>() instead). + * + * Otherwise this method will return nullptr. + */ + template <typename C, typename... Params> + static const typename fruit::Injector<Params...>::template RemoveAnnotations<C>* + unsafeGet(fruit::Injector<Params...>& injector); +}; +} +} + +#include <fruit/impl/injector/injector_accessor_for_tests.defn.h> + +#endif // FRUIT_INJECTOR_ACCESSOR_FOR_TESTS_H diff --git a/include/fruit/impl/injector/injector_storage.defn.h b/include/fruit/impl/injector/injector_storage.defn.h new file mode 100644 index 0000000..b5de693 --- /dev/null +++ b/include/fruit/impl/injector/injector_storage.defn.h @@ -0,0 +1,850 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_STORAGE_DEFN_H +#define FRUIT_INJECTOR_STORAGE_DEFN_H + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/meta/component.h> +#include <fruit/impl/meta/vector.h> +#include <fruit/impl/util/demangle_type_name.h> +#include <fruit/impl/util/lambda_invoker.h> +#include <fruit/impl/util/type_info.h> + +#include <cassert> + +// Redundant, but makes KDevelop happy. +#include <fruit/impl/injector/injector_storage.h> + +namespace fruit { +namespace impl { + +inline InjectorStorage::BindingDataNodeIter* InjectorStorage::BindingDataNodeIter::operator->() { + return this; +} + +inline void InjectorStorage::BindingDataNodeIter::operator++() { + ++itr; +} + +inline bool InjectorStorage::BindingDataNodeIter::operator==(const BindingDataNodeIter& other) const { + return itr == other.itr; +} + +inline bool InjectorStorage::BindingDataNodeIter::operator!=(const BindingDataNodeIter& other) const { + return itr != other.itr; +} + +inline std::ptrdiff_t InjectorStorage::BindingDataNodeIter::operator-(BindingDataNodeIter other) const { + return itr - other.itr; +} + +inline TypeId InjectorStorage::BindingDataNodeIter::getId() { + // For these kinds the type_id has a different meaning, but we never need to call this method for those. + FruitAssert(itr->kind != ComponentStorageEntry::Kind::COMPRESSED_BINDING); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS); + FruitAssert(itr->kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS); + return itr->type_id; +} + +inline NormalizedBinding InjectorStorage::BindingDataNodeIter::getValue() { + return NormalizedBinding(*itr); +} + +inline bool InjectorStorage::BindingDataNodeIter::isTerminal() { +#ifdef FRUIT_EXTRA_DEBUG + if (itr->kind != ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT && + itr->kind != ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION && + itr->kind != ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION && + itr->kind != ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION) { + std::cerr << "Unexpected binding kind: " << (std::size_t)itr->kind << std::endl; + FruitAssert(false); + } +#endif + return itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT; +} + +inline const TypeId* InjectorStorage::BindingDataNodeIter::getEdgesBegin() { + FruitAssert(itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION || + itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION); + return itr->binding_for_object_to_construct.deps->deps; +} + +inline const TypeId* InjectorStorage::BindingDataNodeIter::getEdgesEnd() { + FruitAssert(itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION || + itr->kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION); + return itr->binding_for_object_to_construct.deps->deps + itr->binding_for_object_to_construct.deps->num_deps; +} + +template <typename AnnotatedT> +struct GetFirstStage; + +// General case, value. +template <typename C> +struct GetFirstStage { + const C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + return injector.getPtr<C>(node_itr); + } +}; + +template <typename C> +struct GetFirstStage<const C> { + const C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + return injector.getPtr<C>(node_itr); + } +}; + +template <typename C> +struct GetFirstStage<std::shared_ptr<C>> { + // This method is covered by tests, even though lcov doesn't detect that. + C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + FruitAssert(node_itr.getNode().is_nonconst); + return const_cast<C*>(injector.getPtr<C>(node_itr)); + } +}; + +template <typename C> +struct GetFirstStage<C*> { + C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + FruitAssert(node_itr.getNode().is_nonconst); + return const_cast<C*>(injector.getPtr<C>(node_itr)); + } +}; + +template <typename C> +struct GetFirstStage<const C*> { + const C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + return injector.getPtr<C>(node_itr); + } +}; + +template <typename C> +struct GetFirstStage<C&> { + C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + FruitAssert(node_itr.getNode().is_nonconst); + return const_cast<C*>(injector.getPtr<C>(node_itr)); + } +}; + +template <typename C> +struct GetFirstStage<const C&> { + // This method is covered by tests, even though lcov doesn't detect that. + const C* operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + return injector.getPtr<C>(node_itr); + } +}; + +template <typename C> +struct GetFirstStage<Provider<C>> { + Provider<C> operator()(InjectorStorage& injector, InjectorStorage::Graph::node_iterator node_itr) { + return Provider<C>(&injector, node_itr); + } +}; + +template <typename Annotation, typename T> +struct GetFirstStage<fruit::Annotated<Annotation, T>> : public GetFirstStage<T> {}; + +template <typename AnnotatedT> +struct GetSecondStage; + +// General case, value. +template <typename C> +struct GetSecondStage { + C operator()(const C* p) { + return *p; + } +}; + +template <typename C> +struct GetSecondStage<const C> { + const C operator()(const C* p) { + return *p; + } +}; + +template <typename C> +struct GetSecondStage<std::shared_ptr<C>> { + // This method is covered by tests, even though lcov doesn't detect that. + std::shared_ptr<C> operator()(C* p) { + return std::shared_ptr<C>(std::shared_ptr<char>(), p); + } +}; + +template <typename C> +struct GetSecondStage<C*> { + C* operator()(C* p) { + return p; + } +}; + +template <typename C> +struct GetSecondStage<const C*> { + // This method is covered by tests, even though lcov doesn't detect that. + const C* operator()(const C* p) { + return p; + } +}; + +template <typename C> +struct GetSecondStage<C&> { + C& operator()(C* p) { + return *p; + } +}; + +template <typename C> +struct GetSecondStage<const C&> { + const C& operator()(const C* p) { + return *p; + } +}; + +template <typename C> +struct GetSecondStage<Provider<C>> { + Provider<C> operator()(Provider<C> p) { + return p; + } +}; + +template <typename Annotation, typename T> +struct GetSecondStage<fruit::Annotated<Annotation, T>> : public GetSecondStage<T> {}; + +template <typename AnnotatedT> +inline InjectorStorage::RemoveAnnotations<AnnotatedT> InjectorStorage::get() { + std::lock_guard<std::recursive_mutex> lock(mutex); + return GetSecondStage<AnnotatedT>()(GetFirstStage<AnnotatedT>()(*this, lazyGetPtr<NormalizeType<AnnotatedT>>())); +} + +template <typename T> +inline T InjectorStorage::get(InjectorStorage::Graph::node_iterator node_iterator) { + FruitStaticAssert(fruit::impl::meta::IsSame(fruit::impl::meta::Type<T>, + fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<T>))); + std::lock_guard<std::recursive_mutex> lock(mutex); + return GetSecondStage<T>()(GetFirstStage<T>()(*this, node_iterator)); +} + +template <typename AnnotatedC> +inline InjectorStorage::Graph::node_iterator InjectorStorage::lazyGetPtr() { + return lazyGetPtr(getTypeId<AnnotatedC>()); +} + +template <typename AnnotatedC> +inline InjectorStorage::Graph::node_iterator +InjectorStorage::lazyGetPtr(Graph::edge_iterator deps, std::size_t dep_index, Graph::node_iterator bindings_begin) + const { + // Here we (intentionally) do not lock `mutex', since this is a read-only method that only accesses immutable data. + Graph::node_iterator itr = deps.getNodeIterator(dep_index, bindings_begin); + FruitAssert(bindings.find(getTypeId<AnnotatedC>()) == Graph::const_node_iterator(itr)); + FruitAssert(!(bindings.end() == Graph::const_node_iterator(itr))); + return itr; +} + +template <typename C> +inline const C* InjectorStorage::getPtr(Graph::node_iterator itr) { + FruitStaticAssert(fruit::impl::meta::IsSame(fruit::impl::meta::Type<C>, + fruit::impl::meta::NormalizeType(fruit::impl::meta::Type<C>))); + const void* p = getPtrInternal(itr); + return reinterpret_cast<const C*>(p); +} + +template <typename AnnotatedC> +inline const InjectorStorage::RemoveAnnotations<AnnotatedC>* InjectorStorage::unsafeGet() { + std::lock_guard<std::recursive_mutex> lock(mutex); + using C = RemoveAnnotations<AnnotatedC>; + const void* p = unsafeGetPtr(getTypeId<AnnotatedC>()); + return reinterpret_cast<const C*>(p); +} + +inline InjectorStorage::Graph::node_iterator InjectorStorage::lazyGetPtr(TypeId type) { + return bindings.at(type); +} + +inline const void* InjectorStorage::unsafeGetPtr(TypeId type) { + Graph::node_iterator itr = bindings.find(type); + if (itr == bindings.end()) { + return nullptr; + } + return getPtrInternal(itr); +} + +template <typename AnnotatedC> +inline const std::vector<InjectorStorage::RemoveAnnotations<AnnotatedC>*>& InjectorStorage::getMultibindings() { + std::lock_guard<std::recursive_mutex> lock(mutex); + using C = RemoveAnnotations<AnnotatedC>; + void* p = getMultibindings(getTypeId<AnnotatedC>()); + if (p == nullptr) { + static std::vector<C*> empty_vector; + return empty_vector; + } else { + return *reinterpret_cast<std::vector<C*>*>(p); + } +} + +inline const void* InjectorStorage::getPtrInternal(Graph::node_iterator node_itr) { + NormalizedBinding& normalized_binding = node_itr.getNode(); + if (!node_itr.isTerminal()) { + normalized_binding.object = normalized_binding.create(*this, node_itr); + FruitAssert(node_itr.isTerminal()); + } + return normalized_binding.object; +} + +inline NormalizedMultibindingSet* InjectorStorage::getNormalizedMultibindingSet(TypeId type) { + auto itr = multibindings.find(type); + if (itr != multibindings.end()) + return &(itr->second); + else + return nullptr; +} + +template <typename AnnotatedC> +inline std::shared_ptr<char> InjectorStorage::createMultibindingVector(InjectorStorage& storage) { + using C = RemoveAnnotations<AnnotatedC>; + TypeId type = getTypeId<AnnotatedC>(); + NormalizedMultibindingSet* multibinding_set = storage.getNormalizedMultibindingSet(type); + + // This method is only called if there was at least 1 multibinding (otherwise the would-be caller would have returned + // nullptr + // instead of calling this). + FruitAssert(multibinding_set != nullptr); + + if (multibinding_set->v.get() != nullptr) { + // Result cached, return early. + return multibinding_set->v; + } + + storage.ensureConstructedMultibinding(*multibinding_set); + + std::vector<C*> s; + s.reserve(multibinding_set->elems.size()); + for (const NormalizedMultibinding& multibinding : multibinding_set->elems) { + FruitAssert(multibinding.is_constructed); + s.push_back(reinterpret_cast<C*>(multibinding.object)); + } + + std::shared_ptr<std::vector<C*>> vector_ptr = std::make_shared<std::vector<C*>>(std::move(s)); + std::shared_ptr<char> result(vector_ptr, reinterpret_cast<char*>(vector_ptr.get())); + + multibinding_set->v = result; + + return result; +} + +template <typename I, typename C, typename AnnotatedC> +InjectorStorage::const_object_ptr_t +InjectorStorage::createInjectedObjectForBind(InjectorStorage& injector, + InjectorStorage::Graph::node_iterator node_itr) { + + InjectorStorage::Graph::node_iterator bindings_begin = injector.bindings.begin(); + const C* cPtr = injector.get<const C*>(injector.lazyGetPtr<AnnotatedC>(node_itr.neighborsBegin(), 0, bindings_begin)); + node_itr.setTerminal(); + // This step is needed when the cast C->I changes the pointer + // (e.g. for multiple inheritance). + const I* iPtr = static_cast<const I*>(cPtr); + return reinterpret_cast<const_object_ptr_t>(iPtr); +} + +// I, C must not be pointers. +template <typename AnnotatedI, typename AnnotatedC> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForBind() { + using I = RemoveAnnotations<AnnotatedI>; + using C = RemoveAnnotations<AnnotatedC>; + FruitStaticAssert(fruit::impl::meta::Not(fruit::impl::meta::IsPointer(fruit::impl::meta::Type<I>))); + FruitStaticAssert(fruit::impl::meta::Not(fruit::impl::meta::IsPointer(fruit::impl::meta::Type<C>))); + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + result.type_id = getTypeId<AnnotatedI>(); + ComponentStorageEntry::BindingForObjectToConstruct& binding = result.binding_for_object_to_construct; + binding.create = createInjectedObjectForBind<I, C, AnnotatedC>; + binding.deps = getBindingDeps<fruit::impl::meta::Vector<fruit::impl::meta::Type<AnnotatedC>>>(); +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = true; +#endif + return result; +} + +// I, C must not be pointers. +template <typename AnnotatedI, typename AnnotatedC> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForConstBind() { + using I = RemoveAnnotations<AnnotatedI>; + using C = RemoveAnnotations<AnnotatedC>; + FruitStaticAssert(fruit::impl::meta::Not(fruit::impl::meta::IsPointer(fruit::impl::meta::Type<I>))); + FruitStaticAssert(fruit::impl::meta::Not(fruit::impl::meta::IsPointer(fruit::impl::meta::Type<C>))); + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + result.type_id = getTypeId<AnnotatedI>(); + ComponentStorageEntry::BindingForObjectToConstruct& binding = result.binding_for_object_to_construct; + binding.create = createInjectedObjectForBind<I, C, AnnotatedC>; + binding.deps = getBindingDeps<fruit::impl::meta::Vector<fruit::impl::meta::Type<AnnotatedC>>>(); +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = false; +#endif + return result; +} + +template <typename AnnotatedC, typename C> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForBindInstance(C& instance) { + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::BindingForConstructedObject& binding = result.binding_for_constructed_object; + binding.object_ptr = &instance; +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = true; +#endif + return result; +} + +template <typename AnnotatedC, typename C> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForBindConstInstance(const C& instance) { + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::BindingForConstructedObject& binding = result.binding_for_constructed_object; + binding.object_ptr = &instance; +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = false; +#endif + return result; +} + +// The inner operator() takes an InjectorStorage& and a Graph::edge_iterator (the type's deps) and +// returns the injected object as a C*. +// This takes care of move-constructing a C into the injector's own allocator if needed. +template <typename AnnotatedSignature, typename Lambda, bool lambda_returns_pointer, + typename AnnotatedT = InjectorStorage::SignatureType<AnnotatedSignature>, + typename AnnotatedArgVector = + fruit::impl::meta::Eval<fruit::impl::meta::SignatureArgs(fruit::impl::meta::Type<AnnotatedSignature>)>, + typename Indexes = + fruit::impl::meta::Eval<fruit::impl::meta::GenerateIntSequence(fruit::impl::meta::VectorSize( + fruit::impl::meta::SignatureArgs(fruit::impl::meta::Type<AnnotatedSignature>)))>> +struct InvokeLambdaWithInjectedArgVector; + +// AnnotatedT is of the form C* or Annotated<Annotation, C*> +template <typename AnnotatedSignature, typename Lambda, typename AnnotatedT, typename... AnnotatedArgs, + typename... Indexes> +struct InvokeLambdaWithInjectedArgVector<AnnotatedSignature, Lambda, true /* lambda_returns_pointer */, AnnotatedT, + fruit::impl::meta::Vector<AnnotatedArgs...>, + fruit::impl::meta::Vector<Indexes...>> { + using CPtr = InjectorStorage::RemoveAnnotations<AnnotatedT>; + using AnnotatedC = InjectorStorage::NormalizeType<AnnotatedT>; + + FRUIT_ALWAYS_INLINE + CPtr operator()(InjectorStorage& injector, FixedSizeAllocator& allocator) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + CPtr cPtr = + LambdaInvoker::invoke<Lambda, typename InjectorStorage::AnnotationRemover< + typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>::type...>( + injector.get<fruit::impl::meta::UnwrapType<AnnotatedArgs>>()...); + + allocator.registerExternallyAllocatedObject(cPtr); + + // This can happen if the user-supplied provider returns nullptr. + if (cPtr == nullptr) { + InjectorStorage::fatal("attempting to get an instance for the type " + std::string(getTypeId<AnnotatedC>()) + + " but the provider returned nullptr"); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + return cPtr; + } + + // This is not inlined in outerConstructHelper so that when get<> needs to construct an object more complex than a + // pointer + // (e.g. a shared_ptr), that happens as late as possible so that it's easier for the optimizer to optimize out some + // operations (e.g. the increment/decrement/check of shared_ptr's reference count). + template <typename... GetFirstStageResults> + FRUIT_ALWAYS_INLINE CPtr innerConstructHelper(InjectorStorage& injector, + GetFirstStageResults... getFirstStageResults) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return LambdaInvoker::invoke<Lambda, typename InjectorStorage::AnnotationRemover< + typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>::type...>( + GetSecondStage<InjectorStorage::RemoveAnnotations<fruit::impl::meta::UnwrapType<AnnotatedArgs>>>()( + getFirstStageResults)...); + } + + // This is not inlined in operator() so that all the lazyGetPtr() calls happen first (instead of being interleaved + // with the get() calls). The lazyGetPtr() calls don't branch, while the get() calls branch on the result of the + // lazyGetPtr()s, so it's faster to execute them in this order. + template <typename... NodeItrs> + FRUIT_ALWAYS_INLINE CPtr outerConstructHelper(InjectorStorage& injector, NodeItrs... nodeItrs) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return innerConstructHelper( + injector, GetFirstStage<InjectorStorage::RemoveAnnotations<fruit::impl::meta::UnwrapType<AnnotatedArgs>>>()( + injector, nodeItrs)...); + } + + FRUIT_ALWAYS_INLINE + CPtr operator()(InjectorStorage& injector, SemistaticGraph<TypeId, NormalizedBinding>& bindings, + FixedSizeAllocator& allocator, InjectorStorage::Graph::edge_iterator deps) { + // `deps' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)deps; + + InjectorStorage::Graph::node_iterator bindings_begin = bindings.begin(); + // `bindings_begin' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)bindings_begin; + CPtr cPtr = outerConstructHelper( + injector, + injector.lazyGetPtr<InjectorStorage::NormalizeType<fruit::impl::meta::UnwrapType<AnnotatedArgs>>>( + deps, fruit::impl::meta::getIntValue<Indexes>(), bindings_begin)...); + allocator.registerExternallyAllocatedObject(cPtr); + + // This can happen if the user-supplied provider returns nullptr. + if (cPtr == nullptr) { + InjectorStorage::fatal("attempting to get an instance for the type " + std::string(getTypeId<AnnotatedC>()) + + " but the provider returned nullptr"); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + return cPtr; + } +}; + +template <typename AnnotatedSignature, typename Lambda, typename AnnotatedC, typename... AnnotatedArgs, + typename... Indexes> +struct InvokeLambdaWithInjectedArgVector<AnnotatedSignature, Lambda, false /* lambda_returns_pointer */, AnnotatedC, + fruit::impl::meta::Vector<AnnotatedArgs...>, + fruit::impl::meta::Vector<Indexes...>> { + using C = InjectorStorage::RemoveAnnotations<AnnotatedC>; + + FRUIT_ALWAYS_INLINE + C* operator()(InjectorStorage& injector, FixedSizeAllocator& allocator) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return allocator.constructObject<AnnotatedC, C&&>( + LambdaInvoker::invoke<Lambda, typename InjectorStorage::AnnotationRemover< + typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>::type&&...>( + injector.get<typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>()...)); + } + + // This is not inlined in outerConstructHelper so that when get<> needs to construct an object more complex than a + // pointer + // (e.g. a shared_ptr), that happens as late as possible so that it's easier for the optimizer to optimize out some + // operations (e.g. the increment/decrement/check of shared_ptr's reference count). + template <typename... GetFirstStageResults> + FRUIT_ALWAYS_INLINE C* innerConstructHelper(InjectorStorage& injector, FixedSizeAllocator& allocator, + GetFirstStageResults... getFirstStageResults) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return allocator.constructObject<AnnotatedC, C&&>( + LambdaInvoker::invoke<Lambda, typename InjectorStorage::AnnotationRemover< + typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>::type...>( + GetSecondStage<typename InjectorStorage::AnnotationRemover< + typename fruit::impl::meta::TypeUnwrapper<AnnotatedArgs>::type>::type>()(getFirstStageResults)...)); + } + + // This is not inlined in operator() so that all the lazyGetPtr() calls happen first (instead of being interleaved + // with the get() calls). The lazyGetPtr() calls don't branch, while the get() calls branch on the result of the + // lazyGetPtr()s, so it's faster to execute them in this order. + template <typename... NodeItrs> + FRUIT_ALWAYS_INLINE C* outerConstructHelper(InjectorStorage& injector, FixedSizeAllocator& allocator, + NodeItrs... nodeItrs) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return innerConstructHelper( + injector, allocator, + GetFirstStage<InjectorStorage::RemoveAnnotations<fruit::impl::meta::UnwrapType<AnnotatedArgs>>>()(injector, + nodeItrs)...); + } + + C* operator()(InjectorStorage& injector, SemistaticGraph<TypeId, NormalizedBinding>& bindings, + FixedSizeAllocator& allocator, InjectorStorage::Graph::edge_iterator deps) { + InjectorStorage::Graph::node_iterator bindings_begin = bindings.begin(); + // `bindings_begin' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)bindings_begin; + + // `deps' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)deps; + + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + C* p = outerConstructHelper( + injector, allocator, + injector.lazyGetPtr<InjectorStorage::NormalizeType<fruit::impl::meta::UnwrapType<AnnotatedArgs>>>( + deps, fruit::impl::meta::getIntValue<Indexes>(), bindings_begin)...); + return p; + } +}; + +template <typename C, typename T, typename AnnotatedSignature, typename Lambda> +InjectorStorage::const_object_ptr_t InjectorStorage::createInjectedObjectForProvider(InjectorStorage& injector, + Graph::node_iterator node_itr) { + C* cPtr = InvokeLambdaWithInjectedArgVector<AnnotatedSignature, Lambda, std::is_pointer<T>::value>()( + injector, injector.bindings, injector.allocator, node_itr.neighborsBegin()); + node_itr.setTerminal(); + return reinterpret_cast<const_object_ptr_t>(cPtr); +} + +template <typename AnnotatedSignature, typename Lambda> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForProvider() { +#ifdef FRUIT_EXTRA_DEBUG + using Signature = + fruit::impl::meta::UnwrapType<fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotationsFromSignature( + fruit::impl::meta::Type<AnnotatedSignature>)>>; + FruitStaticAssert(fruit::impl::meta::IsSame(fruit::impl::meta::Type<Signature>, + fruit::impl::meta::FunctionSignature(fruit::impl::meta::Type<Lambda>))); +#endif + using AnnotatedT = SignatureType<AnnotatedSignature>; + using AnnotatedC = NormalizeType<AnnotatedT>; + // T is either C or C*. + using T = RemoveAnnotations<AnnotatedT>; + using C = NormalizeType<T>; + ComponentStorageEntry result; + constexpr bool needs_allocation = !std::is_pointer<T>::value; + result.kind = needs_allocation + ? ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION + : ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::BindingForObjectToConstruct& binding = result.binding_for_object_to_construct; + binding.create = createInjectedObjectForProvider<C, T, AnnotatedSignature, Lambda>; + binding.deps = getBindingDeps<NormalizedSignatureArgs<AnnotatedSignature>>(); +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = true; +#endif + return result; +} + +template <typename I, typename C, typename T, typename AnnotatedSignature, typename Lambda> +InjectorStorage::const_object_ptr_t +InjectorStorage::createInjectedObjectForCompressedProvider(InjectorStorage& injector, Graph::node_iterator node_itr) { + C* cPtr = InvokeLambdaWithInjectedArgVector<AnnotatedSignature, Lambda, std::is_pointer<T>::value>()( + injector, injector.bindings, injector.allocator, node_itr.neighborsBegin()); + node_itr.setTerminal(); + I* iPtr = static_cast<I*>(cPtr); + return reinterpret_cast<object_ptr_t>(iPtr); +} + +template <typename AnnotatedSignature, typename Lambda, typename AnnotatedI> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForCompressedProvider() { +#ifdef FRUIT_EXTRA_DEBUG + using Signature = + fruit::impl::meta::UnwrapType<fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotationsFromSignature( + fruit::impl::meta::Type<AnnotatedSignature>)>>; + FruitStaticAssert(fruit::impl::meta::IsSame(fruit::impl::meta::Type<Signature>, + fruit::impl::meta::FunctionSignature(fruit::impl::meta::Type<Lambda>))); +#endif + using AnnotatedT = SignatureType<AnnotatedSignature>; + using AnnotatedC = NormalizeType<AnnotatedT>; + // T is either C or C*. + using T = RemoveAnnotations<AnnotatedT>; + using C = NormalizeType<T>; + using I = RemoveAnnotations<AnnotatedI>; + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::COMPRESSED_BINDING; + result.type_id = getTypeId<AnnotatedI>(); + ComponentStorageEntry::CompressedBinding& binding = result.compressed_binding; + binding.c_type_id = getTypeId<AnnotatedC>(); + binding.create = createInjectedObjectForCompressedProvider<I, C, T, AnnotatedSignature, Lambda>; + return result; +} + +// The inner operator() takes an InjectorStorage& and a Graph::edge_iterator (the type's deps) and +// returns the injected object as a C*. +// This takes care of allocating the required space into the injector's allocator. +template <typename AnnotatedSignature, + typename Indexes = + fruit::impl::meta::Eval<fruit::impl::meta::GenerateIntSequence(fruit::impl::meta::VectorSize( + fruit::impl::meta::SignatureArgs(fruit::impl::meta::Type<AnnotatedSignature>)))>> +struct InvokeConstructorWithInjectedArgVector; + +template <typename AnnotatedC, typename... AnnotatedArgs, typename... Indexes> +struct InvokeConstructorWithInjectedArgVector<AnnotatedC(AnnotatedArgs...), fruit::impl::meta::Vector<Indexes...>> { + using C = InjectorStorage::RemoveAnnotations<AnnotatedC>; + + // This is not inlined in outerConstructHelper so that when get<> needs to construct an object more complex than a + // pointer + // (e.g. a shared_ptr), that happens as late as possible so that it's easier for the optimizer to optimize out some + // operations (e.g. the increment/decrement/check of shared_ptr's reference count). + template <typename... GetFirstStageResults> + FRUIT_ALWAYS_INLINE C* innerConstructHelper(InjectorStorage& injector, FixedSizeAllocator& allocator, + GetFirstStageResults... getFirstStageResults) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return allocator.constructObject<AnnotatedC, typename InjectorStorage::AnnotationRemover<AnnotatedArgs>::type&&...>( + GetSecondStage<InjectorStorage::RemoveAnnotations<AnnotatedArgs>>()(getFirstStageResults)...); + } + + // This is not inlined in operator() so that all the lazyGetPtr() calls happen first (instead of being interleaved + // with the get() calls). The lazyGetPtr() calls don't branch, while the get() calls branch on the result of the + // lazyGetPtr()s, so it's faster to execute them in this order. + template <typename... NodeItrs> + FRUIT_ALWAYS_INLINE C* outerConstructHelper(InjectorStorage& injector, FixedSizeAllocator& allocator, + NodeItrs... nodeItrs) { + // `injector' *is* used below, but when there are no AnnotatedArgs some compilers report it as unused. + (void)injector; + return innerConstructHelper( + injector, allocator, GetFirstStage<InjectorStorage::RemoveAnnotations<AnnotatedArgs>>()(injector, nodeItrs)...); + } + + FRUIT_ALWAYS_INLINE + C* operator()(InjectorStorage& injector, SemistaticGraph<TypeId, NormalizedBinding>& bindings, + FixedSizeAllocator& allocator, InjectorStorage::Graph::edge_iterator deps) { + + // `deps' *is* used below, but when there are no Args some compilers report it as unused. + (void)deps; + + InjectorStorage::Graph::node_iterator bindings_begin = bindings.begin(); + // `bindings_begin' *is* used below, but when there are no Args some compilers report it as unused. + (void)bindings_begin; + C* p = outerConstructHelper(injector, allocator, + injector.lazyGetPtr<typename InjectorStorage::TypeNormalizer<AnnotatedArgs>::type>( + deps, fruit::impl::meta::getIntValue<Indexes>(), bindings_begin)...); + return p; + } +}; + +template <typename C, typename AnnotatedSignature> +InjectorStorage::const_object_ptr_t InjectorStorage::createInjectedObjectForConstructor(InjectorStorage& injector, + Graph::node_iterator node_itr) { + C* cPtr = InvokeConstructorWithInjectedArgVector<AnnotatedSignature>()(injector, injector.bindings, + injector.allocator, node_itr.neighborsBegin()); + node_itr.setTerminal(); + return reinterpret_cast<InjectorStorage::object_ptr_t>(cPtr); +} + +template <typename AnnotatedSignature> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForConstructor() { + using AnnotatedC = SignatureType<AnnotatedSignature>; + using C = RemoveAnnotations<AnnotatedC>; + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::BindingForObjectToConstruct& binding = result.binding_for_object_to_construct; + binding.create = createInjectedObjectForConstructor<C, AnnotatedSignature>; + binding.deps = getBindingDeps<NormalizedSignatureArgs<AnnotatedSignature>>(); +#ifdef FRUIT_EXTRA_DEBUG + binding.is_nonconst = true; +#endif + return result; +} + +template <typename I, typename C, typename AnnotatedSignature> +InjectorStorage::const_object_ptr_t +InjectorStorage::createInjectedObjectForCompressedConstructor(InjectorStorage& injector, + Graph::node_iterator node_itr) { + C* cPtr = InvokeConstructorWithInjectedArgVector<AnnotatedSignature>()(injector, injector.bindings, + injector.allocator, node_itr.neighborsBegin()); + node_itr.setTerminal(); + I* iPtr = static_cast<I*>(cPtr); + return reinterpret_cast<object_ptr_t>(iPtr); +} + +template <typename AnnotatedSignature, typename AnnotatedI> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForCompressedConstructor() { + using AnnotatedC = SignatureType<AnnotatedSignature>; + using C = RemoveAnnotations<AnnotatedC>; + using I = RemoveAnnotations<AnnotatedI>; + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::COMPRESSED_BINDING; + result.type_id = getTypeId<AnnotatedI>(); + ComponentStorageEntry::CompressedBinding& binding = result.compressed_binding; + binding.c_type_id = getTypeId<AnnotatedC>(); + binding.create = createInjectedObjectForCompressedConstructor<I, C, AnnotatedSignature>; + return result; +} + +template <typename AnnotatedT> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator() { + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::MULTIBINDING_VECTOR_CREATOR; + result.type_id = getTypeId<AnnotatedT>(); + ComponentStorageEntry::MultibindingVectorCreator& binding = result.multibinding_vector_creator; + binding.get_multibindings_vector = createMultibindingVector<AnnotatedT>; + return result; +} + +template <typename I, typename C, typename AnnotatedCPtr> +InjectorStorage::object_ptr_t InjectorStorage::createInjectedObjectForMultibinding(InjectorStorage& m) { + C* cPtr = m.get<AnnotatedCPtr>(); + // This step is needed when the cast C->I changes the pointer + // (e.g. for multiple inheritance). + I* iPtr = static_cast<I*>(cPtr); + return reinterpret_cast<InjectorStorage::object_ptr_t>(iPtr); +} + +template <typename AnnotatedI, typename AnnotatedC> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForMultibinding() { + using AnnotatedCPtr = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::AddPointerInAnnotatedType(fruit::impl::meta::Type<AnnotatedC>)>>; + using I = RemoveAnnotations<AnnotatedI>; + using C = RemoveAnnotations<AnnotatedC>; + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + result.type_id = getTypeId<AnnotatedI>(); + ComponentStorageEntry::MultibindingForObjectToConstruct& binding = result.multibinding_for_object_to_construct; + binding.create = createInjectedObjectForMultibinding<I, C, AnnotatedCPtr>; + binding.deps = getBindingDeps<fruit::impl::meta::Vector<fruit::impl::meta::Type<AnnotatedC>>>(); + return result; +} + +template <typename AnnotatedC, typename C> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForInstanceMultibinding(C& instance) { + ComponentStorageEntry result; + result.kind = ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::MultibindingForConstructedObject& binding = result.multibinding_for_constructed_object; + binding.object_ptr = &instance; + return result; +} + +template <typename C, typename T, typename AnnotatedSignature, typename Lambda> +InjectorStorage::object_ptr_t InjectorStorage::createInjectedObjectForMultibindingProvider(InjectorStorage& injector) { + C* cPtr = InvokeLambdaWithInjectedArgVector<AnnotatedSignature, Lambda, std::is_pointer<T>::value>()( + injector, injector.allocator); + return reinterpret_cast<object_ptr_t>(cPtr); +} + +template <typename AnnotatedSignature, typename Lambda> +inline ComponentStorageEntry InjectorStorage::createComponentStorageEntryForMultibindingProvider() { +#ifdef FRUIT_EXTRA_DEBUG + using Signature = + fruit::impl::meta::UnwrapType<fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotationsFromSignature( + fruit::impl::meta::Type<AnnotatedSignature>)>>; + FruitStaticAssert(fruit::impl::meta::IsSame(fruit::impl::meta::Type<Signature>, + fruit::impl::meta::FunctionSignature(fruit::impl::meta::Type<Lambda>))); +#endif + + using AnnotatedT = SignatureType<AnnotatedSignature>; + using AnnotatedC = NormalizeType<AnnotatedT>; + using T = RemoveAnnotations<AnnotatedT>; + using C = NormalizeType<T>; + ComponentStorageEntry result; + bool needs_allocation = !std::is_pointer<T>::value; + if (needs_allocation) + result.kind = ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION; + else + result.kind = ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + result.type_id = getTypeId<AnnotatedC>(); + ComponentStorageEntry::MultibindingForObjectToConstruct& binding = result.multibinding_for_object_to_construct; + binding.create = createInjectedObjectForMultibindingProvider<C, T, AnnotatedSignature, Lambda>; + binding.deps = getBindingDeps<NormalizedSignatureArgs<AnnotatedSignature>>(); + return result; +} + +} // namespace fruit +} // namespace impl + +#endif // FRUIT_INJECTOR_STORAGE_DEFN_H diff --git a/include/fruit/impl/injector/injector_storage.h b/include/fruit/impl/injector/injector_storage.h new file mode 100644 index 0000000..6faa5f9 --- /dev/null +++ b/include/fruit/impl/injector/injector_storage.h @@ -0,0 +1,285 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_STORAGE_H +#define FRUIT_INJECTOR_STORAGE_H + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/data_structures/fixed_size_allocator.h> +#include <fruit/impl/meta/component.h> +#include <fruit/impl/normalized_component_storage/normalized_bindings.h> + +#include <unordered_map> +#include <vector> +#include <mutex> + +namespace fruit { +namespace impl { + +template <typename T> +struct GetHelper; + +/** + * A component where all types have to be explicitly registered, and all checks are at runtime. + * Used to implement Component<>, don't use directly. + * + * This class handles the creation of types of the forms: + * - shared_ptr<C>, [const] C*, [const] C&, C (where C is an atomic type) + * - Injector<T1, ..., Tk> (with T1, ..., Tk of the above forms). + */ +class InjectorStorage { +public: + // TODO: remove. + // using BindingVectors = std::pair<std::vector<std::pair<TypeId, BindingData>>, + // std::vector<std::pair<TypeId, MultibindingData>>>; + using Graph = SemistaticGraph<TypeId, NormalizedBinding>; + + template <typename AnnotatedT> + using RemoveAnnotations = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<AnnotatedT>)>>; + + // MSVC 14 has trouble specializing alias templates using expanded pack elements. + // This is a known issue: + // https://stackoverflow.com/questions/43411542/metaprogramming-failed-to-specialize-alias-template + // The workaround is just to use a struct directly. + template <typename AnnotatedT> + struct AnnotationRemover { + using type = RemoveAnnotations<AnnotatedT>; + }; + + template <typename T> + using NormalizeType = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::NormalizeType(fruit::impl::meta::Type<T>)>>; + + template <typename T> + struct TypeNormalizer { + using type = NormalizeType<T>; + }; + + template <typename Signature> + using SignatureType = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::SignatureType(fruit::impl::meta::Type<Signature>)>>; + + template <typename Signature> + using NormalizedSignatureArgs = fruit::impl::meta::Eval<fruit::impl::meta::NormalizeTypeVector( + fruit::impl::meta::SignatureArgs(fruit::impl::meta::Type<Signature>))>; + + // Prints the specified error and calls exit(1). + static void fatal(const std::string& error); + + template <typename AnnotatedI, typename AnnotatedC> + static ComponentStorageEntry createComponentStorageEntryForBind(); + + template <typename AnnotatedI, typename AnnotatedC> + static ComponentStorageEntry createComponentStorageEntryForConstBind(); + + template <typename AnnotatedC, typename C> + static ComponentStorageEntry createComponentStorageEntryForBindInstance(C& instance); + + template <typename AnnotatedC, typename C> + static ComponentStorageEntry createComponentStorageEntryForBindConstInstance(const C& instance); + + template <typename AnnotatedSignature, typename Lambda> + static ComponentStorageEntry createComponentStorageEntryForProvider(); + + template <typename AnnotatedSignature, typename Lambda, typename AnnotatedI> + static ComponentStorageEntry createComponentStorageEntryForCompressedProvider(); + + template <typename AnnotatedSignature> + static ComponentStorageEntry createComponentStorageEntryForConstructor(); + + template <typename AnnotatedSignature, typename AnnotatedI> + static ComponentStorageEntry createComponentStorageEntryForCompressedConstructor(); + + template <typename AnnotatedT> + static ComponentStorageEntry createComponentStorageEntryForMultibindingVectorCreator(); + + template <typename AnnotatedI, typename AnnotatedC> + static ComponentStorageEntry createComponentStorageEntryForMultibinding(); + + template <typename AnnotatedC, typename C> + static ComponentStorageEntry createComponentStorageEntryForInstanceMultibinding(C& instance); + + template <typename AnnotatedSignature, typename Lambda> + static ComponentStorageEntry createComponentStorageEntryForMultibindingProvider(); + +private: + // The NormalizedComponentStorage owned by this object (if any). + // Only used for the 1-argument constructor, otherwise it's nullptr. + std::unique_ptr<NormalizedComponentStorage> normalized_component_storage_ptr; + + FixedSizeAllocator allocator; + + // A graph with injected types as nodes (each node stores the NormalizedBindingData for the type) and dependencies as + // edges. + // For types that have a constructed object already, the corresponding node is stored as terminal node. + SemistaticGraph<TypeId, NormalizedBinding> bindings; + + // Maps the type index of a type T to the corresponding NormalizedMultibindingSet object (that stores all + // multibindings). + std::unordered_map<TypeId, NormalizedMultibindingSet> multibindings; + + // This mutex is used to synchronize concurrent accesses to this InjectorStorage object. + std::recursive_mutex mutex; + +private: + template <typename AnnotatedC> + static std::shared_ptr<char> createMultibindingVector(InjectorStorage& storage); + + // If not bound, returns nullptr. + NormalizedMultibindingSet* getNormalizedMultibindingSet(TypeId type); + + // Looks up the location where the type is (or will be) stored, but does not construct the class. + template <typename AnnotatedC> + Graph::node_iterator lazyGetPtr(); + + // getPtr() is equivalent to getPtrInternal(lazyGetPtr()) + template <typename C> + const C* getPtr(Graph::node_iterator itr); + + // Similar to the previous, but takes a node_iterator. Use this when the node_iterator is known, it's faster. + const void* getPtrInternal(Graph::node_iterator itr); + + // getPtr(typeInfo) is equivalent to getPtr(lazyGetPtr(typeInfo)). + Graph::node_iterator lazyGetPtr(TypeId type); + + // getPtr(deps, index) is equivalent to getPtr(lazyGetPtr(deps, index)). + Graph::node_iterator lazyGetPtr(Graph::edge_iterator deps, std::size_t dep_index); + + // Similar to getPtr, but the binding might not exist. Returns nullptr if it doesn't. + const void* unsafeGetPtr(TypeId type); + + void* getPtrForMultibinding(TypeId type); + + // Returns a std::vector<T*>*, or nullptr if there are no multibindings. + void* getMultibindings(TypeId type); + + // Constructs any necessary instances, but NOT the instance set. + void ensureConstructedMultibinding(NormalizedMultibindingSet& multibinding_set); + + template <typename T> + friend struct GetFirstStage; + + template <typename T> + friend class fruit::Provider; + + using object_ptr_t = void*; + using const_object_ptr_t = const void*; + + template <typename I, typename C, typename AnnotatedC> + static const_object_ptr_t createInjectedObjectForBind(InjectorStorage& injector, + InjectorStorage::Graph::node_iterator node_itr); + + template <typename C, typename T, typename AnnotatedSignature, typename Lambda> + static const_object_ptr_t createInjectedObjectForProvider(InjectorStorage& injector, Graph::node_iterator node_itr); + + template <typename I, typename C, typename T, typename AnnotatedSignature, typename Lambda> + static const_object_ptr_t createInjectedObjectForCompressedProvider(InjectorStorage& injector, + Graph::node_iterator node_itr); + + template <typename C, typename AnnotatedSignature> + static const_object_ptr_t createInjectedObjectForConstructor(InjectorStorage& injector, + Graph::node_iterator node_itr); + + template <typename I, typename C, typename AnnotatedSignature> + static const_object_ptr_t createInjectedObjectForCompressedConstructor(InjectorStorage& injector, + Graph::node_iterator node_itr); + + template <typename I, typename C, typename AnnotatedCPtr> + static object_ptr_t createInjectedObjectForMultibinding(InjectorStorage& m); + + template <typename C, typename T, typename AnnotatedSignature, typename Lambda> + static object_ptr_t createInjectedObjectForMultibindingProvider(InjectorStorage& injector); + +public: + // Wraps a std::vector<ComponentStorageEntry>::iterator as an iterator on tuples + // (typeId, normalizedBindingData, isTerminal, edgesBegin, edgesEnd) + struct BindingDataNodeIter { + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>::iterator itr; + + BindingDataNodeIter* operator->(); + + void operator++(); + + bool operator==(const BindingDataNodeIter& other) const; + bool operator!=(const BindingDataNodeIter& other) const; + + std::ptrdiff_t operator-(BindingDataNodeIter other) const; + + TypeId getId(); + NormalizedBinding getValue(); + bool isTerminal(); + const TypeId* getEdgesBegin(); + const TypeId* getEdgesEnd(); + }; + + /** + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + InjectorStorage(ComponentStorage&& storage, const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool); + + /** + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + InjectorStorage(const NormalizedComponentStorage& normalized_storage, ComponentStorage&& storage, + MemoryPool& memory_pool); + + // This is just the default destructor, but we declare it here to avoid including + // normalized_component_storage.h in fruit.h. + ~InjectorStorage(); + + InjectorStorage(InjectorStorage&&) = delete; + InjectorStorage& operator=(InjectorStorage&&) = delete; + + InjectorStorage(const InjectorStorage& other) = delete; + InjectorStorage& operator=(const InjectorStorage& other) = delete; + + // Usually get<T>() returns a T. + // However, get<Annotated<Annotation1, T>>() returns a T, not an Annotated<Annotation1, T>. + template <typename AnnotatedT> + RemoveAnnotations<AnnotatedT> get(); + + // Similar to the above, but specifying the node_iterator of the type. Use this together with lazyGetPtr when the + // node_iterator is known, it's faster. + // Note that T should *not* be annotated. + template <typename T> + T get(InjectorStorage::Graph::node_iterator node_iterator); + + // Looks up the location where the type is (or will be) stored, but does not construct the class. + // get<AnnotatedT>() is equivalent to get<AnnotatedT>(lazyGetPtr<Apply<NormalizeType, AnnotatedT>>(deps, dep_index)) + // and also to get<T> (lazyGetPtr<Apply<NormalizeType, AnnotatedT>>(deps, dep_index)) + // dep_index is the index of the dep in `deps'. + template <typename AnnotatedC> + Graph::node_iterator lazyGetPtr(Graph::edge_iterator deps, std::size_t dep_index, + Graph::node_iterator bindings_begin) const; + + // Returns nullptr if AnnotatedC was not bound. + template <typename AnnotatedC> + const RemoveAnnotations<AnnotatedC>* unsafeGet(); + + template <typename AnnotatedC> + const std::vector<RemoveAnnotations<AnnotatedC>*>& getMultibindings(); + + void eagerlyInjectMultibindings(); +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/injector/injector_storage.defn.h> + +#endif // FRUIT_INJECTOR_STORAGE_H diff --git a/include/fruit/impl/meta/algos.h b/include/fruit/impl/meta/algos.h new file mode 100644 index 0000000..a5a1996 --- /dev/null +++ b/include/fruit/impl/meta/algos.h @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_ALGOS_H +#define FRUIT_META_ALGOS_H + +#include <fruit/impl/fruit-config.h> +#include <fruit/impl/meta/immutable_map.h> + +namespace fruit { +namespace impl { +namespace meta { + +// We need a different (slower) implementation to workaround a Clang bug: +// https://llvm.org/bugs/show_bug.cgi?id=25669 +// TODO: remove this once that bug is fixed (for the appropriate Clang versions). +#if FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG + +struct HasDuplicatesHelper { + template <typename... Types> + struct apply { + using type = Bool<false>; + }; + + template <typename Type, typename... Types> + struct apply<Type, Types...> { + using type = Or(StaticOr<std::is_same<Type, Types>::value...>, Id<HasDuplicatesHelper(Types...)>); + }; +}; + +struct HasDuplicates { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using type = HasDuplicatesHelper(Types...); + }; +}; + +#else // !FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG + +// Checks if the given Vector has duplicated types. +struct HasDuplicates { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using M = VectorsToImmutableMap(Vector<Types...>, GenerateIntSequence(Int<sizeof...(Types)>)); + using type = Not(StaticAnd<Eval<ImmutableMapContainsKey(M, Types)>::value...>); + }; +}; + +#endif // FRUIT_HAS_CLANG_ARBITRARY_OVERLOAD_RESOLUTION_BUG + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_ALGOS_H diff --git a/include/fruit/impl/meta/basics.h b/include/fruit/impl/meta/basics.h new file mode 100644 index 0000000..8cb4604 --- /dev/null +++ b/include/fruit/impl/meta/basics.h @@ -0,0 +1,241 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_BASICS_H +#define FRUIT_META_BASICS_H + +#include <functional> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename T> +struct Type { + using type = T; +}; + +template <bool b> +struct Bool { + static constexpr bool value = b; +}; + +template <int n> +struct Int { + static constexpr int value = n; +}; + +// This was added to workaround a bug in MSVC 2017 15.5, that crashes when expanding Indexes::value... in some cases +// (where Indexes is a template parameter pack of Int<...> types). +// TODO: Remove this once MSVC 2017 is fixed and the fix has been out for some time. +template <typename N> +constexpr int getIntValue() { + return N::value; +} + +// None is used as "the nullptr of metaprogramming". E.g. when a function has no meaningful value to +// return, it can return None instead. +struct None {}; + +struct If {}; + +// PropagateError(E, X) evaluates E then X. The result is X's result, but if E returns an error, +// that's the result instead. +struct PropagateError {}; + +// Used to propagate an ErrorTag::apply<ErrorArgs...> up the instantiation chain, but without instantiating it right +// away, to allow shorter error stacktraces. +// Instantiating ErrorTag::apply<ErrorArgs...> must result in a static_assert error. +template <typename ErrorTag, typename... ErrorArgs> +struct Error {}; + +// Use as Catch(ExpressionThatMightThrow, ErrorTag, Handler) +// Handler(Error<ErrorTag, ErrorArgs...>) is called if ExpressionThatMightThrow throws ErrorTag. +struct Catch {}; + +// Use as CatchAll(ExpressionThatMightThrow, Handler) +// Handler(Error<ErrorTag, ErrorArgs...>) is called if ExpressionThatMightThrow throws any error. +struct CatchAll {}; + +// Call(F, Args...) is equivalent to F(Args...) in a metaexpression, except that Call(F, Args...) +// also works when F is a metaexpression. +struct Call { + template <typename F, typename... Args> + struct apply : public F::template apply<Args...> {}; +}; + +// UnwrapType<Type<T>> is T. +template <typename WrappedType> +using UnwrapType = typename WrappedType::type; + +// MSVC 14 has trouble specializing alias templates using expanded pack elements. +// This is a known issue: +// https://stackoverflow.com/questions/43411542/metaprogramming-failed-to-specialize-alias-template +// The workaround is just to use a struct directly. +// typename TypeUnwrapper<Type<T>>::type is T. +template <typename WrappedType> +struct TypeUnwrapper { + using type = UnwrapType<WrappedType>; +}; + +// Logical And with short-circuit evaluation. +struct And { + template <typename... MetaExprs> + struct apply { + using type = Bool<true>; + }; + + template <typename MetaExpr> + struct apply<MetaExpr> { + using type = MetaExpr; + }; + + template <typename MetaExpr, typename MetaExpr2> + struct apply<MetaExpr, MetaExpr2> { + using type = If(MetaExpr, MetaExpr2, Bool<false>); + }; + + template <typename MetaExpr, typename MetaExpr2, typename... MetaExprs> + struct apply<MetaExpr, MetaExpr2, MetaExprs...> { + using type = If(MetaExpr, If(MetaExpr2, And(MetaExprs...), Bool<false>), Bool<false>); + }; +}; + +// Logical Or with short-circuit evaluation. +struct Or { + template <typename... MetaExprs> + struct apply { + using type = Bool<false>; + }; + + template <typename MetaExpr> + struct apply<MetaExpr> { + using type = MetaExpr; + }; + + template <typename MetaExpr, typename MetaExpr2> + struct apply<MetaExpr, MetaExpr2> { + using type = If(MetaExpr, Bool<true>, MetaExpr2); + }; + + template <typename MetaExpr, typename MetaExpr2, typename... MetaExprs> + struct apply<MetaExpr, MetaExpr2, MetaExprs...> { + using type = If(MetaExpr, Bool<true>, If(MetaExpr2, Bool<true>, Or(MetaExprs...))); + }; +}; + +// Call(Call(DeferArgs(F), Args...), MoreArgs...) +// +// is equivalent to: +// Result = F(Args..., MoreArgs...) +// +// Note that you can't write: +// DeferArgs(F)(Args...)(MoreArgs...) +// +// Because Call must be used to call metafunctions that are metaexpressions. +struct DeferArgs { + template <typename F> + struct apply { + struct type { + template <typename... Args> + struct apply { + struct type { + template <typename... MoreArgs> + struct apply { + using type = F(Args..., MoreArgs...); + }; + }; + }; + }; + }; +}; + +// Call(PartialCall(F, Args...), MoreArgs...) +// +// is equivalent to: +// Result = F(Args..., MoreArgs...) +// +// Note that you can't write: +// PartialCall(F, Args...)(MoreArgs...) +// +// Because Call must be used to call metafunctions that are metaexpressions. +struct PartialCall { + template <typename F, typename... Args> + struct apply { + struct type { + template <typename... MoreArgs> + struct apply { + using type = F(Args..., MoreArgs...); + }; + }; + }; +}; + +struct IsSame { + template <typename T, typename U> + struct apply { + using type = Bool<false>; + }; + + template <typename T> + struct apply<T, T> { + using type = Bool<true>; + }; +}; + +struct Not { + template <typename B> + struct apply { + using type = Bool<!B::value>; + }; +}; + +struct IsNone { + template <typename T> + struct apply { + using type = Bool<false>; + }; +}; + +template <> +struct IsNone::apply<None> { + using type = Bool<true>; +}; + +template <typename T> +using Id = T; + +struct Identity { + template <typename T> + struct apply { + using type = T; + }; +}; + +template <typename T> +struct DebugTypeHelper { + static_assert(sizeof(T*) * 0 != 0, ""); + using type = T; +}; + +template <typename T> +using DebugType = typename DebugTypeHelper<T>::type; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_BASICS_H diff --git a/include/fruit/impl/meta/component.h b/include/fruit/impl/meta/component.h new file mode 100644 index 0000000..54aaf40 --- /dev/null +++ b/include/fruit/impl/meta/component.h @@ -0,0 +1,1012 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_COMPONENT_H +#define FRUIT_META_COMPONENT_H + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/fruit_internal_forward_decls.h> +#include <fruit/impl/injection_debug_errors.h> +#include <fruit/impl/meta/algos.h> +#include <fruit/impl/meta/errors.h> +#include <fruit/impl/meta/list.h> +#include <fruit/impl/meta/map.h> +#include <fruit/impl/meta/metaprogramming.h> +#include <fruit/impl/meta/numeric_operations.h> +#include <fruit/impl/meta/proof_trees.h> +#include <fruit/impl/meta/set.h> +#include <fruit/impl/meta/signatures.h> +#include <fruit/impl/meta/wrappers.h> + +#include <memory> +#include <type_traits> + +namespace fruit { +namespace impl { +namespace meta { + +//******************************************************************************************************************************** +// Part 1: Simple type functors (no ConsComp involved). +//******************************************************************************************************************************** + +// Given a type T, returns the class that should be injected to ensure that T is provided at runtime (if any). +struct GetClassForType { + // General case, if none of the following apply. + // When adding a specialization here, make sure that the ComponentStorage + // can actually get<> the specified type when the class was registered. + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<T*>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<T&>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T*>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T&>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<std::shared_ptr<T>>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<Assisted<T>>> { + using type = None; + }; + + template <typename T> + struct apply<Type<Provider<T>>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<Provider<const T>>> { + using type = Type<T>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Type<T>; + }; +}; + +struct GetClassForTypeVector { + template <typename V> + struct apply { + using type = TransformVector(V, GetClassForType); + }; +}; + +// Given a type T, returns the type in the injection graph that corresponds to T. +struct NormalizeType { + // When adding a specialization here, make sure that the ComponentStorage + // can actually get<> the specified type when the class was registered. + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<T*>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<T&>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T*>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T&>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<std::shared_ptr<T>>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<Assisted<T>>> { + using type = None; + }; + + template <typename T> + struct apply<Type<Provider<T>>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<Provider<const T>>> { + using type = Type<T>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Type<fruit::Annotated<Annotation, UnwrapType<Eval<NormalizeType(Type<T>)>>>>; + }; +}; + +struct NormalizeUntilStable { + template <typename T> + struct apply { + using type = If(IsSame(NormalizeType(T), T), T, NormalizeUntilStable(NormalizeType(T))); + }; +}; + +struct NormalizeTypeVector { + template <typename V> + struct apply { + using type = TransformVector(V, NormalizeType); + }; +}; + +struct TypeInjectionRequiresNonConstBinding { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<const T>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<T*>> { + using type = Bool<true>; + }; + + template <typename T> + struct apply<Type<T&>> { + using type = Bool<true>; + }; + + template <typename T> + struct apply<Type<const T*>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<const T&>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<std::shared_ptr<T>>> { + using type = Bool<true>; + }; + + template <typename T> + struct apply<Type<Assisted<T>>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<Provider<T>>> { + using type = Bool<true>; + }; + + template <typename T> + struct apply<Type<Provider<const T>>> { + using type = Bool<false>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = TypeInjectionRequiresNonConstBinding(Type<T>); + }; +}; + +// Returns U wrapped in the same annotations in AnnotatedT (if any). +struct CopyAnnotation { + template <typename AnnotatedT, typename U> + struct apply; + + template <typename T, typename U> + struct apply { + using type = U; + }; + + template <typename Annotation, typename T, typename U> + struct apply<Type<fruit::Annotated<Annotation, T>>, Type<U>> { + using type = Type<fruit::Annotated<Annotation, U>>; + }; +}; + +struct IsValidSignature { + template <typename Signature> + struct apply { + using type = Bool<false>; + }; + + template <typename T, typename... Args> + struct apply<Type<T(Args...)>> { + using type = Bool<true>; + }; +}; + +// Removes the Annotation (if any) wrapping a type T. +struct RemoveAnnotations { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Type<T>; + }; +}; + +// Removes the Annotation(s) (if any) wrapping the types in AnnotatedSignature. +struct RemoveAnnotationsFromSignature { + template <typename AnnotatedSignature> + struct apply { + using type = ConstructError(NotASignatureErrorTag, AnnotatedSignature); + }; + + template <typename AnnotatedT, typename... AnnotatedArgs> + struct apply<Type<AnnotatedT(AnnotatedArgs...)>> { + using type = ConsSignature(RemoveAnnotations(Type<AnnotatedT>), Id<RemoveAnnotations(Type<AnnotatedArgs>)>...); + }; +}; + +// Removes the Annotation(s) (if any) wrapping the types in the Vector V. +struct RemoveAnnotationsFromVector { + template <typename V> + struct apply { + using type = TransformVector(V, RemoveAnnotations); + }; +}; + +// Maps T->T* in a possibly-annotated type. +struct AddPointerInAnnotatedType { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T*>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Type<fruit::Annotated<Annotation, T*>>; + }; +}; + +// TODO: This also does UnlabelAssisted<>. Consider renaming and/or removing that logic (and +// letting callers do the unlabeling when desired). +struct RemoveNonAssisted { + template <typename V> + struct apply { + struct Helper { + // Non-assisted case + template <typename CurrentResult, typename T> + struct apply { + using type = CurrentResult; + }; + + template <typename CurrentResult, typename T> + struct apply<CurrentResult, Type<Assisted<T>>> { + using type = PushBack(CurrentResult, Type<T>); + }; + }; + + using type = FoldVector(V, Helper, Vector<>); + }; +}; + +struct RemoveAssisted { + template <typename V> + struct apply { + struct Helper { + // Non-assisted case + template <typename CurrentResult, typename T> + struct apply { + using type = PushBack(CurrentResult, T); + }; + + // Assisted case + template <typename CurrentResult, typename T> + struct apply<CurrentResult, Type<Assisted<T>>> { + using type = CurrentResult; + }; + }; + + using type = FoldVector(V, Helper, Vector<>); + }; +}; + +struct UnlabelAssistedSingleType { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<Assisted<T>>> { + using type = Type<T>; + }; +}; + +struct UnlabelAssisted { + template <typename V> + struct apply { + using type = TransformVector(V, UnlabelAssistedSingleType); + }; +}; + +struct RequiredLambdaArgsForAssistedFactory { + template <typename AnnotatedSignature> + struct apply { + using type = RemoveAnnotationsFromVector(UnlabelAssisted(SignatureArgs(AnnotatedSignature))); + }; +}; + +struct RequiredLambdaSignatureForAssistedFactory { + template <typename AnnotatedSignature> + struct apply { + using type = ConsSignatureWithVector(RemoveAnnotations(SignatureType(AnnotatedSignature)), + RequiredLambdaArgsForAssistedFactory(AnnotatedSignature)); + }; +}; + +struct InjectedFunctionArgsForAssistedFactory { + template <typename AnnotatedSignature> + struct apply { + using type = RemoveNonAssisted(SignatureArgs(AnnotatedSignature)); + }; +}; + +struct InjectedSignatureForAssistedFactory { + template <typename AnnotatedSignature> + struct apply { + using type = ConsSignatureWithVector(RemoveAnnotations(SignatureType(AnnotatedSignature)), + InjectedFunctionArgsForAssistedFactory(AnnotatedSignature)); + }; +}; + +struct IsAssisted { + template <typename T> + struct apply { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<Assisted<T>>> { + using type = Bool<true>; + }; +}; + +struct NumAssisted { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using type = SumAll(typename IsAssisted::apply<Types>::type...); + }; +}; + +// Counts the number of Assisted<> types in V before the given index. +struct NumAssistedBefore { + template <typename Index, typename V> + struct apply; + + template <typename V> + struct apply<Int<0>, V> { + using type = Int<0>; + }; + + template <int n, typename V> + struct apply<Int<n>, V> { + using N = Int<n>; + using type = Minus(NumAssisted(V), NumAssisted(VectorRemoveFirstN(V, N))); + }; +}; + +// Checks whether C is auto-injectable thanks to an Inject typedef. +struct HasInjectAnnotation { + template <typename C> + struct apply; + + template <typename C> + struct apply<Type<C>> { + template <typename C1> + static Bool<true> test(typename C1::Inject*); + + template <typename> + static Bool<false> test(...); + + using type = decltype(test<C>(nullptr)); + }; +}; + +struct DoGetInjectAnnotation { + template <typename C> + struct apply; + + template <typename C> + struct apply<Type<C>> { + using type = Type<typename C::Inject>; + }; +}; + +struct GetInjectAnnotation { + template <typename AnnotatedC> + struct apply { + using C = RemoveAnnotations(AnnotatedC); + using DecoratedS = DoGetInjectAnnotation(C); + using SResult = SignatureType(DecoratedS); + using AnnotatedSArgs = SignatureArgs(DecoratedS); + using SArgs = RemoveAnnotationsFromVector(UnlabelAssisted(AnnotatedSArgs)); + // We replace the non-annotated return type with the potentially-annotated AnnotatedC. + using AnnotatedDecoratedS = ConsSignatureWithVector(AnnotatedC, AnnotatedSArgs); + using type = If(IsAbstract(C), ConstructError(CannotConstructAbstractClassErrorTag, C), + If(Not(IsValidSignature(DecoratedS)), + ConstructError(InjectTypedefNotASignatureErrorTag, C, DecoratedS), + If(Not(IsSame(SResult, RemoveAnnotations(SResult))), + ConstructError(InjectTypedefWithAnnotationErrorTag, C), + If(Not(IsSame(C, SResult)), ConstructError(InjectTypedefForWrongClassErrorTag, C, SResult), + If(Not(IsConstructibleWithVector(C, SArgs)), + ConstructError(NoConstructorMatchingInjectSignatureErrorTag, C, + ConsSignatureWithVector(SResult, SArgs)), + AnnotatedDecoratedS))))); + }; +}; + +//******************************************************************************************************************************** +// Part 2: Type functors involving at least one ConsComp. +//******************************************************************************************************************************** + +template <typename RsSupersetParam, typename PsParam, typename NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + typename DepsParam, +#endif + typename InterfaceBindingsParam, typename DeferredBindingFunctorsParam> +struct Comp { + // The actual set of requirements is SetDifference(RsSuperset, Ps) + // We don't store Rs explicitly because we'd need to remove elements very often (and that's slow). + using RsSuperset = RsSupersetParam; + + using Ps = PsParam; + // This is a set of normalized types. + // - If a type is in SetDifference(RsSuperset, Ps) and not here: it's required as const only + // - If a type is in SetDifference(RsSuperset, Ps) and also here: it's required as non-const + // - If a type is in Ps and not here: it's provided as const only + // - If a type is in Ps and also here: it's provided as non-const + using NonConstRsPs = NonConstRsPsParam; +#ifndef FRUIT_NO_LOOP_CHECK + using Deps = DepsParam; +#endif + using InterfaceBindings = InterfaceBindingsParam; + using DeferredBindingFunctors = DeferredBindingFunctorsParam; + + // Invariants: + // * all types appearing as arguments of Deps are in Rs + // * all types in Ps are at the head of one (and only one) Dep. + // (note that the types in Rs can appear in deps any number of times, 0 is also ok) + // * Deps is of the form Vector<Dep...> with each Dep of the form T(Args...) and where Vector<Args...> is a set (no + // repetitions). + // * Bindings is a proof tree forest, with injected classes as formulas. + // * Each element X of the list DeferredBindingFunctors has: + // - a default-constructible X::apply<Comp> type + // - a void X::apply<Comp>::operator(ComponentStorage&) + // - an X::apply<Comp>::Result type + // * Each element of NonConstRsPs is in RsSuperset or in Ps (or both) +}; + +// Using ConsComp instead of Comp<...> in a meta-expression allows the types to be evaluated. +// See ConsVector for more details. +struct ConsComp { + template <typename RsSupersetParam, typename PsParam, typename NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + typename DepsParam, +#endif + typename InterfaceBindingsParam, typename DeferredBindingFunctorsParam> + struct apply { + using type = Comp<RsSupersetParam, PsParam, NonConstRsPsParam, +#ifndef FRUIT_NO_LOOP_CHECK + DepsParam, +#endif + InterfaceBindingsParam, DeferredBindingFunctorsParam>; + }; +}; + +struct GetComponentDeps { + template <typename Comp> + struct apply { + using type = typename Comp::Deps; + }; +}; + +struct GetComponentPs { + template <typename Comp> + struct apply { + using type = typename Comp::Ps; + }; +}; + +struct GetComponentRsSuperset { + template <typename Comp> + struct apply { + using type = typename Comp::RsSuperset; + }; +}; + +struct GetComponentNonConstRsPs { + template <typename Comp> + struct apply { + using type = typename Comp::NonConstRsPs; + }; +}; + +struct IsInjectableBareType { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<std::is_arithmetic<T>::value || std::is_class<T>::value || std::is_enum<T>::value>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<std::shared_ptr<T>>> { + using type = Bool<false>; + }; +}; + +// Checks if T is a (non-annotated) injectable type. +struct IsInjectableType { + template <typename T> + struct apply { + using type = IsInjectableBareType(NormalizeType(T)); + }; +}; + +// Checks that T is a (non-annotated) injectable type. If it isn't this returns an error, otherwise it returns None. +struct CheckInjectableType { + template <typename T> + struct apply { + using type = If(Not(IsInjectableType(T)), ConstructError(NonInjectableTypeErrorTag, T), None); + }; +}; + +// Checks that Types... are (non-annotated) injectable types. If they have an annotation or they are not injectable it +// an appropriate error is returned. +// Otherwise this returns None. +struct CheckInjectableTypeVector { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = PropagateError(CheckInjectableType(T), CurrentResult); + }; + }; + + template <typename V> + struct apply { + using type = FoldVector(V, Helper, None); + }; +}; + +// Checks that Types... are normalized and injectable types. If not it returns an appropriate error. +// If they are all normalized types this returns Result. +struct CheckNormalizedTypes { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Type<Types>...>> { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using NormalizedType = NormalizeType(T); + using type = PropagateError(CheckInjectableType(RemoveAnnotations(NormalizeUntilStable(T))), + If(Not(IsSame(NormalizeType(T), T)), + ConstructError(NonClassTypeErrorTag, RemoveAnnotations(T), + RemoveAnnotations(NormalizeUntilStable(T))), + CurrentResult)); + }; + }; + + using type = Fold(Helper, None, Type<Types>...); + }; +}; + +// Checks that Types... are not annotated types. If they have an annotation it returns an appropriate error. +// If none of them is annotated, this returns None. +struct CheckNotAnnotatedTypes { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Type<Types>...>> { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using TypeWithoutAnnotations = RemoveAnnotations(T); + using type = If(Not(IsSame(TypeWithoutAnnotations, T)), + ConstructError(AnnotatedTypeErrorTag, T, TypeWithoutAnnotations), CurrentResult); + }; + }; + + using type = Fold(Helper, None, Type<Types>...); + }; +}; + +// Check that there are no fruit::Required<> types in Component/NormalizedComponent's arguments. +// If there aren't any, this returns None. +struct CheckNoRequiredTypesInComponentArguments { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using type = None; + }; + + template <typename T, typename... OtherTypes> + struct apply<Vector<Type<T>, OtherTypes...>> { + using type = CheckNoRequiredTypesInComponentArguments(Vector<OtherTypes...>); + }; + + template <typename... RequiredArgs, typename... OtherTypes> + struct apply<Vector<Type<fruit::Required<RequiredArgs...>>, OtherTypes...>> { + using type = ConstructError(RequiredTypesInComponentArgumentsErrorTag, Type<fruit::Required<RequiredArgs...>>); + }; +}; + +// Check that there are no fruit::Required<> types in Injector's arguments. +// If there aren't any, this returns None. +struct CheckNoRequiredTypesInInjectorArguments { + template <typename... Types> + struct apply { + using type = None; + }; + + template <typename T, typename... Types> + struct apply<T, Types...> { + using type = CheckNoRequiredTypesInInjectorArguments(Types...); + }; + + template <typename... RequiredArgs, typename... Types> + struct apply<Type<fruit::Required<RequiredArgs...>>, Types...> { + using type = ConstructError(InjectorWithRequirementsErrorTag, Type<RequiredArgs>...); + }; +}; + +// Checks that there are no repetitions in Types. If there are, it returns an appropriate error. +// If there are no repetitions it returns None. +struct CheckNoRepeatedTypes { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using type = If(HasDuplicates(Vector<Types...>), ConstructError(RepeatedTypesErrorTag, Types...), None); + }; +}; + +struct RemoveConstFromType { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T>; + }; + + template <typename T> + struct apply<Type<const T>> { + using type = Type<T>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, T>>> { + using type = Type<fruit::Annotated<Annotation, T>>; + }; + + template <typename Annotation, typename T> + struct apply<Type<fruit::Annotated<Annotation, const T>>> { + using type = Type<fruit::Annotated<Annotation, T>>; + }; +}; + +struct RemoveConstFromTypes { + template <typename V> + struct apply; + + template <typename... Types> + struct apply<Vector<Types...>> { + using type = ConsVector(Id<RemoveConstFromType(Types)>...); + }; +}; + +struct RemoveConstTypes { + struct Helper { + template <typename Acc, typename T> + struct apply; + + template <typename... AccContent, typename T> + struct apply<Vector<AccContent...>, Type<const T>> { + using type = Vector<AccContent...>; + }; + + template <typename... AccContent, typename T> + struct apply<Vector<AccContent...>, Type<T>> { + using type = Vector<AccContent..., Type<T>>; + }; + + template <typename... AccContent, typename Annotation, typename T> + struct apply<Vector<AccContent...>, Type<fruit::Annotated<Annotation, const T>>> { + using type = Vector<AccContent...>; + }; + + template <typename... AccContent, typename Annotation, typename T> + struct apply<Vector<AccContent...>, Type<fruit::Annotated<Annotation, T>>> { + using type = Vector<AccContent..., Type<fruit::Annotated<Annotation, T>>>; + }; + }; + + template <typename V> + struct apply { + using type = FoldVector(V, Helper, Vector<>); + }; +}; + +// From a vector of injected types, this filters out the types that only require const bindings and then normalizes +// the types in the result. +struct NormalizedNonConstTypesIn { + struct Helper { + template <typename Acc, typename T> + struct apply { + using type = If(TypeInjectionRequiresNonConstBinding(T), PushBack(Acc, NormalizeType(T)), Acc); + }; + }; + + template <typename V> + struct apply { + using type = FoldVector(V, Helper, Vector<>); + }; +}; + +struct ConstructComponentImpl { + // Non-specialized case: no requirements. + template <typename... Ps> + struct apply { + using type = PropagateError( + CheckNoRepeatedTypes(RemoveConstFromTypes(Vector<Ps...>)), + PropagateError(CheckNormalizedTypes(RemoveConstFromTypes(Vector<Ps...>)), + PropagateError(CheckNoRequiredTypesInComponentArguments(Vector<Ps...>), + ConsComp(EmptySet, VectorToSetUnchecked(RemoveConstFromTypes(Vector<Ps...>)), + RemoveConstTypes(Vector<Ps...>), +#ifndef FRUIT_NO_LOOP_CHECK + Vector<Pair<Ps, Vector<>>...>, +#endif + Vector<>, EmptyList)))); + }; + + // With requirements. + template <typename... Rs, typename... Ps> + struct apply<Type<Required<Rs...>>, Ps...> { + using type1 = PropagateError( + CheckNoRepeatedTypes(RemoveConstFromTypes(Vector<Type<Rs>..., Ps...>)), + PropagateError(CheckNormalizedTypes(RemoveConstFromTypes(Vector<Type<Rs>..., Ps...>)), + PropagateError(CheckNoRequiredTypesInComponentArguments(Vector<Ps...>), + ConsComp(VectorToSetUnchecked(RemoveConstFromTypes(Vector<Type<Rs>...>)), + VectorToSetUnchecked(RemoveConstFromTypes(Vector<Ps...>)), + RemoveConstTypes(Vector<Type<Rs>..., Ps...>), +#ifndef FRUIT_NO_LOOP_CHECK + Vector<Pair<Ps, Vector<Type<Rs>...>>...>, +#endif + Vector<>, EmptyList)))); + +#if !defined(FRUIT_NO_LOOP_CHECK) && defined(FRUIT_EXTRA_DEBUG) + using Loop = ProofForestFindLoop(GetComponentDeps(type1)); + using type = If(IsNone(Loop), type1, ConstructErrorWithArgVector(SelfLoopErrorTag, Loop)); +#else // defined(FRUIT_NO_LOOP_CHECK) || !defined(FRUIT_EXTRA_DEBUG) + using type = type1; +#endif // defined(FRUIT_NO_LOOP_CHECK) || !defined(FRUIT_EXTRA_DEBUG) + }; +}; + +struct CheckTypesNotProvidedAsConst { + template <typename Comp, typename V> + struct apply { + struct Helper { + template <typename Acc, typename T> + struct apply { + using type = If(And(IsInSet(T, typename Comp::Ps), Not(IsInSet(T, typename Comp::NonConstRsPs))), + ConstructError(NonConstBindingRequiredButConstBindingProvidedErrorTag, T), Acc); + }; + }; + + using type = FoldVector(V, Helper, None); + }; +}; + +// Adds the types in NewRequirementsVector to the requirements (unless they are already provided/required). +// The caller must convert the types to the corresponding class type and expand any Provider<>s. +struct AddRequirements { + template <typename Comp, typename NewRequirementsVector, typename NewNonConstRequirementsVector> + struct apply { + using Comp1 = ConsComp(FoldVector(NewRequirementsVector, AddToSet, typename Comp::RsSuperset), typename Comp::Ps, + FoldVector(NewNonConstRequirementsVector, AddToSet, typename Comp::NonConstRsPs), +#ifndef FRUIT_NO_LOOP_CHECK + typename Comp::Deps, +#endif + typename Comp::InterfaceBindings, typename Comp::DeferredBindingFunctors); + using type = PropagateError(CheckTypesNotProvidedAsConst(Comp, NewNonConstRequirementsVector), Comp1); + }; +}; + +// Similar to AddProvidedType, but doesn't report an error if a Bind<C, CImpl> was present. +struct AddProvidedTypeIgnoringInterfaceBindings { + template <typename Comp, typename C, typename IsNonConst, typename CRequirements, typename CNonConstRequirements> + struct apply { + using Comp1 = ConsComp( + FoldVector(CRequirements, AddToSet, typename Comp::RsSuperset), AddToSetUnchecked(typename Comp::Ps, C), + If(IsNonConst, AddToSetUnchecked(FoldVector(CNonConstRequirements, AddToSet, typename Comp::NonConstRsPs), C), + FoldVector(CNonConstRequirements, AddToSet, typename Comp::NonConstRsPs)), +#ifndef FRUIT_NO_LOOP_CHECK + PushFront(typename Comp::Deps, Pair<C, CRequirements>), +#endif + typename Comp::InterfaceBindings, typename Comp::DeferredBindingFunctors); + using type = If(IsInSet(C, typename Comp::Ps), ConstructError(TypeAlreadyBoundErrorTag, C), + PropagateError(CheckTypesNotProvidedAsConst(Comp, CNonConstRequirements), Comp1)); + }; +}; + +// Adds C to the provides and removes it from the requirements (if it was there at all). +// Also checks that it wasn't already provided. +// Moreover, adds the requirements of C to the requirements, unless they were already provided/required. +// The caller must convert the types to the corresponding class type and expand any Provider<>s. +struct AddProvidedType { + template <typename Comp, typename C, typename IsNonConst, typename CRequirements, typename CNonConstRequirements> + struct apply { + using type = If(Not(IsNone(FindInMap(typename Comp::InterfaceBindings, C))), + ConstructError(TypeAlreadyBoundErrorTag, C), + AddProvidedTypeIgnoringInterfaceBindings(Comp, C, IsNonConst, CRequirements, + CNonConstRequirements)); + }; +}; + +struct AddDeferredBinding { + template <typename Comp, typename DeferredBinding> + struct apply { + using new_DeferredBindingFunctors = Cons<DeferredBinding, typename Comp::DeferredBindingFunctors>; + using type = ConsComp(typename Comp::RsSuperset, typename Comp::Ps, typename Comp::NonConstRsPs, +#ifndef FRUIT_NO_LOOP_CHECK + typename Comp::Deps, +#endif + typename Comp::InterfaceBindings, new_DeferredBindingFunctors); + }; +}; + +struct CheckNoLoopInDeps { + template <typename Comp> + struct apply { + using Loop = ProofForestFindLoop(typename Comp::Deps); + using type = If(IsNone(Loop), Bool<true>, ConstructErrorWithArgVector(SelfLoopErrorTag, Loop)); + }; +}; + +#if defined(FRUIT_EXTRA_DEBUG) || defined(FRUIT_IN_META_TEST) +struct CheckComponentEntails { + template <typename Comp, typename EntailedComp> + struct apply { + using CompRs = SetDifference(typename Comp::RsSuperset, typename Comp::Ps); + using EntailedCompRs = SetDifference(typename EntailedComp::RsSuperset, typename EntailedComp::Ps); + using CommonRs = SetIntersection(CompRs, EntailedCompRs); + using CommonPs = SetIntersection(typename Comp::Ps, typename EntailedComp::Ps); + using type = + If(Not(IsContained(typename EntailedComp::Ps, typename Comp::Ps)), + ConstructErrorWithArgVector(ComponentDoesNotEntailDueToProvidesErrorTag, + SetToVector(SetDifference(typename EntailedComp::Ps, typename Comp::Ps))), + If(Not(IsVectorContained(typename EntailedComp::InterfaceBindings, typename Comp::InterfaceBindings)), + ConstructErrorWithArgVector(ComponentDoesNotEntailDueToInterfaceBindingsErrorTag, + SetToVector(SetDifference(typename EntailedComp::InterfaceBindings, + typename Comp::InterfaceBindings))), + If(Not(IsContained(CompRs, EntailedCompRs)), + ConstructErrorWithArgVector(ComponentDoesNotEntailDueToRequirementsErrorTag, + SetToVector(SetDifference(CompRs, EntailedCompRs))), + If(Not(IsContained(SetIntersection(CommonRs, typename Comp::NonConstRsPs), + typename EntailedComp::NonConstRsPs)), + ConstructErrorWithArgVector(ComponentDoesNotEntailDueToDifferentConstnessOfRequirementsErrorTag, + SetToVector(SetDifference(SetIntersection(CommonRs, + typename Comp::NonConstRsPs), + typename EntailedComp::NonConstRsPs))), + If(Not(IsContained(SetIntersection(CommonPs, typename EntailedComp::NonConstRsPs), + typename Comp::NonConstRsPs)), + ConstructErrorWithArgVector( + ComponentDoesNotEntailDueToDifferentConstnessOfProvidesErrorTag, + SetToVector(SetDifference(SetIntersection(CommonPs, typename EntailedComp::NonConstRsPs), + typename Comp::NonConstRsPs))), + Bool<true>))))); + static_assert(true || sizeof(typename CheckIfError<Eval<type>>::type), ""); + }; +}; +#endif // defined(FRUIT_EXTRA_DEBUG) || defined(FRUIT_IN_META_TEST) + +// This calls ConstructError(NoBindingFoundErrorTag, ...) or +// ConstructError(NoBindingFoundForAbstractClassErrorTag, ...) as appropriate. +// Call this when we're unable to auto-inject a type AnnotatedC and we're giving up. +struct ConstructNoBindingFoundError { + template <typename AnnotatedC> + struct apply { + using type = If(IsAbstract(RemoveAnnotations(AnnotatedC)), + ConstructError(NoBindingFoundForAbstractClassErrorTag, AnnotatedC, RemoveAnnotations(AnnotatedC)), + ConstructError(NoBindingFoundErrorTag, AnnotatedC)); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_COMPONENT_H diff --git a/include/fruit/impl/meta/errors.h b/include/fruit/impl/meta/errors.h new file mode 100644 index 0000000..6f9de8a --- /dev/null +++ b/include/fruit/impl/meta/errors.h @@ -0,0 +1,67 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_ERRORS_H +#define FRUIT_META_ERRORS_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/logical_operations.h> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename T> +struct CheckIfError { + using type = T; +}; + +template <typename ErrorTag, typename... ErrorArgs> +struct CheckIfError<Error<ErrorTag, ErrorArgs...>> { + using type = typename ErrorTag::template apply<ErrorArgs...>; +}; + +// ConstructError(ErrorTag, Args...) returns Error<ErrorTag, Args...>. +// Never construct an Error<...> directly, using this metafunction makes debugging easier. +struct ConstructError { + template <typename ErrorTag, typename... Args> + struct apply { +#ifdef FRUIT_DEEP_TEMPLATE_INSTANTIATION_STACKTRACES_FOR_ERRORS + static_assert(true || sizeof(typename CheckIfError<Error<ErrorTag, UnwrapType<Args>...>>::type), ""); +#endif + using type = Error<ErrorTag, typename TypeUnwrapper<Args>::type...>; + }; +}; + +// Extracts the first error in the given types. +struct ExtractFirstError { + template <typename... Types> + struct apply; + + template <typename Type, typename... Types> + struct apply<Type, Types...> : public apply<Types...> {}; + + template <typename ErrorTag, typename... ErrorParams, typename... Types> + struct apply<Error<ErrorTag, ErrorParams...>, Types...> { + using type = Error<ErrorTag, ErrorParams...>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_ERRORS_H diff --git a/include/fruit/impl/meta/eval.h b/include/fruit/impl/meta/eval.h new file mode 100644 index 0000000..268b7dd --- /dev/null +++ b/include/fruit/impl/meta/eval.h @@ -0,0 +1,259 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_EVAL_H +#define FRUIT_META_EVAL_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/errors.h> +#include <fruit/impl/meta/logical_operations.h> + +#include <functional> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename MetaExpr> +struct DoEval; + +// General case, meta-constant. +template <typename MetaExpr> +struct DoEval { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = MetaExpr; +}; + +template <typename Type> +struct SimpleIsError { + static constexpr bool value = false; +}; +template <typename ErrorTag, typename... ErrorArgs> +struct SimpleIsError<Error<ErrorTag, ErrorArgs...>> { + static constexpr bool value = true; +}; + +#ifdef FRUIT_EXTRA_DEBUG + +// For debugging, we use a separate DoEvalFun so that we get longer (and more informative) +// instantiation traces. + +template <typename MetaFun, typename... Params> +struct DoEvalFun { + using type = + typename DoEval<typename std::conditional<StaticOr<SimpleIsError<Params>::value...>::value, ExtractFirstError, + MetaFun>::type::template apply<Params...>::type>::type; +}; + +template <typename MetaFun, typename... MetaExprs> +struct DoEval<MetaFun(MetaExprs...)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEvalFun<MetaFun, typename DoEval<MetaExprs>::type...>::type; +}; + +// Similar to the previous specialization, but this will be selected when the function signature +// became a function pointer (this happens when a signature parameter is itself a signature). +template <typename MetaFun, typename... MetaExprs> +struct DoEval<MetaFun (*)(MetaExprs...)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEvalFun<MetaFun, typename DoEval<MetaExprs>::type...>::type; +}; + +#else // FRUIT_EXTRA_DEBUG + +template <typename MetaFun, typename... MetaExprs> +struct DoEval<MetaFun(MetaExprs...)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEval<typename std::conditional< + StaticOr<SimpleIsError<typename DoEval<MetaExprs>::type>::value...>::value, ExtractFirstError, + MetaFun>::type::template apply<typename DoEval<MetaExprs>::type...>::type>::type; +}; + +// Similar to the previous specialization, but this will be selected when the function signature +// became a function pointer (this happens when a signature parameter is itself a signature). +template <typename MetaFun, typename... MetaExprs> +struct DoEval<MetaFun (*)(MetaExprs...)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEval<typename std::conditional< + StaticOr<SimpleIsError<typename DoEval<MetaExprs>::type>::value...>::value, ExtractFirstError, + MetaFun>::type::template apply<typename DoEval<MetaExprs>::type...>::type>::type; +}; + +#endif // FRUIT_EXTRA_DEBUG + +template <typename ExprResult, typename ErrorTag, typename Handler> +struct EvalCatch { + using type = ExprResult; +}; + +template <typename CaughtErrorTag, typename... ErrorArgs, typename Handler> +struct EvalCatch<Error<CaughtErrorTag, ErrorArgs...>, CaughtErrorTag, Handler> { + using type = + typename DoEval<typename DoEval<Handler>::type::template apply<Error<CaughtErrorTag, ErrorArgs...>>::type>::type; +}; + +template <typename ExprResult, typename Handler> +struct EvalCatchAll { + using type = ExprResult; +}; + +template <typename CaughtErrorTag, typename... ErrorArgs, typename Handler> +struct EvalCatchAll<Error<CaughtErrorTag, ErrorArgs...>, Handler> { + using type = + typename DoEval<typename DoEval<Handler>::type::template apply<Error<CaughtErrorTag, ErrorArgs...>>::type>::type; +}; + +template <typename Expr, typename ErrorTag, typename Handler> +struct DoEval<Catch(Expr, ErrorTag, Handler)> { + using type = typename EvalCatch<typename DoEval<Expr>::type, typename DoEval<ErrorTag>::type, Handler>::type; +}; + +template <typename Expr, typename ErrorTag, typename Handler> +struct DoEval<Catch (*)(Expr, ErrorTag, Handler)> { + using type = typename EvalCatch<typename DoEval<Expr>::type, typename DoEval<ErrorTag>::type, Handler>::type; +}; + +template <typename Expr, typename Handler> +struct DoEval<CatchAll(Expr, Handler)> { + using type = typename EvalCatchAll<typename DoEval<Expr>::type, Handler>::type; +}; + +template <typename Expr, typename Handler> +struct DoEval<CatchAll (*)(Expr, Handler)> { + using type = typename EvalCatchAll<typename DoEval<Expr>::type, Handler>::type; +}; + +template <typename MetaBool, typename ThenMetaExpr, typename ElseMetaExpr> +struct EvalIf; + +template <typename ErrorTag, typename... ErrorArgs, typename ThenMetaExpr, typename ElseMetaExpr> +struct EvalIf<Error<ErrorTag, ErrorArgs...>, ThenMetaExpr, ElseMetaExpr> { + using type = Error<ErrorTag, ErrorArgs...>; +}; + +template <typename ThenMetaExpr, typename ElseMetaExpr> +struct EvalIf<Bool<true>, ThenMetaExpr, ElseMetaExpr> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEval<ThenMetaExpr>::type; +}; + +template <typename ThenMetaExpr, typename ElseMetaExpr> +struct EvalIf<Bool<false>, ThenMetaExpr, ElseMetaExpr> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename DoEval<ElseMetaExpr>::type; +}; + +template <typename CondMetaExpr, typename ThenMetaExpr, typename ElseMetaExpr> +struct DoEval<If(CondMetaExpr, ThenMetaExpr, ElseMetaExpr)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename EvalIf<typename DoEval<CondMetaExpr>::type, ThenMetaExpr, ElseMetaExpr>::type; +}; + +// Similar to the previous specialization, but this will be selected when the function signature +// became a function pointer (this happens when a signature parameter is itself a signature). +template <typename CondMetaExpr, typename ThenMetaExpr, typename ElseMetaExpr> +struct DoEval<If (*)(CondMetaExpr, ThenMetaExpr, ElseMetaExpr)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename EvalIf<typename DoEval<CondMetaExpr>::type, ThenMetaExpr, ElseMetaExpr>::type; +}; + +template <typename T, typename ElseMetaExpr> +struct EvalPropagateError { + using type = typename DoEval<ElseMetaExpr>::type; +}; + +template <typename ErrorTag, typename... ErrorArgs, typename ElseMetaExpr> +struct EvalPropagateError<Error<ErrorTag, ErrorArgs...>, ElseMetaExpr> { + using type = Error<ErrorTag, ErrorArgs...>; +}; + +template <typename MaybeErrorMetaExpr, typename ElseMetaExpr> +struct DoEval<PropagateError(MaybeErrorMetaExpr, ElseMetaExpr)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename EvalPropagateError<typename DoEval<MaybeErrorMetaExpr>::type, ElseMetaExpr>::type; +}; + +// Similar to the previous specialization, but this will be selected when the function signature +// became a function pointer (this happens when a signature parameter is itself a signature). +template <typename MaybeErrorMetaExpr, typename ElseMetaExpr> +struct DoEval<PropagateError (*)(MaybeErrorMetaExpr, ElseMetaExpr)> { +#ifdef FRUIT_TRACE_INSTANTIATIONS + constexpr static bool static_warning() __attribute__((deprecated("static_warning"))) { + return true; + } + static_assert(static_warning(), ""); +#endif + using type = typename EvalPropagateError<typename DoEval<MaybeErrorMetaExpr>::type, ElseMetaExpr>::type; +}; + +template <typename MetaExpr> +using Eval = typename DoEval<MetaExpr>::type; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_EVAL_H diff --git a/include/fruit/impl/meta/fold.h b/include/fruit/impl/meta/fold.h new file mode 100644 index 0000000..e8c86f5 --- /dev/null +++ b/include/fruit/impl/meta/fold.h @@ -0,0 +1,124 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_FOLD_H +#define FRUIT_META_FOLD_H + +#include <functional> + +namespace fruit { +namespace impl { +namespace meta { + +struct Fold { + template <typename F, typename InitialValue, typename... Types> + struct apply; + + template <typename F, typename InitialValue> + struct apply<F, InitialValue> { + using type = InitialValue; + }; + + template <typename F, typename InitialValue, typename T0> + struct apply<F, InitialValue, T0> { + using type = typename F::template apply<InitialValue, T0>::type; + }; + + template <typename F, typename InitialValue, typename T0, typename T1> + struct apply<F, InitialValue, T0, T1> { + using type = + typename F::template apply<typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, T1>::type; + }; + + template <typename F, typename InitialValue, typename T0, typename T1, typename T2> + struct apply<F, InitialValue, T0, T1, T2> { + using type = typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, T1>::type>::type, + T2>::type; + }; + + template <typename F, typename InitialValue, typename T0, typename T1, typename T2, typename T3> + struct apply<F, InitialValue, T0, T1, T2, T3> { + using type = typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, T1>::type>::type, + T2>::type>::type, + T3>::type; + }; + + template <typename F, typename InitialValue, typename T0, typename T1, typename T2, typename T3, typename T4> + struct apply<F, InitialValue, T0, T1, T2, T3, T4> { + using type = typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, T1>::type>::type, + T2>::type>::type, + T3>::type>::type, + T4>::type; + }; + + template <typename F, typename InitialValue, typename T0, typename T1, typename T2, typename T3, typename T4, + typename... Types> + struct apply<F, InitialValue, T0, T1, T2, T3, T4, Types...> { + using type = Fold( + F, typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, T1>::type>::type, + T2>::type>::type, + T3>::type>::type, + T4>::type, + Types...); + }; + + // Optimized specialization, processing 10 values at a time. + template <typename F, typename InitialValue, typename T0, typename T1, typename T2, typename T3, typename T4, + typename T5, typename T6, typename T7, typename T8, typename T9, typename... Types> + struct apply<F, InitialValue, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, Types...> { + using type = Fold( + F, + typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply< + typename DoEval<typename F::template apply<InitialValue, T0>::type>::type, + T1>::type>::type, + T2>::type>::type, + T3>::type>::type, + T4>::type>::type, + T5>::type>::type, + T6>::type>::type, + T7>::type>::type, + T8>::type>::type, + T9>::type, + Types...); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_FOLD_H diff --git a/include/fruit/impl/meta/graph.h b/include/fruit/impl/meta/graph.h new file mode 100644 index 0000000..ca8dece --- /dev/null +++ b/include/fruit/impl/meta/graph.h @@ -0,0 +1,110 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_GRAPH_H +#define FRUIT_META_GRAPH_H + +#include <fruit/impl/meta/immutable_map.h> +#include <fruit/impl/meta/map.h> +#include <fruit/impl/meta/set.h> +#include <fruit/impl/meta/triplet.h> + +namespace fruit { +namespace impl { +namespace meta { + +// A Graph is a Map from each node to a set containing its neighbors. + +using GetGraphNodes = GetImmutableMapKeys; + +using GraphFindNeighbors = FindInImmutableMap; + +using GraphContainsNode = ImmutableMapContainsKey; + +// Returns a loop in the given graph as a Vector<N1, ..., Nk> such that the graph contains a loop +// N1->...->Nk->N1, or None if there are no loops. +struct GraphFindLoop { + template <typename G> + struct apply { + using ImmutableG = VectorToImmutableMap(G); + + // DfsVisit(VisitedSet, VisitingSet, Node) does a DFS visit starting at Node and returns a + // Triplet<NewVisitedSet, Loop, IsLoopComplete>, where Loop is the Vector representing the part + // of the loop starting at Node (if any loop was found) or Null otherwise. + struct DfsVisit { + template <typename VisitedSet, typename VisitingSet, typename Node> + struct apply { + using NewVisitingSet = AddToSetUnchecked(VisitingSet, Node); + + struct VisitSingleNeighbor { + // CurrentResult is a Triplet<VisitedSet, Loop, IsLoopComplete> (where IsLoopComplete + // is only meaningful when Loop is not None). + template <typename CurrentResult, typename Neighbor> + struct apply { + using type = If(IsNone(typename CurrentResult::Second), + // Go ahead, no loop found yet. + DfsVisit(typename CurrentResult::First, NewVisitingSet, Neighbor), + // Found a loop in another neighbor of the same node, we don't need to + // visit this neighbor. + CurrentResult); + }; + }; + + using NewVisitedSet = AddToSet(VisitedSet, Node); + using Neighbors = GraphFindNeighbors(ImmutableG, Node); + using Result = FoldVector(Neighbors, VisitSingleNeighbor, ConsTriplet(NewVisitedSet, None, Bool<false>)); + using type = If(IsNone(Neighbors), + // No neighbors. + ConsTriplet(NewVisitedSet, None, Bool<false>), + If(IsInSet(Node, VisitingSet), + // We've just found a loop, since Node is another node that we're currently + // visiting + Triplet<VisitedSet, Vector<Node>, Bool<false>>, + If(IsNone(GetSecond(Result)), + // No loop found. + Result, + // Found a loop + If(GetThird(Result) /* IsLoopComplete */, Result, + If(VectorEndsWith(GetSecond(Result) /* Loop */, Node), + // The loop is complete now. + ConsTriplet(GetFirst(Result), GetSecond(Result), Bool<true>), + // Loop still not complete, add the current node. + ConsTriplet(GetFirst(Result), PushFront(GetSecond(Result), Node), Bool<false>)))))); + }; + }; + + struct VisitStartingAtNode { + // CurrentResult is a Pair<CurrentVisitedSet, Loop> + template <typename CurrentResult, typename Node> + struct apply { + using DfsResult = DfsVisit(GetFirst(CurrentResult), EmptySet, Node); + using type = If(IsNone(GetSecond(CurrentResult)), + // No loop found yet. + MakePair(GetFirst(DfsResult), GetSecond(DfsResult)), + // Found a loop, return early + CurrentResult); + }; + }; + + using type = GetSecond(FoldVector(GetMapKeys(G), VisitStartingAtNode, Pair<EmptySet, None>)); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_GRAPH_H diff --git a/include/fruit/impl/meta/immutable_map.h b/include/fruit/impl/meta/immutable_map.h new file mode 100644 index 0000000..898cc6e --- /dev/null +++ b/include/fruit/impl/meta/immutable_map.h @@ -0,0 +1,91 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_IMMUTABLE_MAP_H +#define FRUIT_META_IMMUTABLE_MAP_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/immutable_set.h> +#include <fruit/impl/meta/pair.h> +#include <fruit/impl/meta/set.h> + +namespace fruit { +namespace impl { +namespace meta { + +// ImmutableMap ::= ImmutableSet<Pair<Key1, Value1>, ..., Pair<KeyN, ValueN>> + +struct VectorsToImmutableMap { + template <typename KeyVector, typename ValueVector> + struct apply; + + template <typename... Keys, typename... Values> + struct apply<Vector<Keys...>, Vector<Values...>> { + using type = ConsImmutableSet<Pair<Keys, Values>...>; + }; +}; + +struct VectorToImmutableMap { + template <typename PairVector> + struct apply; + + template <typename... Pairs> + struct apply<Vector<Pairs...>> { + using type = ConsImmutableSet<Pairs...>; + }; +}; + +struct IsInImmutableMap { + template <typename S, typename T> + struct apply { + using type = Bool<std::is_base_of<T, S>::value>; + }; +}; + +struct FindInImmutableMap { + template <typename M, typename T> + struct apply { + template <typename Value> + static Value f(Pair<T, Value>*); + + static None f(void*); + + using type = decltype(f((M*)nullptr)); + }; +}; + +struct ImmutableMapContainsKey { + template <typename M, typename T> + struct apply { + using type = Not(IsNone(FindInImmutableMap(M, T))); + }; +}; + +struct GetImmutableMapKeys { + template <typename M> + struct apply; + + template <typename... Pairs> + struct apply<ConsImmutableSet<Pairs...>> { + using type = Vector<typename Pairs::First...>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_IMMUTABLE_MAP_H diff --git a/include/fruit/impl/meta/immutable_set.h b/include/fruit/impl/meta/immutable_set.h new file mode 100644 index 0000000..5e6998a --- /dev/null +++ b/include/fruit/impl/meta/immutable_set.h @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_IMMUTABLE_SET_H +#define FRUIT_META_IMMUTABLE_SET_H + +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/vector.h> + +namespace fruit { +namespace impl { +namespace meta { + +// ImmutableSet ::= ConsImmutableSet<Ts...> + +template <typename... Ts> +struct ConsImmutableSet : public Ts... {}; + +struct VectorToImmutableSet { + template <typename V> + struct apply; + + template <typename... Ts> + struct apply<Vector<Ts...>> { + using type = ConsImmutableSet<Ts...>; + }; +}; + +struct IsInImmutableSet { + template <typename S, typename T> + struct apply { + using type = Bool<std::is_base_of<T, S>::value>; + }; +}; + +struct SizeOfImmutableSet { + template <typename IS> + struct apply; + + template <typename... Ts> + struct apply<ConsImmutableSet<Ts...>> { + using type = Int<sizeof...(Ts)>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_IMMUTABLE_SET_H diff --git a/include/fruit/impl/meta/list.h b/include/fruit/impl/meta/list.h new file mode 100644 index 0000000..b0bb695 --- /dev/null +++ b/include/fruit/impl/meta/list.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_LIST_H +#define FRUIT_META_LIST_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/logical_operations.h> +#include <fruit/impl/meta/numeric_operations.h> +#include <functional> + +namespace fruit { +namespace impl { +namespace meta { + +// List ::= EmptyList | Cons<T, List> + +struct EmptyList {}; + +template <typename T, typename Tail> +struct Cons {}; + +// TODO: Consider inlining to improve performance. +// If L is a list containing T1,...,Tn this calculates F(InitialValue, F(T1, F(..., F(Tn) ...))). +struct FoldList { + template <typename L, typename F, typename InitialValue> + struct apply; + + template <typename F, typename InitialValue> + struct apply<EmptyList, F, InitialValue> { + using type = InitialValue; + }; + + template <typename Head, typename Tail, typename F, typename InitialValue> + struct apply<Cons<Head, Tail>, F, InitialValue> { + using type = FoldList(Tail, F, F(InitialValue, Head)); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_LIST_H diff --git a/include/fruit/impl/meta/logical_operations.h b/include/fruit/impl/meta/logical_operations.h new file mode 100644 index 0000000..e35ebeb --- /dev/null +++ b/include/fruit/impl/meta/logical_operations.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_LOGICAL_OPERATIONS_H +#define FRUIT_META_LOGICAL_OPERATIONS_H + +#include <fruit/impl/meta/basics.h> + +namespace fruit { +namespace impl { +namespace meta { + +template <bool... bs> +struct BoolVector {}; + +template <bool... bs> +using StaticAnd = Bool<std::is_same<BoolVector<bs...>, BoolVector<(true || bs)...>>::value>; + +template <bool... bs> +using StaticOr = Bool<!std::is_same<BoolVector<bs...>, BoolVector<(false && bs)...>>::value>; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_LOGICAL_OPERATIONS_H diff --git a/include/fruit/impl/meta/map.h b/include/fruit/impl/meta/map.h new file mode 100644 index 0000000..6291a59 --- /dev/null +++ b/include/fruit/impl/meta/map.h @@ -0,0 +1,104 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_MAP_H +#define FRUIT_META_MAP_H + +#include <fruit/impl/meta/set.h> + +namespace fruit { +namespace impl { +namespace meta { + +// A Map is a Set whose elements have the form Pair<Key, Value> + +struct GetMapKeys { + template <typename M> + struct apply; + + template <typename... Pairs> + struct apply<Vector<Pairs...>> { + using type = Vector<typename Pairs::First...>; + }; +}; + +// TODO: Consider implementing this by finding the position. +struct MapContainsKey { + template <typename TToFind> + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = CurrentResult; + }; + template <typename CurrentResult, typename Value> + struct apply<CurrentResult, Pair<TToFind, Value>> { + using type = Bool<true>; + }; + }; + + template <typename M, typename TToFind> + struct apply { + using type = FoldVector(M, Helper<TToFind>, Bool<false>); + }; +}; + +// TODO: Consider implementing this by finding the position first, then calling VectorRemoveFirstN +// and getting the first element. +struct FindInMap { + template <typename TToFind> + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = CurrentResult; + }; + template <typename CurrentResult, typename Value> + struct apply<CurrentResult, Pair<TToFind, Value>> { + using type = Value; + }; + }; + + template <typename M, typename TToFind> + struct apply { + using type = FoldVector(M, Helper<TToFind>, None); + }; +}; + +// TODO: Consider implementing this by finding the position first, then calling VectorRemoveFirstN +// and getting the first element. +struct FindValueInMap { + template <typename TToFind> + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = CurrentResult; + }; + template <typename CurrentResult, typename Value> + struct apply<CurrentResult, Pair<Value, TToFind>> { + using type = Value; + }; + }; + + template <typename M, typename TToFind> + struct apply { + using type = FoldVector(M, Helper<TToFind>, None); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_MAP_H diff --git a/include/fruit/impl/meta/metaprogramming.h b/include/fruit/impl/meta/metaprogramming.h new file mode 100644 index 0000000..c6190a0 --- /dev/null +++ b/include/fruit/impl/meta/metaprogramming.h @@ -0,0 +1,183 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_METAPROGRAMMING_H +#define FRUIT_META_METAPROGRAMMING_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/vector.h> + +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/fruit_internal_forward_decls.h> +#include <fruit/impl/injection_errors.h> +#include <fruit/impl/meta/errors.h> + +#include <memory> + +namespace fruit { +namespace impl { +namespace meta { + +struct IsConstructible { + template <typename C, typename... Args> + struct apply; + + template <typename C, typename... Args> + struct apply<Type<C>, Type<Args>...> { + using type = Bool<std::is_constructible<C, Args...>::value>; + }; +}; + +struct IsConstructibleWithVector { + template <typename C, typename V> + struct apply; + + template <typename C, typename... Types> + struct apply<Type<C>, Vector<Type<Types>...>> { + using type = Bool<std::is_constructible<C, Types...>::value>; + }; +}; + +struct AddPointer { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T*>; + }; +}; + +struct IsCallable { + template <typename T> + struct apply; + + template <typename C> + struct apply<Type<C>> { + template <typename C1> + static Bool<true> test(decltype(&C1::operator())); + + template <typename> + static Bool<false> test(...); + + using type = decltype(test<C>(nullptr)); + }; +}; + +struct GetCallOperatorSignature { + template <typename T> + struct apply; + + template <typename C> + struct apply<Type<C>> { + using type = Type<decltype(&C::operator())>; + }; +}; + +struct AddPointerToVector { + template <typename V> + struct apply; + + template <typename... Ts> + struct apply<Vector<Type<Ts>...>> { + using type = Vector<Type<Ts*>...>; + }; +}; + +struct GetNthTypeHelper { + template <typename N, typename... Ts> + struct apply; + + template <typename T, typename... Ts> + struct apply<Int<0>, T, Ts...> { + using type = T; + }; + + template <int n, typename T, typename... Ts> + struct apply<Int<n>, T, Ts...> { + using type = GetNthTypeHelper(Int<n - 1>, Ts...); + }; +}; + +struct GetNthType { + template <typename N, typename V> + struct apply; + + template <typename N, typename... Ts> + struct apply<N, Vector<Ts...>> { + using type = GetNthTypeHelper(N, Ts...); + }; +}; + +struct FunctorResultHelper { + template <typename MethodSignature> + struct apply; + + template <typename Result, typename Functor, typename... Args> + struct apply<Type<Result (Functor::*)(Args...)>> { + using type = Type<Result>; + }; +}; + +struct FunctorResult { + template <typename F> + struct apply; + + template <typename F> + struct apply<Type<F>> { + using type = FunctorResultHelper(Type<decltype(&F::operator())>); + }; +}; + +struct FunctionSignatureHelper { + template <typename LambdaMethod> + struct apply; + + template <typename Result, typename LambdaObject, typename... Args> + struct apply<Type<Result (LambdaObject::*)(Args...) const>> { + using type = Type<Result(Args...)>; + }; +}; + +// Function is either a plain function type of the form T(*)(Args...) or a lambda. +struct FunctionSignature { + template <typename Function> + struct apply; + + template <typename Function> + struct apply<Type<Function>> { + using CandidateSignature = FunctionSignatureHelper(GetCallOperatorSignature(Type<Function>)); + using type = If(Not(IsCallable(Type<Function>)), ConstructError(NotALambdaErrorTag, Type<Function>), + If(Not(IsConstructible(AddPointer(CandidateSignature), Type<Function>)), + ConstructError(FunctorUsedAsProviderErrorTag, Type<Function>), CandidateSignature)); + }; + + template <typename Result, typename... Args> + struct apply<Type<Result(Args...)>> { + using type = Type<Result(Args...)>; + }; + + template <typename Result, typename... Args> + struct apply<Type<Result (*)(Args...)>> { + using type = Type<Result(Args...)>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_METAPROGRAMMING_H diff --git a/include/fruit/impl/meta/numeric_operations.h b/include/fruit/impl/meta/numeric_operations.h new file mode 100644 index 0000000..be362c1 --- /dev/null +++ b/include/fruit/impl/meta/numeric_operations.h @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_NUMERIC_OPERATIONS_H +#define FRUIT_META_NUMERIC_OPERATIONS_H + +#include <fruit/impl/meta/basics.h> + +namespace fruit { +namespace impl { +namespace meta { + +struct Sum { + template <typename... Ints> + struct apply; + + template <int n, int m> + struct apply<Int<n>, Int<m>> { + using type = Int<n + m>; + }; +}; + +struct SumAll { + template <typename... Ints> + struct apply { + using type = Int<0>; + }; + + template <typename N1, typename... Ints> + struct apply<N1, Ints...> { + using type = Int<N1::value + apply<Ints...>::type::value>; + }; + + // Optimization, not required for correctness. + template <typename N0, typename N1, typename N2, typename N3, typename N4, typename... Ints> + struct apply<N0, N1, N2, N3, N4, Ints...> { + using type = Int<N0::value + N1::value + N2::value + N3::value + N4::value + apply<Ints...>::type::value>; + }; +}; + +struct Minus { + template <typename N, typename M> + struct apply; + + template <int n, int m> + struct apply<Int<n>, Int<m>> { + using type = Int<n - m>; + }; +}; + +struct GreaterThan { + template <typename N, typename M> + struct apply; + + template <int n, int m> + struct apply<Int<n>, Int<m>> { + using type = Bool<(n > m)>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_NUMERIC_OPERATIONS_H diff --git a/include/fruit/impl/meta/pair.h b/include/fruit/impl/meta/pair.h new file mode 100644 index 0000000..10d8a3e --- /dev/null +++ b/include/fruit/impl/meta/pair.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_PAIR_H +#define FRUIT_META_PAIR_H + +#include <fruit/impl/meta/basics.h> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename First1, typename Second1> +struct Pair { + using First = First1; + using Second = Second1; +}; + +struct MakePair { + template <typename First, typename Second> + struct apply { + using type = Pair<First, Second>; + }; +}; + +struct GetFirst { + template <typename P> + struct apply { + using type = typename P::First; + }; +}; + +struct GetSecond { + template <typename P> + struct apply { + using type = typename P::Second; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_PAIR_H diff --git a/include/fruit/impl/meta/proof_tree_comparison.h b/include/fruit/impl/meta/proof_tree_comparison.h new file mode 100644 index 0000000..a8d1f91 --- /dev/null +++ b/include/fruit/impl/meta/proof_tree_comparison.h @@ -0,0 +1,125 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PROOF_TREE_COMPARISON_H +#define FRUIT_PROOF_TREE_COMPARISON_H + +#if !defined(FRUIT_EXTRA_DEBUG) && !defined(FRUIT_IN_META_TEST) +#error "This file should only be included in debug mode or in tests." +#endif + +namespace fruit { +namespace impl { +namespace meta { + +// Checks whether Proof is entailed by Forest, i.e. whether there is a corresponding Proof1 in Forest with the same +// thesis +// and with the same hypotheses as Proof (or a subset). +struct IsProofEntailedByForest { + template <typename ProofTh, typename ProofHps, typename Forest> + struct apply { + using ForestHps = FindInMap(Forest, ProofTh); + using type = If(IsNone(ForestHps), Bool<false>, IsContained(ForestHps, ProofHps)); + }; +}; + +struct IsForestEntailedByForest { + template <typename EntailedForest, typename Forest> + struct apply { + struct Helper { + template <typename CurrentResult, typename EntailedProof> + struct apply; + + template <typename CurrentResult, typename EntailedProofTh, typename EntailedProofHps> + struct apply<CurrentResult, Pair<EntailedProofTh, EntailedProofHps>> { + using type = And(CurrentResult, IsProofEntailedByForest(EntailedProofTh, EntailedProofHps, Forest)); + }; + }; + + using type = FoldVector(EntailedForest, Helper, Bool<true>); + }; +}; + +// Given two proof trees, check if they are equal. +// Only for debugging. +struct IsProofTreeEqualTo { + template <typename Proof1, typename Proof2> + struct apply { + using type = And(IsSame(typename Proof1::First, typename Proof2::First), + IsSameSet(typename Proof1::Second, typename Proof2::Second)); + }; +}; + +// Given two proofs forests, check if they are equal. +// This is not very efficient, consider re-implementing if it will be used often. +// Only for debugging. +struct IsForestEqualTo { + template <typename Forest1, typename Forest2> + struct apply { + using type = And(IsForestEntailedByForest(Forest1, Forest2), IsForestEntailedByForest(Forest2, Forest1)); + }; +}; + +// Only for debugging, similar to checking IsProofEntailedByForest but gives a detailed error. +// Only for debugging. +struct CheckProofEntailedByForest { + template <typename ProofTh, typename ProofHps, typename Forest> + struct apply { + using ForestHps = FindInMap(Forest, ProofTh); + using type = If(IsNone(ForestHps), + ConstructError(ProofNotEntailedByForestBecauseThNotFoundErrorTag, ProofTh, GetMapKeys(Forest)), + If(IsContained(ForestHps, ProofHps), Bool<true>, + ConstructError(ProofNotEntailedByForestBecauseHpsNotASubsetErrorTag, ForestHps, ProofHps, + SetDifference(ForestHps, ProofHps)))); + }; +}; + +// Only for debugging, similar to checking IsProofEntailedByForest but gives a detailed error. +// Only for debugging. +struct CheckForestEntailedByForest { + template <typename EntailedForest, typename Forest> + struct apply { + struct Helper { + template <typename CurrentResult, typename EntailedProof> + struct apply; + + template <typename CurrentResult, typename EntailedProofTh, typename EntailedProofHps> + struct apply<CurrentResult, Pair<EntailedProofTh, EntailedProofHps>> { + using type = PropagateError(CurrentResult, + CheckProofEntailedByForest(EntailedProofTh, EntailedProofHps, Forest)); + }; + }; + + using type = FoldVector(EntailedForest, Helper, Bool<true>); + }; +}; + +// Given two proofs forests, check if they are equal. +// This is not very efficient, consider re-implementing if it will be used often. +// Only for debugging. +struct CheckForestEqualTo { + template <typename Forest1, typename Forest2> + struct apply { + using type = PropagateError(CheckForestEntailedByForest(Forest1, Forest2), + CheckForestEntailedByForest(Forest2, Forest1)); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_PROOF_TREE_COMPARISON_H diff --git a/include/fruit/impl/meta/proof_trees.h b/include/fruit/impl/meta/proof_trees.h new file mode 100644 index 0000000..ad6795f --- /dev/null +++ b/include/fruit/impl/meta/proof_trees.h @@ -0,0 +1,84 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_PROOF_TREES_H +#define FRUIT_META_PROOF_TREES_H + +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/injection_debug_errors.h> +#include <fruit/impl/meta/errors.h> +#include <fruit/impl/meta/graph.h> +#include <fruit/impl/meta/set.h> + +namespace fruit { +namespace impl { +namespace meta { + +// Given a set of formulas Hps=Set<Hp1, ... Hp(n)> and a formula Th, Pair<Th, Hps> represents the +// following proof tree: +// +// Hp1 ... Hp(n) +// ------------- +// Th +// +// Hp1, ... Hp(n) must be distinct. +// Formulas are atomic, any type can be used as formula (except None). +// +// A proof forest is a map (i.e. a Set of Pair(s)) from each Th to the corresponding set of Hps. +// Note that a proof forest doesn't need to have any additional property (for example, a proof tree +// might contain the thesis as hypotheses, or there might be a longer loop e.g A=>B, B=>A. +using EmptyProofForest = EmptySet; + +#ifndef FRUIT_NO_LOOP_CHECK + +using ProofForestFindHps = GraphFindNeighbors; + +// ProofForestFindLoop(F) returns a loop in the given forest as a Vector<Th1, ..., Thk> such that: +// IsInSet(Th1, ProofForestFindHps(Th2)), IsInSet(Th2, ProofForestFindHps(Th3)), ..., +// IsInSet(Th{k-1}, ProofForestFindHps(Thk)), IsInSet(Thk, ProofForestFindHps(Th1)) +// if there is no such loop, returns None. +using ProofForestFindLoop = GraphFindLoop; + +#else // FRUIT_NO_LOOP_CHECK + +struct ProofForestFindLoop { + template <typename F> + struct apply { + using type = None; + }; +}; + +struct AddProofTreeToForest { + template <typename Forest, typename Proof> + struct apply { + using type = Vector<>; + }; +}; + +struct AddProofTreeSetToForest { + template <typename Proofs, typename Forest> + struct apply { + using type = Vector<>; + }; +}; + +#endif // FRUIT_NO_LOOP_CHECK + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_PROOF_TREES_H diff --git a/include/fruit/impl/meta/set.h b/include/fruit/impl/meta/set.h new file mode 100644 index 0000000..7a39d43 --- /dev/null +++ b/include/fruit/impl/meta/set.h @@ -0,0 +1,181 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_SET_H +#define FRUIT_META_SET_H + +#include <fruit/impl/fruit_assert.h> +#include <fruit/impl/meta/immutable_set.h> +#include <fruit/impl/meta/pair.h> +#include <fruit/impl/meta/vector.h> + +namespace fruit { +namespace impl { +namespace meta { + +// Set ::= Vector<Ts...>, with no duplicates. + +using EmptySet = Vector<>; + +template <typename T> +using ToSet1 = Vector<T>; + +template <typename T, typename U> +using ToSet2 = Vector<T, U>; + +using IsInSet = IsInVector; + +// If S is a set with elements (T1, ..., Tn) this calculates +// F(InitialValue, F(T1, F(..., F(Tn) ...))). +// If S is EmptySet this returns InitialValue. +using FoldSet = FoldVector; + +// If S is a set with elements (T1, ..., Tn) this calculates +// Combine(F(T1), Combine(F(T2),..., F(Tn) ...)). +// +// `Combine' must be associative, and CombineIdentity must be an identity value wrt Combine. +// Use this instead of FoldSet when possible, it shares more sub-instances when invoked multiple +// times with similar sets. +struct FoldSetWithCombine { + template <typename S, typename F, typename Combine, typename CombineIdentity> + struct apply { + using type = FoldVector(TransformVector(S, F), Combine, CombineIdentity); + }; +}; + +// Converts a set (T1,...,Tn) to a set (F(T1), ..., F(Tn)). +// F(T1), ..., F(Tn) must be distinct. +using TransformSet = TransformVector; + +using SetSize = VectorSize; + +using AddToSetUnchecked = PushFront; + +struct AddToSet { + template <typename S, typename T> + struct apply { + using type = If(IsInSet(T, S), S, AddToSetUnchecked(S, T)); + }; +}; + +// Checks if S1 is contained in S2. +struct IsContained { + template <typename S1, typename S2> + struct apply { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = And(CurrentResult, IsInSet(T, S2)); + }; + }; + + using type = FoldVector(S1, Helper, Bool<true>); + }; +}; + +// Checks if S1 is disjoint from S2. +struct IsDisjoint { + template <typename S1, typename S2> + struct apply { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = Or(CurrentResult, IsInSet(T, S2)); + }; + }; + + using type = Not(FoldVector(S1, Helper, Bool<false>)); + }; +}; + +struct IsEmptySet { + template <typename S> + struct apply { + using type = Bool<false>; + }; +}; + +template <> +struct IsEmptySet::apply<Vector<>> { + using type = Bool<true>; +}; + +using SetToVector = Identity; + +// The vector must have no duplicates. +using VectorToSetUnchecked = Identity; + +struct SetDifference { + template <typename S1, typename S2> + struct apply { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = If(IsInSet(T, S2), CurrentResult, AddToSetUnchecked(CurrentResult, T)); + }; + }; + + using type = FoldSet(S1, Helper, EmptySet); + }; +}; + +struct SetIntersection { + template <typename S1, typename S2> + struct apply { + struct Helper { + template <typename CurrentResult, typename T> + struct apply { + using type = If(IsInSet(T, S2), AddToSetUnchecked(CurrentResult, T), CurrentResult); + }; + }; + + using type = If(GreaterThan(SetSize(S1), SetSize(S2)), SetIntersection(S2, S1), FoldSet(S1, Helper, EmptySet)); + }; +}; + +struct SetUnion { + template <typename S1, typename S2> + struct apply { + using type = If(GreaterThan(SetSize(S1), SetSize(S2)), SetUnion(S2, S1), + FoldSet(SetDifference(S1, S2), AddToSetUnchecked, S2)); + }; +}; + +using SetUncheckedUnion = ConcatVectors; + +struct IsSameSet { + template <typename S1, typename S2> + struct apply { + using type = And(IsContained(S1, S2), IsContained(S2, S1)); + }; +}; + +// Returns an arbitrary element from the given set (that must be non-empty). +struct GetArbitrarySetElement { + template <typename S> + struct apply; + + template <typename T, typename... Ts> + struct apply<Vector<T, Ts...>> { + using type = T; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_SET_H diff --git a/include/fruit/impl/meta/signatures.h b/include/fruit/impl/meta/signatures.h new file mode 100644 index 0000000..69e5ac7 --- /dev/null +++ b/include/fruit/impl/meta/signatures.h @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_SIGNATURES_H +#define FRUIT_META_SIGNATURES_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/vector.h> + +namespace fruit { +namespace impl { +namespace meta { + +// Similar to ConsSignature, but takes a Vector of args instead of individual args. +struct ConsSignatureWithVector { + template <typename ReturnType, typename ArgVector> + struct apply; + + template <typename ReturnType, typename... Args> + struct apply<Type<ReturnType>, Vector<Type<Args>...>> { + using type = Type<ReturnType(Args...)>; + }; +}; + +struct SignatureType { + template <typename Signature> + struct apply; + + template <typename T, typename... Types> + struct apply<Type<T(Types...)>> { + using type = Type<T>; + }; +}; + +struct SignatureArgs { + template <typename Signature> + struct apply; + + template <typename T, typename... Types> + struct apply<Type<T(Types...)>> { + using type = Vector<Type<Types>...>; + }; +}; + +struct IsSignature { + template <typename Signature> + struct apply { + using type = Bool<false>; + }; + + template <typename C, typename... Args> + struct apply<Type<C(Args...)>> { + using type = Bool<true>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_SIGNATURES_H diff --git a/include/fruit/impl/meta/triplet.h b/include/fruit/impl/meta/triplet.h new file mode 100644 index 0000000..832a8cf --- /dev/null +++ b/include/fruit/impl/meta/triplet.h @@ -0,0 +1,51 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_TRIPLET_H +#define FRUIT_META_TRIPLET_H + +#include <fruit/impl/meta/basics.h> + +namespace fruit { +namespace impl { +namespace meta { + +template <typename First1, typename Second1, typename Third1> +struct Triplet { + using First = First1; + using Second = Second1; + using Third = Third1; +}; + +struct ConsTriplet { + template <typename First, typename Second, typename Third> + struct apply { + using type = Triplet<First, Second, Third>; + }; +}; + +struct GetThird { + template <typename T> + struct apply { + using type = typename T::Third; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_TRIPLET_H diff --git a/include/fruit/impl/meta/vector.h b/include/fruit/impl/meta/vector.h new file mode 100644 index 0000000..a94a21d --- /dev/null +++ b/include/fruit/impl/meta/vector.h @@ -0,0 +1,274 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_VECTOR_H +#define FRUIT_META_VECTOR_H + +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/eval.h> +#include <fruit/impl/meta/fold.h> +#include <fruit/impl/meta/logical_operations.h> +#include <fruit/impl/meta/numeric_operations.h> +#include <functional> + +namespace fruit { +namespace impl { +namespace meta { + +// Used to pass around a Vector<Types...>, no meaning per se. +template <typename... Types> +struct Vector {}; + +// Using ConsVector(MetaExpr...) instead of Vector<MetaExpr...> in a meta-expression allows the +// types to be evaluated. Avoid using Vector<...> directly in a meta-expression, unless you're sure +// that the arguments have already been evaluated (e.g. if Args... are arguments of a metafunction, +// Vector<Args...> is ok but Vector<MyFunction(Args)...> is wrong. +struct ConsVector { + template <typename... Types> + struct apply { + using type = Vector<Types...>; + }; +}; + +struct GenerateIntSequenceEvenHelper { + template <typename Half> + struct apply; + + template <int... ns> + struct apply<Vector<Int<ns>...>> { + using type = Vector<Int<ns>..., Int<sizeof...(ns) + ns>...>; + }; +}; + +struct GenerateIntSequenceOddHelper { + template <typename Half> + struct apply; + + template <int... ns> + struct apply<Vector<Int<ns>...>> { + using type = Vector<Int<ns>..., Int<sizeof...(ns)>, Int<sizeof...(ns) + 1 + ns>...>; + }; +}; + +struct GenerateIntSequence { + template <typename N> + struct apply { + using type = If(Bool<(N::value % 2) == 0>, GenerateIntSequenceEvenHelper(GenerateIntSequence(Int<N::value / 2>)), + GenerateIntSequenceOddHelper(GenerateIntSequence(Int<N::value / 2>))); + }; +}; + +template <> +struct GenerateIntSequence::apply<Int<0>> { + using type = Vector<>; +}; + +template <> +struct GenerateIntSequence::apply<Int<1>> { + using type = Vector<Int<0>>; +}; + +struct IsInVector { + template <typename T> + struct AlwaysFalseBool { + constexpr static bool value = false; + }; + + template <bool... bs> + struct BoolVector; + + template <typename T, typename V> + struct apply; + + template <typename T, typename... Ts> + struct apply<T, Vector<Ts...>> { + using type = Bool< + !std::is_same<BoolVector<AlwaysFalseBool<Ts>::value...>, BoolVector<std::is_same<T, Ts>::value...>>::value>; + }; +}; + +struct IsVectorContained { + template <typename V1, typename V2> + struct apply; + + template <typename T> + struct AlwaysTrueBool { + constexpr static bool value = true; + }; + + template <bool... bs> + struct BoolVector; + + template <typename... Ts, typename V2> + struct apply<Vector<Ts...>, V2> { + using type = Bool<std::is_same<BoolVector<AlwaysTrueBool<Ts>::value...>, + BoolVector<Id<typename IsInVector::template apply<Ts, V2>::type>::value...>>::value>; + }; +}; + +struct VectorSize { + template <typename V> + struct apply; + + template <typename... Ts> + struct apply<Vector<Ts...>> { + using type = Int<sizeof...(Ts)>; + }; +}; + +struct PushFront { + template <typename V, typename T> + struct apply; + + template <typename T, typename... Ts> + struct apply<Vector<Ts...>, T> { + using type = Vector<T, Ts...>; + }; +}; + +struct PushBack { + template <typename V, typename T> + struct apply; + + template <typename T, typename... Ts> + struct apply<Vector<Ts...>, T> { + using type = Vector<Ts..., T>; + }; +}; + +struct ConcatVectors { + template <typename V1, typename V2> + struct apply; + + template <typename... Ts, typename... Us> + struct apply<Vector<Ts...>, Vector<Us...>> { + using type = Vector<Ts..., Us...>; + }; +}; + +struct TransformVector { + template <typename V, typename F> + struct apply; + + template <typename... Ts, typename F> + struct apply<Vector<Ts...>, F> { + using type = Vector<Eval<typename F::template apply<Ts>::type>...>; + }; +}; + +struct ReplaceInVectorHelper { + template <typename ToReplace, typename NewElem, typename T> + struct apply { + using type = T; + }; + + template <typename ToReplace, typename NewElem> + struct apply<ToReplace, NewElem, ToReplace> { + using type = NewElem; + }; +}; + +struct ReplaceInVector { + template <typename V, typename ToReplace, typename NewElem> + struct apply { + using type = TransformVector(V, PartialCall(ReplaceInVectorHelper, ToReplace, NewElem)); + }; +}; + +// If V is Vector<T1, ..., Tn> this calculates F(InitialValue, F(T1, F(..., F(Tn) ...))). +// If V is Vector<> this returns InitialValue. +struct FoldVector { + template <typename V, typename F, typename InitialValue> + struct apply; + + template <typename... Ts, typename F, typename InitialValue> + struct apply<Vector<Ts...>, F, InitialValue> { + using type = Fold(F, InitialValue, Ts...); + }; +}; + +template <typename Unused> +using AlwaysVoidPtr = void*; + +// Returns a copy of V but without the first N elements. +// N must be at least 0 and at most VectorSize(V). +struct VectorRemoveFirstN { + template <typename V, typename N, typename Indexes = Eval<GenerateIntSequence(N)>> + struct apply; + + template <typename... Types, typename N, typename... Indexes> + struct apply<Vector<Types...>, N, Vector<Indexes...>> { + template <typename... RemainingTypes> + static Vector<RemainingTypes...> f(AlwaysVoidPtr<Indexes>..., RemainingTypes*...); + + using type = decltype(f((Types*)nullptr...)); + }; +}; + +struct VectorEndsWith { + template <typename V, typename T> + struct apply { + using N = Int<Eval<VectorSize(V)>::value - 1>; + using type = IsSame(VectorRemoveFirstN(V, N), Vector<T>); + }; + + template <typename T> + struct apply<Vector<>, T> { + using type = Bool<false>; + }; +}; + +// Removes all None elements from the vector. +// O(n) instantiations. +struct VectorRemoveNone { + template <typename V> + struct apply { + using type = Vector<>; + }; + + template <typename T, typename... Ts> + struct apply<Vector<T, Ts...>> { + using type = PushFront(VectorRemoveNone(Vector<Ts...>), T); + }; + + template <typename... Ts> + struct apply<Vector<None, Ts...>> { + using type = VectorRemoveNone(Vector<Ts...>); + }; +}; + +struct ConstructErrorWithArgVectorHelper { + template <typename ErrorTag, typename ArgsVector, typename... OtherArgs> + struct apply; + + template <typename ErrorTag, typename... Args, typename... OtherArgs> + struct apply<ErrorTag, Vector<Args...>, OtherArgs...> { + using type = ConstructError(ErrorTag, OtherArgs..., Args...); + }; +}; + +struct ConstructErrorWithArgVector { + template <typename ErrorTag, typename ArgsVector, typename... OtherArgs> + struct apply { + using type = ConstructErrorWithArgVectorHelper(ErrorTag, VectorRemoveNone(ArgsVector), OtherArgs...); + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_VECTOR_H diff --git a/include/fruit/impl/meta/wrappers.h b/include/fruit/impl/meta/wrappers.h new file mode 100644 index 0000000..53dd05c --- /dev/null +++ b/include/fruit/impl/meta/wrappers.h @@ -0,0 +1,178 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_META_WRAPPERS_H +#define FRUIT_META_WRAPPERS_H + +#include <fruit/impl/fruit-config.h> + +#include <memory> + +namespace fruit { +namespace impl { +namespace meta { + +struct ConsSignature { + template <typename ReturnType, typename... Args> + struct apply; + + template <typename ReturnType, typename... Args> + struct apply<Type<ReturnType>, Type<Args>...> { + using type = Type<ReturnType(Args...)>; + }; +}; + +struct ConsStdFunction { + template <typename Signature> + struct apply; + + template <typename Signature> + struct apply<Type<Signature>> { + using type = Type<std::function<Signature>>; + }; +}; + +struct ConsUniquePtr { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<std::unique_ptr<T>>; + }; +}; + +struct RemoveUniquePtr { + template <typename T> + struct apply { + using type = T; + }; + + template <typename T> + struct apply<Type<std::unique_ptr<T>>> { + using type = Type<T>; + }; +}; + +struct RemovePointer { + template <typename T> + struct apply { + using type = T; + }; + + template <typename T> + struct apply<Type<T*>> { + using type = Type<T>; + }; +}; + +struct ConsReference { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<T&>; + }; +}; + +struct ConsConstReference { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Type<const T&>; + }; +}; + +struct IsEmpty { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<std::is_empty<T>::value>; + }; +}; + +struct IsTriviallyCopyable { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<FRUIT_IS_TRIVIALLY_COPYABLE(T)>; + }; +}; + +struct IsPointer { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<std::is_pointer<T>::value>; + }; +}; + +struct IsUniquePtr { + template <typename T> + struct apply { + using type = Bool<false>; + }; + + template <typename T> + struct apply<Type<std::unique_ptr<T>>> { + using type = Bool<true>; + }; +}; + +struct IsAbstract { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<std::is_abstract<T>::value>; + }; +}; + +struct IsBaseOf { + template <typename I, typename C> + struct apply; + + template <typename I, typename C> + struct apply<Type<I>, Type<C>> { + using type = Bool<std::is_base_of<I, C>::value>; + }; +}; + +struct HasVirtualDestructor { + template <typename T> + struct apply; + + template <typename T> + struct apply<Type<T>> { + using type = Bool<std::has_virtual_destructor<T>::value>; + }; +}; + +} // namespace meta +} // namespace impl +} // namespace fruit + +#endif // FRUIT_META_WRAPPERS_H diff --git a/include/fruit/impl/normalized_component.defn.h b/include/fruit/impl/normalized_component.defn.h new file mode 100644 index 0000000..3aad35f --- /dev/null +++ b/include/fruit/impl/normalized_component.defn.h @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_COMPONENT_INLINES_H +#define FRUIT_NORMALIZED_COMPONENT_INLINES_H + +#include <fruit/normalized_component.h> + +#include <fruit/component.h> +#include <fruit/impl/util/type_info.h> + +namespace fruit { + +template <typename... Params> +template <typename... FormalArgs, typename... Args> +inline NormalizedComponent<Params...>::NormalizedComponent(Component<Params...> (*getComponent)(FormalArgs...), + Args&&... args) + : NormalizedComponent(std::move(fruit::Component<Params...>( + fruit::createComponent().install(getComponent, std::forward<Args>(args)...)) + .storage), + fruit::impl::MemoryPool()) {} + +template <typename... Params> +inline NormalizedComponent<Params...>::NormalizedComponent(fruit::impl::ComponentStorage&& storage, + fruit::impl::MemoryPool memory_pool) + : storage(std::move(storage), + fruit::impl::getTypeIdsForList<typename fruit::impl::meta::Eval<fruit::impl::meta::SetToVector( + typename fruit::impl::meta::Eval<fruit::impl::meta::ConstructComponentImpl( + fruit::impl::meta::Type<Params>...)>::Ps)>>(memory_pool), + memory_pool, fruit::impl::NormalizedComponentStorageHolder::WithUndoableCompression()) {} + +} // namespace fruit + +#endif // FRUIT_NORMALIZED_COMPONENT_INLINES_H diff --git a/include/fruit/impl/normalized_component_storage/binding_normalization.h b/include/fruit/impl/normalized_component_storage/binding_normalization.h new file mode 100644 index 0000000..b5d395e --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/binding_normalization.h @@ -0,0 +1,371 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_BINDING_NORMALIZATION_H +#define FRUIT_BINDING_NORMALIZATION_H + +#ifndef IN_FRUIT_CPP_FILE +// We don't want to include it in public headers to save some compile time. +#error "binding_normalization.h included in non-cpp file." +#endif + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/fixed_size_allocator.h> +#include <fruit/impl/normalized_component_storage/normalized_component_storage.h> +#include <fruit/impl/util/hash_helpers.h> + +namespace fruit { +namespace impl { + +/** + * This struct contains helper functions used for binding normalization. + * They are wrapped in a struct so that Fruit classes can easily declare to be friend + * of all these. + */ +class BindingNormalization { +public: + // Stores an element of the form (c_type_id, -> undo_info) for each binding compression that was + // performed. + // These are used to undo binding compression after applying it (if necessary). + using BindingCompressionInfoMap = + HashMapWithArenaAllocator<TypeId, NormalizedComponentStorage::CompressedBindingUndoInfo>; + + using LazyComponentWithNoArgs = ComponentStorageEntry::LazyComponentWithNoArgs; + using LazyComponentWithArgs = ComponentStorageEntry::LazyComponentWithArgs; + + using LazyComponentWithNoArgsSet = NormalizedComponentStorage::LazyComponentWithNoArgsSet; + using LazyComponentWithArgsSet = NormalizedComponentStorage::LazyComponentWithArgsSet; + + using LazyComponentWithNoArgsReplacementMap = NormalizedComponentStorage::LazyComponentWithNoArgsReplacementMap; + using LazyComponentWithArgsReplacementMap = NormalizedComponentStorage::LazyComponentWithArgsReplacementMap; + + /** + * Normalizes the toplevel entries and performs binding compression. + * This does *not* keep track of what binding compressions were performed, so they can't be undone. When we might need + * to undo the binding compression, use normalizeBindingsWithUndoableBindingCompression() instead. + */ + static void normalizeBindingsWithPermanentBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings); + + /** + * Normalizes the toplevel entries and performs binding compression, but keeps track of which compressions were + * performed so that we can later undo some of them if needed. + * This is more expensive than normalizeBindingsWithPermanentBindingCompression(), use that when it suffices. + */ + static void normalizeBindingsWithUndoableBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, + MemoryPool& memory_pool_for_component_replacements_maps, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + BindingCompressionInfoMap& bindingCompressionInfoMap, + LazyComponentWithNoArgsSet& fully_expanded_components_with_no_args, + LazyComponentWithArgsSet& fully_expanded_components_with_args, + LazyComponentWithNoArgsReplacementMap& component_with_no_args_replacements, + LazyComponentWithArgsReplacementMap& component_with_args_replacements); + + static void normalizeBindingsAndAddTo( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, MemoryPool& memory_pool, + const NormalizedComponentStorage& base_normalized_component, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& new_bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings); + +private: + using multibindings_vector_elem_t = std::pair<ComponentStorageEntry, ComponentStorageEntry>; + using multibindings_vector_t = std::vector<multibindings_vector_elem_t, ArenaAllocator<multibindings_vector_elem_t>>; + + /** + * Adds the multibindings in multibindings_vector to the `multibindings' map. + * Each element of multibindings_vector is a pair, where the first element is the multibinding and the second is the + * corresponding MULTIBINDING_VECTOR_CREATOR entry. + */ + static void addMultibindings(std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + const multibindings_vector_t& multibindings_vector); + + static void printLazyComponentInstallationLoop( + const std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& entries_to_process, + const ComponentStorageEntry& last_entry); + + /** + * Normalizes the toplevel entries (but doesn't perform binding compression). + */ + template <typename... Functors> + static void normalizeBindings(FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + MemoryPool& memory_pool, MemoryPool& memory_pool_for_fully_expanded_components_maps, + MemoryPool& memory_pool_for_component_replacements_maps, + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>& binding_data_map, + Functors... functors); + + struct BindingCompressionInfo { + TypeId i_type_id; + ComponentStorageEntry::BindingForObjectToConstruct::create_t create_i_with_compression; + }; + + /** + * Normalizes the toplevel entries and performs binding compression. + * - SaveCompressedBindingUndoInfo should have an operator()(TypeId, CompressedBindingUndoInfo) that will be called + * with (c_type_id, undo_info) for each binding compression that was applied (and that therefore might need to be + * undone later). + */ + template <typename SaveCompressedBindingUndoInfo, typename SaveFullyExpandedComponentsWithNoArgs, + typename SaveFullyExpandedComponentsWithArgs, typename SaveComponentReplacementsWithNoArgs, + typename SaveComponentReplacementsWithArgs> + static void normalizeBindingsWithBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, + MemoryPool& memory_pool_for_component_replacements_maps, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + SaveCompressedBindingUndoInfo save_compressed_binding_undo_info, + SaveFullyExpandedComponentsWithNoArgs save_fully_expanded_components_with_no_args, + SaveFullyExpandedComponentsWithArgs save_fully_expanded_components_with_args, + SaveComponentReplacementsWithNoArgs save_component_replacements_with_no_args, + SaveComponentReplacementsWithArgs save_component_replacements_with_args); + + /** + * bindingCompressionInfoMap is an output parameter. This function will store information on all performed binding + * compressions in that map, to allow them to be undone later, if necessary. + * compressed_bindings_map is a map CtypeId -> (ItypeId, bindingData) + * - SaveCompressedBindingUndoInfo should have an operator()(TypeId, CompressedBindingUndoInfo) that will be called + * with (c_type_id, undo_info) for each binding compression that was applied (and that therefore might need to be + * undone later). + */ + template <typename SaveCompressedBindingUndoInfo> + static std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>> + performBindingCompression(HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>&& binding_data_map, + HashMapWithArenaAllocator<TypeId, BindingCompressionInfo>&& compressed_bindings_map, + MemoryPool& memory_pool, const multibindings_vector_t& multibindings_vector, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + SaveCompressedBindingUndoInfo save_compressed_binding_undo_info); + + static void handlePreexistingLazyComponentWithArgsReplacement(ComponentStorageEntry& replaced_component_entry, + const ComponentStorageEntry& preexisting_replacement, + ComponentStorageEntry& new_replacement); + + static void handlePreexistingLazyComponentWithNoArgsReplacement(ComponentStorageEntry& replaced_component_entry, + const ComponentStorageEntry& preexisting_replacement, + ComponentStorageEntry& new_replacement); + + template <typename HandleCompressedBinding, typename HandleMultibinding, typename FindNormalizedBinding, + typename IsValidItr, typename IsNormalizedBindingItrForConstructedObject, typename GetObjectPtr, + typename GetCreate, typename IsComponentWithNoArgsAlreadyExpandedInNormalizedComponent, + typename IsComponentWithArgsAlreadyExpandedInNormalizedComponent, + typename SaveFullyExpandedComponentsWithNoArgs, typename SaveFullyExpandedComponentsWithArgs, + typename GetComponentWithNoArgsReplacementInNormalizedComponent, + typename GetComponentWithArgsReplacementInNormalizedComponent, + typename IsLazyComponentWithNoArgsIteratorValid, typename IsLazyComponentWithArgsIteratorValid, + typename DereferenceLazyComponentWithNoArgsIterator, typename DereferenceLazyComponentWithArgsIterator, + typename SaveComponentReplacementsWithNoArgs, typename SaveComponentReplacementsWithArgs> + struct BindingNormalizationFunctors { + + /** + * This should have an operator()(ComponentStorageEntry&) that will be called for each COMPRESSED_BINDING entry. + */ + HandleCompressedBinding handle_compressed_binding; + + /** + * This should have an + * operator()(ComponentStorageEntry& multibinding_entry, ComponentStorageEntry& multibinding_vector_creator_entry) + * that will be called for each multibinding entry. + */ + HandleMultibinding handle_multibinding; + + /** + * This should have a + * NormalizedBindingItr operator()(TypeId) + * that returns a NormalizedBindingItr describing whether the binding is present in a base component (if any). + */ + FindNormalizedBinding find_normalized_binding; + + /** + * This should have a + * bool operator()(NormalizedBindingItr) + */ + IsValidItr is_valid_itr; + + /** + * This should have a + * bool operator()(NormalizedBindingItr) + * (that can only be used when IsValidItr returns true). + */ + IsNormalizedBindingItrForConstructedObject is_normalized_binding_itr_for_constructed_object; + + /** + * This should have a + * ComponentStorageEntry::BindingForConstructedObject::object_ptr_t operator()(NormalizedBindingItr) + * (that can only be used when IsNormalizedBindingItrForConstructedObject returns true). + */ + GetObjectPtr get_object_ptr; + + /** + * This should have a + * ComponentStorageEntry::BindingForObjectToConstruct::create_t operator()(NormalizedBindingItr) + * (that can only be used when IsNormalizedBindingItrForConstructedObject returns false). + */ + GetCreate get_create; + + IsComponentWithNoArgsAlreadyExpandedInNormalizedComponent + is_component_with_no_args_already_expanded_in_normalized_component; + IsComponentWithArgsAlreadyExpandedInNormalizedComponent + is_component_with_args_already_expanded_in_normalized_component; + SaveFullyExpandedComponentsWithNoArgs save_fully_expanded_components_with_no_args; + SaveFullyExpandedComponentsWithArgs save_fully_expanded_components_with_args; + + /** + * Gets a LazyComponentWithNoArgsIterator pointing to the replacement for the given lazy component in the normalized + * component (if any). + */ + GetComponentWithNoArgsReplacementInNormalizedComponent + get_component_with_no_args_replacement_in_normalized_component; + + /** + * Gets a LazyComponentWithArgsIterator pointing to the replacement for the given lazy component in the normalized + * component (if any). + */ + GetComponentWithArgsReplacementInNormalizedComponent get_component_with_args_replacement_in_normalized_component; + + IsLazyComponentWithNoArgsIteratorValid is_lazy_component_with_no_args_iterator_valid; + IsLazyComponentWithArgsIteratorValid is_lazy_component_with_args_iterator_valid; + + DereferenceLazyComponentWithNoArgsIterator dereference_lazy_component_with_no_args_iterator; + DereferenceLazyComponentWithArgsIterator dereference_lazy_component_with_args_iterator; + + SaveComponentReplacementsWithNoArgs save_component_replacements_with_no_args; + SaveComponentReplacementsWithArgs save_component_replacements_with_args; + }; + + /** + * This struct groups all data structures available during binding normalization, to avoid mentioning them in all + * handle*Binding functions below. + */ + template <typename... Functors> + struct BindingNormalizationContext { + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data; + MemoryPool& memory_pool; + MemoryPool& memory_pool_for_fully_expanded_components_maps; + MemoryPool& memory_pool_for_component_replacements_maps; + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>& binding_data_map; + BindingNormalizationFunctors<Functors...> functors; + + // These are in reversed order (note that toplevel_entries must also be in reverse order). + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>> entries_to_process; + + // These sets contain the lazy components whose expansion has already completed. + LazyComponentWithNoArgsSet fully_expanded_components_with_no_args = + NormalizedComponentStorage::createLazyComponentWithNoArgsSet(20 /* capacity */, + memory_pool_for_fully_expanded_components_maps); + LazyComponentWithArgsSet fully_expanded_components_with_args = + NormalizedComponentStorage::createLazyComponentWithArgsSet(20 /* capacity */, + memory_pool_for_fully_expanded_components_maps); + + // These sets contain the elements with kind *_END_MARKER in entries_to_process. + // For component with args, these sets do *not* own the objects, entries_to_process does. + LazyComponentWithNoArgsSet components_with_no_args_with_expansion_in_progress = + NormalizedComponentStorage::createLazyComponentWithNoArgsSet(20 /* capacity */, memory_pool); + LazyComponentWithArgsSet components_with_args_with_expansion_in_progress = + NormalizedComponentStorage::createLazyComponentWithArgsSet(20 /* capacity */, memory_pool); + + // These sets contain Component replacements, as mappings componentToReplace->replacementComponent. + LazyComponentWithNoArgsReplacementMap component_with_no_args_replacements = + NormalizedComponentStorage::createLazyComponentWithNoArgsReplacementMap( + 20 /* capacity */, memory_pool_for_component_replacements_maps); + LazyComponentWithArgsReplacementMap component_with_args_replacements = + NormalizedComponentStorage::createLazyComponentWithArgsReplacementMap( + 20 /* capacity */, memory_pool_for_component_replacements_maps); + + BindingNormalizationContext(FixedSizeVector<ComponentStorageEntry>& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + MemoryPool& memory_pool, MemoryPool& memory_pool_for_fully_expanded_components_maps, + MemoryPool& memory_pool_for_component_replacements_maps, + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>& binding_data_map, + BindingNormalizationFunctors<Functors...> functors); + + BindingNormalizationContext(const BindingNormalizationContext&) = delete; + BindingNormalizationContext(BindingNormalizationContext&&) = delete; + + BindingNormalizationContext& operator=(const BindingNormalizationContext&) = delete; + BindingNormalizationContext& operator=(BindingNormalizationContext&&) = delete; + + ~BindingNormalizationContext(); + }; + + template <typename... Params> + static void handleBindingForConstructedObject(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleBindingForObjectToConstructThatNeedsAllocation(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleBindingForObjectToConstructThatNeedsNoAllocation(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleCompressedBinding(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleMultibinding(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleMultibindingVectorCreator(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleComponentWithoutArgsEndMarker(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleComponentWithArgsEndMarker(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleReplacedLazyComponentWithArgs(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleReplacedLazyComponentWithNoArgs(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleLazyComponentWithArgs(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void handleLazyComponentWithNoArgs(BindingNormalizationContext<Params...>& context); + + template <typename... Params> + static void performComponentReplacement(BindingNormalizationContext<Params...>& context, + const ComponentStorageEntry& replacement); + + static void printMultipleBindingsError(TypeId type); + + static void printIncompatibleComponentReplacementsError(const ComponentStorageEntry& replaced_component_entry, + const ComponentStorageEntry& replacement_component_entry1, + const ComponentStorageEntry& replacement_component_entry2); + + static void + printComponentReplacementFailedBecauseTargetAlreadyExpanded(const ComponentStorageEntry& replaced_component_entry, + const ComponentStorageEntry& replacement_component_entry); +}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_BINDING_NORMALIZATION_H diff --git a/include/fruit/impl/normalized_component_storage/binding_normalization.templates.h b/include/fruit/impl/normalized_component_storage/binding_normalization.templates.h new file mode 100644 index 0000000..85888d7 --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/binding_normalization.templates.h @@ -0,0 +1,725 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_BINDING_NORMALIZATION_TEMPLATES_H +#define FRUIT_BINDING_NORMALIZATION_TEMPLATES_H + +#ifndef IN_FRUIT_CPP_FILE +// We don't want to include it in public headers to save some compile time. +#error "binding_normalization.templates.h included in non-cpp file." +#endif + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.h> +#include <fruit/impl/util/type_info.h> + +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +template <typename... Functors> +BindingNormalization::BindingNormalizationContext<Functors...>::BindingNormalizationContext( + FixedSizeVector<ComponentStorageEntry>& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, MemoryPool& memory_pool_for_component_replacements_maps, + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>& binding_data_map, + BindingNormalizationFunctors<Functors...> functors) + : fixed_size_allocator_data(fixed_size_allocator_data), memory_pool(memory_pool), + memory_pool_for_fully_expanded_components_maps(memory_pool_for_fully_expanded_components_maps), + memory_pool_for_component_replacements_maps(memory_pool_for_component_replacements_maps), + binding_data_map(binding_data_map), functors(functors), + entries_to_process(toplevel_entries.begin(), toplevel_entries.end(), + ArenaAllocator<ComponentStorageEntry>(memory_pool)) { + + toplevel_entries.clear(); +} + +template <typename... Functors> +BindingNormalization::BindingNormalizationContext<Functors...>::~BindingNormalizationContext() { + FruitAssert(components_with_no_args_with_expansion_in_progress.empty()); + FruitAssert(components_with_args_with_expansion_in_progress.empty()); + + for (const ComponentStorageEntry::LazyComponentWithArgs& x : fully_expanded_components_with_args) { + x.destroy(); + } + + for (const auto& pair : component_with_args_replacements) { + const LazyComponentWithArgs& replaced_component = pair.first; + const ComponentStorageEntry& replacement_component = pair.second; + replaced_component.destroy(); + replacement_component.destroy(); + } + + for (const auto& pair : component_with_no_args_replacements) { + const ComponentStorageEntry& replacement_component = pair.second; + replacement_component.destroy(); + } +} + +template <typename... Functors> +void BindingNormalization::normalizeBindings(FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, + MemoryPool& memory_pool_for_component_replacements_maps, + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>& binding_data_map, + Functors... functors) { + + FruitAssert(binding_data_map.empty()); + + using Context = BindingNormalizationContext<Functors...>; + + Context context(toplevel_entries, fixed_size_allocator_data, memory_pool, + memory_pool_for_fully_expanded_components_maps, memory_pool_for_component_replacements_maps, + binding_data_map, BindingNormalizationFunctors<Functors...>{functors...}); + + // When we expand a lazy component, instead of removing it from the stack we change its kind (in entries_to_process) + // to one of the *_END_MARKER kinds. This allows to keep track of the "call stack" for the expansion. + + while (!context.entries_to_process.empty()) { + switch (context.entries_to_process.back().kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT: + handleBindingForConstructedObject(context); + break; + + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION: + handleBindingForObjectToConstructThatNeedsAllocation(context); + break; + + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION: + handleBindingForObjectToConstructThatNeedsNoAllocation(context); + break; + + case ComponentStorageEntry::Kind::COMPRESSED_BINDING: + handleCompressedBinding(context); + break; + + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT: + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION: + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION: + handleMultibinding(context); + break; + + case ComponentStorageEntry::Kind::MULTIBINDING_VECTOR_CREATOR: + handleMultibindingVectorCreator(context); + break; + + case ComponentStorageEntry::Kind::COMPONENT_WITHOUT_ARGS_END_MARKER: + handleComponentWithoutArgsEndMarker(context); + break; + + case ComponentStorageEntry::Kind::COMPONENT_WITH_ARGS_END_MARKER: + handleComponentWithArgsEndMarker(context); + break; + + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS: + handleReplacedLazyComponentWithArgs(context); + break; + + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS: + handleReplacedLazyComponentWithNoArgs(context); + break; + + case ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS: + handleLazyComponentWithArgs(context); + break; + + case ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS: + handleLazyComponentWithNoArgs(context); + break; + + default: +#ifdef FRUIT_EXTRA_DEBUG + std::cerr << "Unexpected kind: " << (std::size_t)context.entries_to_process.back().kind << std::endl; +#endif + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + } + + context.functors.save_fully_expanded_components_with_no_args(context.fully_expanded_components_with_no_args); + context.functors.save_fully_expanded_components_with_args(context.fully_expanded_components_with_args); + context.functors.save_component_replacements_with_no_args(context.component_with_no_args_replacements); + context.functors.save_component_replacements_with_args(context.component_with_args_replacements); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleBindingForConstructedObject(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT); + + context.entries_to_process.pop_back(); + + auto itr = context.functors.find_normalized_binding(entry.type_id); + if (context.functors.is_valid_itr(itr)) { + if (!context.functors.is_normalized_binding_itr_for_constructed_object(itr) || + context.functors.get_object_ptr(itr) != entry.binding_for_constructed_object.object_ptr) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + // Otherwise ok, duplicate but consistent binding. + return; + } + + ComponentStorageEntry& entry_in_map = context.binding_data_map[entry.type_id]; + if (entry_in_map.type_id.type_info != nullptr) { + if (entry_in_map.kind != ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT || + entry.binding_for_constructed_object.object_ptr != entry_in_map.binding_for_constructed_object.object_ptr) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } +// Otherwise ok, duplicate but consistent binding. + +// This avoids assertion failures when injecting a non-const pointer and there is a const duplicate binding that +// appears before the non-const one (so we'd otherwise ignore the non-const one). +#ifdef FRUIT_EXTRA_DEBUG + entry_in_map.binding_for_constructed_object.is_nonconst |= entry.binding_for_constructed_object.is_nonconst; +#endif + return; + } + + // New binding, add it to the map. + entry_in_map = std::move(entry); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void BindingNormalization::handleBindingForObjectToConstructThatNeedsAllocation( + BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION); + context.entries_to_process.pop_back(); + + auto itr = context.functors.find_normalized_binding(entry.type_id); + if (context.functors.is_valid_itr(itr)) { + if (context.functors.is_normalized_binding_itr_for_constructed_object(itr) || + context.functors.get_create(itr) != entry.binding_for_object_to_construct.create) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + // Otherwise ok, duplicate but consistent binding. + return; + } + + ComponentStorageEntry& entry_in_map = context.binding_data_map[entry.type_id]; + context.fixed_size_allocator_data.addType(entry.type_id); + if (entry_in_map.type_id.type_info != nullptr) { + if (entry_in_map.kind != ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + entry.binding_for_object_to_construct.create != entry_in_map.binding_for_object_to_construct.create) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + // Otherwise ok, duplicate but consistent binding. + return; + } + + // New binding, add it to the map. + entry_in_map = std::move(entry); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void BindingNormalization::handleBindingForObjectToConstructThatNeedsNoAllocation( + BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + context.entries_to_process.pop_back(); + + auto itr = context.functors.find_normalized_binding(entry.type_id); + if (context.functors.is_valid_itr(itr)) { + if (context.functors.is_normalized_binding_itr_for_constructed_object(itr) || + context.functors.get_create(itr) != entry.binding_for_object_to_construct.create) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + // Otherwise ok, duplicate but consistent binding. + return; + } + + ComponentStorageEntry& entry_in_map = context.binding_data_map[entry.type_id]; + context.fixed_size_allocator_data.addExternallyAllocatedType(entry.type_id); + if (entry_in_map.type_id.type_info != nullptr) { + if (entry_in_map.kind != ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION || + entry.binding_for_object_to_construct.create != entry_in_map.binding_for_object_to_construct.create) { + printMultipleBindingsError(entry.type_id); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + // Otherwise ok, duplicate but consistent binding. + return; + } + + // New binding, add it to the map. + entry_in_map = std::move(entry); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleCompressedBinding(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::COMPRESSED_BINDING); + context.entries_to_process.pop_back(); + context.functors.handle_compressed_binding(entry); +} + +template <typename... Params> +void BindingNormalization::handleMultibinding(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT || + entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + context.entries_to_process.pop_back(); + FruitAssert(!context.entries_to_process.empty()); + ComponentStorageEntry vector_creator_entry = std::move(context.entries_to_process.back()); + context.entries_to_process.pop_back(); + FruitAssert(vector_creator_entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_VECTOR_CREATOR); + context.functors.handle_multibinding(entry, vector_creator_entry); +} + +template <typename... Params> +void BindingNormalization::handleMultibindingVectorCreator(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_VECTOR_CREATOR); + context.entries_to_process.pop_back(); + FruitAssert(!context.entries_to_process.empty()); + ComponentStorageEntry multibinding_entry = std::move(context.entries_to_process.back()); + context.entries_to_process.pop_back(); + FruitAssert(multibinding_entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT || + multibinding_entry.kind == + ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + multibinding_entry.kind == + ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + context.functors.handle_multibinding(multibinding_entry, entry); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleComponentWithoutArgsEndMarker(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::COMPONENT_WITHOUT_ARGS_END_MARKER); + context.entries_to_process.pop_back(); + // A lazy component expansion has completed; we now move the component from + // components_with_*_with_expansion_in_progress to fully_expanded_components_*. + + context.components_with_no_args_with_expansion_in_progress.erase(entry.lazy_component_with_no_args); + context.fully_expanded_components_with_no_args.insert(std::move(entry.lazy_component_with_no_args)); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleComponentWithArgsEndMarker(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::COMPONENT_WITH_ARGS_END_MARKER); + context.entries_to_process.pop_back(); + // A lazy component expansion has completed; we now move the component from + // components_with_*_with_expansion_in_progress to fully_expanded_components_*. + + context.components_with_args_with_expansion_in_progress.erase(entry.lazy_component_with_args); + context.fully_expanded_components_with_args.insert(std::move(entry.lazy_component_with_args)); +} + +template <typename... Params> +void BindingNormalization::handleReplacedLazyComponentWithArgs(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS); + ComponentStorageEntry replaced_component_entry = std::move(entry); + context.entries_to_process.pop_back(); + FruitAssert(!context.entries_to_process.empty()); + + ComponentStorageEntry replacement_component_entry = std::move(context.entries_to_process.back()); + context.entries_to_process.pop_back(); + FruitAssert(replacement_component_entry.kind == + ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS || + replacement_component_entry.kind == ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS); + + if (context.components_with_args_with_expansion_in_progress.count(entry.lazy_component_with_args) != 0 || + context.fully_expanded_components_with_args.count(entry.lazy_component_with_args) != 0 || + context.functors.is_component_with_args_already_expanded_in_normalized_component( + entry.lazy_component_with_args)) { + printComponentReplacementFailedBecauseTargetAlreadyExpanded(replaced_component_entry, replacement_component_entry); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + auto replacement_in_normalized_component_itr = + context.functors.get_component_with_args_replacement_in_normalized_component( + replaced_component_entry.lazy_component_with_args); + if (context.functors.is_lazy_component_with_args_iterator_valid(replacement_in_normalized_component_itr)) { + handlePreexistingLazyComponentWithArgsReplacement( + replaced_component_entry, + context.functors.dereference_lazy_component_with_args_iterator(replacement_in_normalized_component_itr), + replacement_component_entry); + return; + } + + ComponentStorageEntry& replacement_component_entry_in_map = + context.component_with_args_replacements[replaced_component_entry.lazy_component_with_args]; + if (replacement_component_entry_in_map.type_id.type_info != nullptr) { + handlePreexistingLazyComponentWithArgsReplacement(replaced_component_entry, replacement_component_entry_in_map, + replacement_component_entry); + return; + } + + // We just inserted replaced_component_entry.lazy_component_with_args in the map, so it's now owned by the + // map. + replacement_component_entry_in_map = replacement_component_entry; +} + +template <typename... Params> +void BindingNormalization::handleReplacedLazyComponentWithNoArgs(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS); + ComponentStorageEntry replaced_component_entry = std::move(entry); + context.entries_to_process.pop_back(); + FruitAssert(!context.entries_to_process.empty()); + + ComponentStorageEntry replacement_component_entry = std::move(context.entries_to_process.back()); + context.entries_to_process.pop_back(); + FruitAssert(replacement_component_entry.kind == + ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS || + replacement_component_entry.kind == ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS); + + if (context.components_with_no_args_with_expansion_in_progress.count(entry.lazy_component_with_no_args) != 0 || + context.fully_expanded_components_with_no_args.count(entry.lazy_component_with_no_args) != 0 || + context.functors.is_component_with_no_args_already_expanded_in_normalized_component( + entry.lazy_component_with_no_args)) { + printComponentReplacementFailedBecauseTargetAlreadyExpanded(replaced_component_entry, replacement_component_entry); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + auto replacement_in_normalized_component_itr = + context.functors.get_component_with_no_args_replacement_in_normalized_component( + replaced_component_entry.lazy_component_with_no_args); + if (context.functors.is_lazy_component_with_no_args_iterator_valid(replacement_in_normalized_component_itr)) { + handlePreexistingLazyComponentWithNoArgsReplacement( + replaced_component_entry, + context.functors.dereference_lazy_component_with_no_args_iterator(replacement_in_normalized_component_itr), + replacement_component_entry); + return; + } + + ComponentStorageEntry& replacement_component_entry_in_map = + context.component_with_no_args_replacements[replaced_component_entry.lazy_component_with_no_args]; + if (replacement_component_entry_in_map.type_id.type_info != nullptr) { + handlePreexistingLazyComponentWithNoArgsReplacement(replaced_component_entry, replacement_component_entry_in_map, + replacement_component_entry); + return; + } + + // We just inserted replaced_component_entry.lazy_component_with_args in the map, so it's now owned by the + // map. + replacement_component_entry_in_map = replacement_component_entry; +} + +template <typename... Params> +void BindingNormalization::performComponentReplacement(BindingNormalizationContext<Params...>& context, + const ComponentStorageEntry& replacement) { + + // Instead of removing the entry from context.entries_to_process, we just replace it with the new component entry. + + ComponentStorageEntry& entry = context.entries_to_process.back(); + + switch (replacement.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + entry.kind = ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS; + entry.type_id = replacement.type_id; + entry.lazy_component_with_args = replacement.lazy_component_with_args.copy(); + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + entry.kind = ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS; + entry.type_id = replacement.type_id; + entry.lazy_component_with_no_args = replacement.lazy_component_with_no_args; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleLazyComponentWithArgs(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS); + if (context.fully_expanded_components_with_args.count(entry.lazy_component_with_args) || + context.functors.is_component_with_args_already_expanded_in_normalized_component( + entry.lazy_component_with_args)) { + // This lazy component was already inserted, skip it. + entry.lazy_component_with_args.destroy(); + context.entries_to_process.pop_back(); + return; + } + + auto replacement_component_in_normalized_component_itr = + context.functors.get_component_with_args_replacement_in_normalized_component(entry.lazy_component_with_args); + if (context.functors.is_lazy_component_with_args_iterator_valid(replacement_component_in_normalized_component_itr)) { + entry.lazy_component_with_args.destroy(); + performComponentReplacement(context, + context.functors.dereference_lazy_component_with_args_iterator( + replacement_component_in_normalized_component_itr)); + return; + } + + auto replacement_component_itr = context.component_with_args_replacements.find(entry.lazy_component_with_args); + if (replacement_component_itr != context.component_with_args_replacements.end()) { + entry.lazy_component_with_args.destroy(); + performComponentReplacement(context, replacement_component_itr->second); + return; + } + + bool actually_inserted = + context.components_with_args_with_expansion_in_progress.insert(entry.lazy_component_with_args).second; + if (!actually_inserted) { + printLazyComponentInstallationLoop(context.entries_to_process, entry); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "Expanding lazy component: " << entry.lazy_component_with_args.component->getFunTypeId() << std::endl; +#endif + + // Instead of removing the component from component.lazy_components, we just change its kind to the + // corresponding *_END_MARKER kind. + // When we pop this marker, this component's expansion will be complete. + context.entries_to_process.back().kind = ComponentStorageEntry::Kind::COMPONENT_WITH_ARGS_END_MARKER; + + // Note that this can also add other lazy components, so the resulting bindings can have a non-intuitive + // (although deterministic) order. + context.entries_to_process.back().lazy_component_with_args.component->addBindings(context.entries_to_process); +} + +template <typename... Params> +FRUIT_ALWAYS_INLINE inline void +BindingNormalization::handleLazyComponentWithNoArgs(BindingNormalizationContext<Params...>& context) { + ComponentStorageEntry entry = context.entries_to_process.back(); + FruitAssert(entry.kind == ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS); + + if (context.fully_expanded_components_with_no_args.count(entry.lazy_component_with_no_args) || + context.functors.is_component_with_no_args_already_expanded_in_normalized_component( + entry.lazy_component_with_no_args)) { + // This lazy component was already inserted, skip it. + context.entries_to_process.pop_back(); + return; + } + + auto replacement_component_in_normalized_component_itr = + context.functors.get_component_with_no_args_replacement_in_normalized_component( + entry.lazy_component_with_no_args); + if (context.functors.is_lazy_component_with_no_args_iterator_valid( + replacement_component_in_normalized_component_itr)) { + performComponentReplacement(context, + context.functors.dereference_lazy_component_with_no_args_iterator( + replacement_component_in_normalized_component_itr)); + return; + } + + auto replacement_component_itr = context.component_with_no_args_replacements.find(entry.lazy_component_with_no_args); + if (replacement_component_itr != context.component_with_no_args_replacements.end()) { + performComponentReplacement(context, replacement_component_itr->second); + return; + } + + bool actually_inserted = + context.components_with_no_args_with_expansion_in_progress.insert(entry.lazy_component_with_no_args).second; + if (!actually_inserted) { + printLazyComponentInstallationLoop(context.entries_to_process, entry); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "Expanding lazy component: " << entry.type_id << std::endl; +#endif + + // Instead of removing the component from component.lazy_components, we just change its kind to the + // corresponding *_END_MARKER kind. + // When we pop this marker, this component's expansion will be complete. + context.entries_to_process.back().kind = ComponentStorageEntry::Kind::COMPONENT_WITHOUT_ARGS_END_MARKER; + + // Note that this can also add other lazy components, so the resulting bindings can have a non-intuitive + // (although deterministic) order. + context.entries_to_process.back().lazy_component_with_no_args.addBindings(context.entries_to_process); +} + +template <typename SaveCompressedBindingUndoInfo> +std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>> +BindingNormalization::performBindingCompression( + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry>&& binding_data_map, + HashMapWithArenaAllocator<TypeId, BindingCompressionInfo>&& compressed_bindings_map, MemoryPool& memory_pool, + const multibindings_vector_t& multibindings_vector, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + SaveCompressedBindingUndoInfo save_compressed_binding_undo_info) { + using result_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + result_t result = result_t(ArenaAllocator<ComponentStorageEntry>(memory_pool)); + + // We can't compress the binding if C is a dep of a multibinding. + for (const std::pair<ComponentStorageEntry, ComponentStorageEntry>& multibinding_entry_pair : multibindings_vector) { + const ComponentStorageEntry& entry = multibinding_entry_pair.first; + FruitAssert(entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT || + entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + entry.kind == + ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + if (entry.kind != ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT) { + const BindingDeps* deps = entry.multibinding_for_object_to_construct.deps; + FruitAssert(deps != nullptr); + for (std::size_t i = 0; i < deps->num_deps; ++i) { + compressed_bindings_map.erase(deps->deps[i]); +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: ignoring compressed binding for " << deps->deps[i] + << " because it's a dep of a multibinding." << std::endl; +#endif + } + } + } + + // We can't compress the binding if C is an exposed type (but I is likely to be exposed instead). + for (TypeId type : exposed_types) { + compressed_bindings_map.erase(type); +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: ignoring compressed binding for " << type << " because it's an exposed type." + << std::endl; +#endif + } + + // We can't compress the binding if some type X depends on C and X!=I. + for (auto& binding_data_map_entry : binding_data_map) { + TypeId x_id = binding_data_map_entry.first; + ComponentStorageEntry entry = binding_data_map_entry.second; + FruitAssert(entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT || + entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + entry.kind == ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + + if (entry.kind != ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT) { + for (std::size_t i = 0; i < entry.binding_for_object_to_construct.deps->num_deps; ++i) { + TypeId c_id = entry.binding_for_object_to_construct.deps->deps[i]; + auto itr = compressed_bindings_map.find(c_id); + if (itr != compressed_bindings_map.end() && itr->second.i_type_id != x_id) { + compressed_bindings_map.erase(itr); +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: ignoring compressed binding for " << c_id << " because the type " << x_id + << " depends on it." << std::endl; +#endif + } + } + } + } + + // Two pairs of compressible bindings (I->C) and (C->X) can not exist (the C of a compressible binding is always bound + // either + // using constructor binding or provider binding, it can't be a binding itself). So no need to check for that. + + // Now perform the binding compression. + for (auto& entry : compressed_bindings_map) { + TypeId c_id = entry.first; + TypeId i_id = entry.second.i_type_id; + auto i_binding_data = binding_data_map.find(i_id); + auto c_binding_data = binding_data_map.find(c_id); + FruitAssert(i_binding_data != binding_data_map.end()); + FruitAssert(c_binding_data != binding_data_map.end()); + NormalizedComponentStorage::CompressedBindingUndoInfo undo_info; + undo_info.i_type_id = i_id; + FruitAssert(i_binding_data->second.kind == + ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION); + undo_info.i_binding = i_binding_data->second.binding_for_object_to_construct; + FruitAssert(c_binding_data->second.kind == + ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION || + c_binding_data->second.kind == + ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION); + undo_info.c_binding = c_binding_data->second.binding_for_object_to_construct; + save_compressed_binding_undo_info(c_id, undo_info); + + // Note that even if I is the one that remains, C is the one that will be allocated, not I. + + i_binding_data->second.kind = c_binding_data->second.kind; + i_binding_data->second.binding_for_object_to_construct.create = entry.second.create_i_with_compression; + i_binding_data->second.binding_for_object_to_construct.deps = + c_binding_data->second.binding_for_object_to_construct.deps; +#ifdef FRUIT_EXTRA_DEBUG + i_binding_data->second.binding_for_object_to_construct.is_nonconst |= + c_binding_data->second.binding_for_object_to_construct.is_nonconst; +#endif + + binding_data_map.erase(c_binding_data); +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: performing binding compression for the edge " << i_id << "->" << c_id << std::endl; +#endif + } + + // Copy the normalized bindings into the result vector. + result.reserve(binding_data_map.size()); + for (auto& p : binding_data_map) { + result.push_back(p.second); + } + + return result; +} + +template <typename SaveCompressedBindingUndoInfo, typename SaveFullyExpandedComponentsWithNoArgs, + typename SaveFullyExpandedComponentsWithArgs, typename SaveComponentReplacementsWithNoArgs, + typename SaveComponentReplacementsWithArgs> +void BindingNormalization::normalizeBindingsWithBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, MemoryPool& memory_pool_for_component_replacements_maps, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + SaveCompressedBindingUndoInfo save_compressed_binding_undo_info, + SaveFullyExpandedComponentsWithNoArgs save_fully_expanded_components_with_no_args, + SaveFullyExpandedComponentsWithArgs save_fully_expanded_components_with_args, + SaveComponentReplacementsWithNoArgs save_component_replacements_with_no_args, + SaveComponentReplacementsWithArgs save_component_replacements_with_args) { + + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry> binding_data_map = + createHashMapWithArenaAllocator<TypeId, ComponentStorageEntry>(20 /* capacity */, memory_pool); + // CtypeId -> (ItypeId, bindingData) + HashMapWithArenaAllocator<TypeId, BindingNormalization::BindingCompressionInfo> compressed_bindings_map = + createHashMapWithArenaAllocator<TypeId, BindingCompressionInfo>(20 /* capacity */, memory_pool); + + multibindings_vector_t multibindings_vector = + multibindings_vector_t(ArenaAllocator<multibindings_vector_elem_t>(memory_pool)); + + struct DummyIterator {}; + + normalizeBindings( + std::move(toplevel_entries), fixed_size_allocator_data, memory_pool, + memory_pool_for_fully_expanded_components_maps, memory_pool_for_component_replacements_maps, binding_data_map, + [&compressed_bindings_map](ComponentStorageEntry entry) { + BindingCompressionInfo& compression_info = compressed_bindings_map[entry.compressed_binding.c_type_id]; + compression_info.i_type_id = entry.type_id; + compression_info.create_i_with_compression = entry.compressed_binding.create; + }, + [&multibindings_vector](ComponentStorageEntry multibinding, ComponentStorageEntry multibinding_vector_creator) { + multibindings_vector.emplace_back(multibinding, multibinding_vector_creator); + }, + [](TypeId) { return DummyIterator(); }, [](DummyIterator) { return false; }, [](DummyIterator) { return false; }, + [](DummyIterator) { return nullptr; }, [](DummyIterator) { return nullptr; }, + [](const LazyComponentWithNoArgs&) { return false; }, [](const LazyComponentWithArgs&) { return false; }, + save_fully_expanded_components_with_no_args, save_fully_expanded_components_with_args, + [](const LazyComponentWithNoArgs&) { return (ComponentStorageEntry*)nullptr; }, + [](const LazyComponentWithArgs&) { return (ComponentStorageEntry*)nullptr; }, + [](ComponentStorageEntry*) { return false; }, [](ComponentStorageEntry*) { return false; }, + [](ComponentStorageEntry* p) { return *p; }, [](ComponentStorageEntry* p) { return *p; }, + save_component_replacements_with_no_args, save_component_replacements_with_args); + + bindings_vector = BindingNormalization::performBindingCompression( + std::move(binding_data_map), std::move(compressed_bindings_map), memory_pool, multibindings_vector, exposed_types, + save_compressed_binding_undo_info); + + addMultibindings(multibindings, fixed_size_allocator_data, multibindings_vector); +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_BINDING_NORMALIZATION_TEMPLATES_H diff --git a/include/fruit/impl/normalized_component_storage/normalized_bindings.defn.h b/include/fruit/impl/normalized_component_storage/normalized_bindings.defn.h new file mode 100644 index 0000000..07022c5 --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/normalized_bindings.defn.h @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_BINDINGS_DEFN_H +#define FRUIT_NORMALIZED_BINDINGS_DEFN_H + +#include <fruit/impl/normalized_component_storage/normalized_bindings.h> + +namespace fruit { +namespace impl { + +inline NormalizedBinding::NormalizedBinding(ComponentStorageEntry entry) { + switch (entry.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT: + object = entry.binding_for_constructed_object.object_ptr; +#ifdef FRUIT_EXTRA_DEBUG + is_nonconst = entry.binding_for_constructed_object.is_nonconst; +#endif + break; + + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION: + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION: + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION: + create = entry.binding_for_object_to_construct.create; +#ifdef FRUIT_EXTRA_DEBUG + is_nonconst = entry.binding_for_object_to_construct.is_nonconst; +#endif + break; + + default: +#ifdef FRUIT_EXTRA_DEBUG + std::cerr << "Unexpected kind: " << (std::size_t)entry.kind << std::endl; +#endif + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_NORMALIZED_BINDINGS_DEFN_H diff --git a/include/fruit/impl/normalized_component_storage/normalized_bindings.h b/include/fruit/impl/normalized_component_storage/normalized_bindings.h new file mode 100644 index 0000000..cf7876a --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/normalized_bindings.h @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_BINDINGS_H +#define FRUIT_NORMALIZED_BINDINGS_H + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <memory> + +namespace fruit { +namespace impl { + +/** A single normalized binding (not a multibinding). */ +struct NormalizedBinding { + union { + // Valid iff this is a terminal node (in the SemistaticGraph that contains this NormalizedBinding object). + ComponentStorageEntry::BindingForConstructedObject::object_ptr_t object; + + // Valid iff this is not a terminal node (in the SemistaticGraph that contains this NormalizedBinding object). + ComponentStorageEntry::BindingForObjectToConstruct::create_t create; + }; + +#ifdef FRUIT_EXTRA_DEBUG + bool is_nonconst; +#endif + + NormalizedBinding() = default; + + // Converts a ComponentStorageEntry to a NormalizedBinding. + // This is only supported for entries with these kinds: + // * BINDING_FOR_CONSTRUCTED_OBJECT, + // * BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION, + // * BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION, + explicit NormalizedBinding(ComponentStorageEntry entry); +}; + +/** A single normalized multibinding. */ +struct NormalizedMultibinding { + + bool is_constructed; + + union { + // Valid iff is_constructed==true. + ComponentStorageEntry::MultibindingForConstructedObject::object_ptr_t object; + + // Valid iff is_constructed==false. + ComponentStorageEntry::MultibindingForObjectToConstruct::create_t create; + }; +}; + +/** This stores all multibindings for a given type_id. */ +struct NormalizedMultibindingSet { + + // Can be empty, but only if v is present and non-empty. + std::vector<NormalizedMultibinding> elems; + + // TODO: Check this comment. + // Returns the std::vector<T*> of instances, or nullptr if none. + // Caches the result in the `v' member. + ComponentStorageEntry::MultibindingVectorCreator::get_multibindings_vector_t get_multibindings_vector; + + // A (casted) pointer to the std::vector<T*> of objects, or nullptr if the vector hasn't been constructed yet. + // Can't be empty. + std::shared_ptr<char> v; +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/normalized_component_storage/normalized_bindings.defn.h> + +#endif // FRUIT_NORMALIZED_BINDINGS_H diff --git a/include/fruit/impl/normalized_component_storage/normalized_component_storage.defn.h b/include/fruit/impl/normalized_component_storage/normalized_component_storage.defn.h new file mode 100644 index 0000000..a0442cf --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/normalized_component_storage.defn.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_COMPONENT_STORAGE_DEFN_H +#define FRUIT_NORMALIZED_COMPONENT_STORAGE_DEFN_H + +#include <fruit/impl/normalized_component_storage/normalized_component_storage.h> + +namespace fruit { +namespace impl { + +inline NormalizedComponentStorage::LazyComponentWithNoArgsSet +NormalizedComponentStorage::createLazyComponentWithNoArgsSet(size_t capacity, MemoryPool& memory_pool) { + return createHashSetWithArenaAllocatorAndCustomFunctors<LazyComponentWithNoArgs>( + capacity, memory_pool, NormalizedComponentStorage::HashLazyComponentWithNoArgs(), + std::equal_to<LazyComponentWithNoArgs>()); +} + +inline NormalizedComponentStorage::LazyComponentWithArgsSet +NormalizedComponentStorage::createLazyComponentWithArgsSet(size_t capacity, MemoryPool& memory_pool) { + return createHashSetWithArenaAllocatorAndCustomFunctors<LazyComponentWithArgs>( + capacity, memory_pool, NormalizedComponentStorage::HashLazyComponentWithArgs(), + NormalizedComponentStorage::LazyComponentWithArgsEqualTo()); +} + +inline NormalizedComponentStorage::LazyComponentWithNoArgsReplacementMap +NormalizedComponentStorage::createLazyComponentWithNoArgsReplacementMap(size_t capacity, MemoryPool& memory_pool) { + return createHashMapWithArenaAllocatorAndCustomFunctors<LazyComponentWithNoArgs, ComponentStorageEntry>( + capacity, memory_pool, NormalizedComponentStorage::HashLazyComponentWithNoArgs(), + std::equal_to<LazyComponentWithNoArgs>()); +} + +inline NormalizedComponentStorage::LazyComponentWithArgsReplacementMap +NormalizedComponentStorage::createLazyComponentWithArgsReplacementMap(size_t capacity, MemoryPool& memory_pool) { + return createHashMapWithArenaAllocatorAndCustomFunctors<LazyComponentWithArgs, ComponentStorageEntry>( + capacity, memory_pool, NormalizedComponentStorage::HashLazyComponentWithArgs(), + NormalizedComponentStorage::LazyComponentWithArgsEqualTo()); +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_NORMALIZED_COMPONENT_STORAGE_DEFN_H diff --git a/include/fruit/impl/normalized_component_storage/normalized_component_storage.h b/include/fruit/impl/normalized_component_storage/normalized_component_storage.h new file mode 100644 index 0000000..d580b59 --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/normalized_component_storage.h @@ -0,0 +1,168 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_COMPONENT_STORAGE_H +#define FRUIT_NORMALIZED_COMPONENT_STORAGE_H + +#ifndef IN_FRUIT_CPP_FILE +// We don't want to include it in public headers to save some compile time. +#error "normalized_component_storage.h included in non-cpp file." +#endif + +#include <fruit/impl/component_storage/component_storage_entry.h> +#include <fruit/impl/data_structures/fixed_size_allocator.h> +#include <fruit/impl/data_structures/semistatic_graph.h> +#include <fruit/impl/data_structures/semistatic_map.h> +#include <fruit/impl/fruit_internal_forward_decls.h> +#include <fruit/impl/normalized_component_storage/normalized_bindings.h> +#include <fruit/impl/util/hash_helpers.h> +#include <fruit/impl/util/type_info.h> + +#include <memory> +#include <unordered_map> + +namespace fruit { +namespace impl { + +/** + * Similar to ComponentStorage, but used a normalized representation to minimize the amount + * of work needed to turn this into an injector. However, adding bindings to a normalized + * component is slower than adding them to a simple component. + */ +class NormalizedComponentStorage { +public: + struct CompressedBindingUndoInfo { + TypeId i_type_id; + ComponentStorageEntry::BindingForObjectToConstruct i_binding; + ComponentStorageEntry::BindingForObjectToConstruct c_binding; + }; + + // A map from c_type_id to the corresponding CompressedBindingUndoInfo (if binding compression was performed for + // c_type_id). + using BindingCompressionInfoMap = HashMapWithArenaAllocator<TypeId, CompressedBindingUndoInfo>; + using BindingCompressionInfoMapAllocator = BindingCompressionInfoMap::allocator_type; + + using LazyComponentWithNoArgs = ComponentStorageEntry::LazyComponentWithNoArgs; + using LazyComponentWithArgs = ComponentStorageEntry::LazyComponentWithArgs; + + struct HashLazyComponentWithNoArgs { + std::size_t operator()(const LazyComponentWithNoArgs& x) const { + return x.hashCode(); + } + }; + + struct LazyComponentWithArgsEqualTo { + bool operator()(const LazyComponentWithArgs& x, const LazyComponentWithArgs& y) const { + return *x.component == *y.component; + } + }; + + struct HashLazyComponentWithArgs { + std::size_t operator()(const LazyComponentWithArgs& x) const { + return x.component->hashCode(); + } + }; + + using LazyComponentWithNoArgsSet = HashSetWithArenaAllocator<LazyComponentWithNoArgs, HashLazyComponentWithNoArgs, + std::equal_to<LazyComponentWithNoArgs>>; + using LazyComponentWithArgsSet = + HashSetWithArenaAllocator<LazyComponentWithArgs, HashLazyComponentWithArgs, LazyComponentWithArgsEqualTo>; + + using LazyComponentWithNoArgsReplacementMap = + HashMapWithArenaAllocator<LazyComponentWithNoArgs, ComponentStorageEntry, + NormalizedComponentStorage::HashLazyComponentWithNoArgs, + std::equal_to<LazyComponentWithNoArgs>>; + using LazyComponentWithArgsReplacementMap = + HashMapWithArenaAllocator<LazyComponentWithArgs, ComponentStorageEntry, + NormalizedComponentStorage::HashLazyComponentWithArgs, + NormalizedComponentStorage::LazyComponentWithArgsEqualTo>; + + static LazyComponentWithNoArgsSet createLazyComponentWithNoArgsSet(size_t capacity, MemoryPool& memory_pool); + static LazyComponentWithArgsSet createLazyComponentWithArgsSet(size_t capacity, MemoryPool& memory_pool); + + static LazyComponentWithNoArgsReplacementMap createLazyComponentWithNoArgsReplacementMap(size_t capacity, + MemoryPool& memory_pool); + static LazyComponentWithArgsReplacementMap createLazyComponentWithArgsReplacementMap(size_t capacity, + MemoryPool& memory_pool); + +private: + // A graph with types as nodes (each node stores the BindingData for the type) and dependencies as edges. + // For types that have a constructed object already, the corresponding node is stored as terminal node. + SemistaticGraph<TypeId, NormalizedBinding> bindings; + + // Maps the type index of a type T to the corresponding NormalizedMultibindingSet. + std::unordered_map<TypeId, NormalizedMultibindingSet> multibindings; + + // Contains data on the set of types that can be allocated using this component. + FixedSizeAllocator::FixedSizeAllocatorData fixed_size_allocator_data; + + // The MemoryPool used to allocate bindingCompressionInfoMap, fully_expanded_components_with_no_args and + // fully_expanded_components_with_args. + MemoryPool normalized_component_memory_pool; + + // Stores information on binding compression that was performed in bindings of this object. + // See also the documentation for BindingCompressionInfoMap. + BindingCompressionInfoMap binding_compression_info_map; + + LazyComponentWithNoArgsSet fully_expanded_components_with_no_args; + LazyComponentWithArgsSet fully_expanded_components_with_args; + + LazyComponentWithNoArgsReplacementMap component_with_no_args_replacements; + LazyComponentWithArgsReplacementMap component_with_args_replacements; + + friend class InjectorStorage; + friend class BindingNormalization; + +public: + using Graph = SemistaticGraph<TypeId, NormalizedBinding>; + + NormalizedComponentStorage() = delete; + + NormalizedComponentStorage(NormalizedComponentStorage&&) = delete; + NormalizedComponentStorage(const NormalizedComponentStorage&) = delete; + + NormalizedComponentStorage& operator=(NormalizedComponentStorage&&) = delete; + NormalizedComponentStorage& operator=(const NormalizedComponentStorage&) = delete; + + // These are just used as tags to select the desired constructor. + struct WithUndoableCompression {}; + struct WithPermanentCompression {}; + + /** + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + NormalizedComponentStorage(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, MemoryPool& memory_pool, + WithUndoableCompression); + + /** + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + NormalizedComponentStorage(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, MemoryPool& memory_pool, + WithPermanentCompression); + + // We don't use the default destructor because that will require the inclusion of + // the Boost's hashmap header. We define this in the cpp file instead. + ~NormalizedComponentStorage(); +}; + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/normalized_component_storage/normalized_component_storage.defn.h> + +#endif // FRUIT_NORMALIZED_COMPONENT_STORAGE_H diff --git a/include/fruit/impl/normalized_component_storage/normalized_component_storage_holder.h b/include/fruit/impl/normalized_component_storage/normalized_component_storage_holder.h new file mode 100644 index 0000000..6b1b7e9 --- /dev/null +++ b/include/fruit/impl/normalized_component_storage/normalized_component_storage_holder.h @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_COMPONENT_STORAGE_HOLDER_H +#define FRUIT_NORMALIZED_COMPONENT_STORAGE_HOLDER_H + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/memory_pool.h> +#include <fruit/impl/fruit_internal_forward_decls.h> +#include <memory> + +namespace fruit { +namespace impl { + +/** + * A wrapper around NormalizedComponentStorage, holding the NormalizedComponentStorage + * through a unique_ptr so that we don't need to include NormalizedComponentStorage in + * fruit.h. + */ +class NormalizedComponentStorageHolder { +private: + std::unique_ptr<NormalizedComponentStorage> storage; + + friend class InjectorStorage; + + template <typename... P> + friend class fruit::Injector; + +public: + // These are just used as tags to select the desired constructor. + struct WithUndoableCompression {}; + struct WithPermanentCompression {}; + + NormalizedComponentStorageHolder() = default; + + /** + * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool. + */ + NormalizedComponentStorageHolder(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool, WithUndoableCompression); + + NormalizedComponentStorageHolder(NormalizedComponentStorage&&) = delete; + NormalizedComponentStorageHolder(const NormalizedComponentStorage&) = delete; + + NormalizedComponentStorageHolder& operator=(NormalizedComponentStorageHolder&&) = delete; + NormalizedComponentStorageHolder& operator=(const NormalizedComponentStorageHolder&) = default; + + // We don't use the default destructor because that would require the inclusion of + // normalized_component_storage.h. We define this in the cpp file instead. + ~NormalizedComponentStorageHolder(); +}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_NORMALIZED_COMPONENT_STORAGE_HOLDER_H diff --git a/include/fruit/impl/provider.defn.h b/include/fruit/impl/provider.defn.h new file mode 100644 index 0000000..6f65bbf --- /dev/null +++ b/include/fruit/impl/provider.defn.h @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PROVIDER_DEFN_H +#define FRUIT_PROVIDER_DEFN_H + +#include <fruit/impl/injector/injector_storage.h> + +// Redundant, but makes KDevelop happy. +#include <fruit/provider.h> + +namespace fruit { + +template <typename C> +inline Provider<C>::Provider(fruit::impl::InjectorStorage* storage, + fruit::impl::InjectorStorage::Graph::node_iterator itr) + : storage(storage), itr(itr) {} + +template <typename C> +inline C* Provider<C>::get() { + return get<C*>(); +} + +namespace impl { +namespace meta { + +template <typename C> +struct ProviderImplHelper { + + template <typename T> + using CheckGet = Eval<PropagateError( + CheckInjectableType(RemoveAnnotations(Type<T>)), + If(Not(IsSame(GetClassForType(Type<T>), RemoveConstFromType(Type<C>))), + ConstructError(Id<TypeNotProvidedErrorTag>, Type<T>), + If(And(TypeInjectionRequiresNonConstBinding(Type<T>), Not(IsSame(Id<GetClassForType(Type<T>)>, Type<C>))), + ConstructError(TypeProvidedAsConstOnlyErrorTag, Type<T>), None)))>; +}; + +} // namespace meta +} // namespace impl + +template <typename C> +template <typename T> +inline T Provider<C>::get() { + using E = typename fruit::impl::meta::ProviderImplHelper<C>::template CheckGet<T>; + (void)typename fruit::impl::meta::CheckIfError<E>::type(); + return storage->template get<T>(itr); +} + +template <typename C> +template <typename T> +inline Provider<C>::operator T() { + return get<T>(); +} + +} // namespace fruit + +#endif // FRUIT_PROVIDER_DEFN_H diff --git a/include/fruit/impl/util/call_with_tuple.h b/include/fruit/impl/util/call_with_tuple.h new file mode 100644 index 0000000..763e6a6 --- /dev/null +++ b/include/fruit/impl/util/call_with_tuple.h @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_CALL_WITH_TUPLE_H +#define FRUIT_CALL_WITH_TUPLE_H + +#include <fruit/impl/meta/vector.h> + +namespace fruit { +namespace impl { + +template <typename IntVector, typename Result, typename ArgsTuple> +struct CallWithTupleHelper; + +template <typename... Ints, typename Result, typename... Args> +struct CallWithTupleHelper<fruit::impl::meta::Vector<Ints...>, Result, std::tuple<Args...>> { + Result operator()(Result (*fun)(Args...), std::tuple<Args...> args) { + // This parameter *is* used, but when the tuple is empty some compilers report is as unused. + (void)args; + return fun(std::get<fruit::impl::meta::getIntValue<Ints>()>(args)...); + } +}; + +template <typename Result, typename... Args> +inline Result callWithTuple(Result (*fun)(Args...), std::tuple<Args...> args) { + using IntVector = + fruit::impl::meta::Eval<fruit::impl::meta::GenerateIntSequence(fruit::impl::meta::Int<sizeof...(Args)>)>; + return CallWithTupleHelper<IntVector, Result, std::tuple<Args...>>()(fun, args); +} +} +} + +#endif // FRUIT_CALL_WITH_TUPLE_H diff --git a/include/fruit/impl/util/demangle_type_name.h b/include/fruit/impl/util/demangle_type_name.h new file mode 100644 index 0000000..59687db --- /dev/null +++ b/include/fruit/impl/util/demangle_type_name.h @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_DEMANGLE_TYPE_NAME_H +#define FRUIT_DEMANGLE_TYPE_NAME_H + +#include <string> + +std::string demangleTypeName(const char* name); + +#endif // FRUIT_DEMANGLE_TYPE_NAME_H diff --git a/include/fruit/impl/util/hash_codes.defn.h b/include/fruit/impl/util/hash_codes.defn.h new file mode 100644 index 0000000..a860e24 --- /dev/null +++ b/include/fruit/impl/util/hash_codes.defn.h @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_HASH_CODES_DEFN_H +#define FRUIT_HASH_CODES_DEFN_H + +#include <fruit/impl/util/hash_codes.h> + +namespace fruit { +namespace impl { + +template <typename Tuple, int index> +inline std::size_t hashTupleElement(const Tuple& x) { + const auto& value = std::get<index>(x); + using T = typename std::remove_const<typename std::remove_reference<decltype(value)>::type>::type; + return std::hash<T>()(value); +} + +template <typename Tuple, int last_index> +struct HashTupleHelper; + +template <int last_index> +struct HashTupleHelper<std::tuple<>, last_index> { + std::size_t operator()(const std::tuple<>&) { + return 0; + } +}; + +template <typename... Args> +struct HashTupleHelper<std::tuple<Args...>, 1> { + std::size_t operator()(const std::tuple<Args...>& x) { + return hashTupleElement<std::tuple<Args...>, 0>(x); + } +}; + +template <typename... Args, int last_index> +struct HashTupleHelper<std::tuple<Args...>, last_index> { + std::size_t operator()(const std::tuple<Args...>& x) { + return combineHashes(hashTupleElement<std::tuple<Args...>, last_index - 1>(x), + HashTupleHelper<std::tuple<Args...>, last_index - 1>()(x)); + } +}; + +template <typename... Args> +inline std::size_t hashTuple(const std::tuple<Args...>& x) { + return HashTupleHelper<std::tuple<Args...>, sizeof...(Args)>()(x); +} + +inline std::size_t combineHashes(std::size_t h1, std::size_t h2) { + h1 ^= h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2); + return h1; +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_HASH_CODES_DEFN_H diff --git a/include/fruit/impl/util/hash_codes.h b/include/fruit/impl/util/hash_codes.h new file mode 100644 index 0000000..b9213db --- /dev/null +++ b/include/fruit/impl/util/hash_codes.h @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_HASH_CODES_H +#define FRUIT_HASH_CODES_H + +#include <tuple> + +namespace fruit { +namespace impl { + +template <typename... Args> +std::size_t hashTuple(const std::tuple<Args...>& x); + +std::size_t combineHashes(std::size_t h1, std::size_t h2); + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/util/hash_codes.defn.h> + +#endif // FRUIT_HASH_CODES_H diff --git a/include/fruit/impl/util/hash_helpers.defn.h b/include/fruit/impl/util/hash_helpers.defn.h new file mode 100644 index 0000000..e75ec4a --- /dev/null +++ b/include/fruit/impl/util/hash_helpers.defn.h @@ -0,0 +1,77 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_HASH_HELPERS_DEFN_H +#define FRUIT_HASH_HELPERS_DEFN_H + +#include <fruit/impl/meta/vector.h> +#include <fruit/impl/util/hash_helpers.h> + +namespace fruit { +namespace impl { + +template <typename T> +inline HashSet<T> createHashSet() { + return createHashSet<T>(10); +} + +template <typename T> +inline HashSet<T> createHashSet(size_t capacity) { + return HashSet<T>(capacity, std::hash<T>()); +} + +template <typename T> +inline HashSetWithArenaAllocator<T> createHashSetWithArenaAllocator(size_t capacity, MemoryPool& memory_pool) { + return HashSetWithArenaAllocator<T>(capacity, std::hash<T>(), std::equal_to<T>(), ArenaAllocator<T>(memory_pool)); +} + +template <typename T, typename Hasher, typename EqualityComparator> +inline HashSetWithArenaAllocator<T, Hasher, EqualityComparator> +createHashSetWithArenaAllocatorAndCustomFunctors(size_t capacity, MemoryPool& memory_pool, Hasher hasher, + EqualityComparator equality_comparator) { + return HashSetWithArenaAllocator<T, Hasher, EqualityComparator>(capacity, hasher, equality_comparator, + ArenaAllocator<T>(memory_pool)); +} + +template <typename Key, typename Value> +inline HashMap<Key, Value> createHashMap() { + return createHashMap<Key, Value>(10); +} + +template <typename Key, typename Value> +inline HashMap<Key, Value> createHashMap(size_t capacity) { + return HashMap<Key, Value>(capacity, std::hash<Key>()); +} + +template <typename Key, typename Value> +inline HashMapWithArenaAllocator<Key, Value> createHashMapWithArenaAllocator(std::size_t capacity, + MemoryPool& memory_pool) { + return createHashMapWithArenaAllocatorAndCustomFunctors<Key, Value>(capacity, memory_pool, std::hash<Key>(), + std::equal_to<Key>()); +} + +template <typename Key, typename Value, typename Hasher, typename EqualityComparator> +inline HashMapWithArenaAllocator<Key, Value, Hasher, EqualityComparator> +createHashMapWithArenaAllocatorAndCustomFunctors(size_t capacity, MemoryPool& memory_pool, Hasher hasher, + EqualityComparator equality_comparator) { + return HashMapWithArenaAllocator<Key, Value, Hasher, EqualityComparator>( + capacity, hasher, equality_comparator, ArenaAllocator<std::pair<const Key, Value>>(memory_pool)); +} + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_HASH_HELPERS_DEFN_H diff --git a/include/fruit/impl/util/hash_helpers.h b/include/fruit/impl/util/hash_helpers.h new file mode 100644 index 0000000..a060aac --- /dev/null +++ b/include/fruit/impl/util/hash_helpers.h @@ -0,0 +1,102 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_HASH_HELPERS_H +#define FRUIT_HASH_HELPERS_H + +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/fruit-config.h> + +#ifndef IN_FRUIT_CPP_FILE +// We don't want to include it in public headers to save some compile time. +#error "hash_helpers included in non-cpp file." +#endif + +#if FRUIT_USES_BOOST +#include <boost/unordered_map.hpp> +#include <boost/unordered_set.hpp> +#else +#include <unordered_map> +#include <unordered_set> +#endif + +namespace fruit { +namespace impl { + +#if FRUIT_USES_BOOST +template <typename T, typename Hasher = std::hash<T>, typename EqualityComparator = std::equal_to<T>> +using HashSet = boost::unordered_set<T, Hasher, EqualityComparator>; + +template <typename T, typename Hasher = std::hash<T>, typename EqualityComparator = std::equal_to<T>> +using HashSetWithArenaAllocator = boost::unordered_set<T, Hasher, EqualityComparator, ArenaAllocator<T>>; + +template <typename Key, typename Value, typename Hasher = std::hash<Key>> +using HashMap = boost::unordered_map<Key, Value, Hasher>; + +template <typename Key, typename Value, typename Hasher = std::hash<Key>, + typename EqualityComparator = std::equal_to<Key>> +using HashMapWithArenaAllocator = + boost::unordered_map<Key, Value, Hasher, EqualityComparator, ArenaAllocator<std::pair<const Key, Value>>>; + +#else +template <typename T, typename Hasher = std::hash<T>, typename EqualityComparator = std::equal_to<T>> +using HashSet = std::unordered_set<T, Hasher, EqualityComparator>; + +template <typename T, typename Hasher = std::hash<T>, typename EqualityComparator = std::equal_to<T>> +using HashSetWithArenaAllocator = std::unordered_set<T, Hasher, EqualityComparator, ArenaAllocator<T>>; + +template <typename Key, typename Value, typename Hasher = std::hash<Key>> +using HashMap = std::unordered_map<Key, Value, Hasher>; + +template <typename Key, typename Value, typename Hasher = std::hash<Key>, + typename EqualityComparator = std::equal_to<Key>> +using HashMapWithArenaAllocator = + std::unordered_map<Key, Value, Hasher, EqualityComparator, ArenaAllocator<std::pair<const Key, Value>>>; + +#endif + +template <typename T> +HashSet<T> createHashSet(); + +template <typename T> +HashSet<T> createHashSet(size_t capacity); + +template <typename T> +HashSetWithArenaAllocator<T> createHashSetWithArenaAllocator(size_t capacity, MemoryPool& memory_pool); + +template <typename T, typename Hasher, typename EqualityComparator> +HashSetWithArenaAllocator<T, Hasher, EqualityComparator> +createHashSetWithArenaAllocatorAndCustomFunctors(size_t capacity, MemoryPool& memory_pool, Hasher, EqualityComparator); + +template <typename Key, typename Value> +HashMap<Key, Value> createHashMap(); + +template <typename Key, typename Value> +HashMap<Key, Value> createHashMap(size_t capacity); + +template <typename Key, typename Value> +HashMapWithArenaAllocator<Key, Value> createHashMapWithArenaAllocator(size_t capacity, MemoryPool& memory_pool); + +template <typename Key, typename Value, typename Hasher, typename EqualityComparator> +HashMapWithArenaAllocator<Key, Value, Hasher, EqualityComparator> +createHashMapWithArenaAllocatorAndCustomFunctors(size_t capacity, MemoryPool& memory_pool, Hasher, EqualityComparator); + +} // namespace impl +} // namespace fruit + +#include <fruit/impl/util/hash_helpers.defn.h> + +#endif // FRUIT_HASH_HELPERS_H diff --git a/include/fruit/impl/util/lambda_invoker.h b/include/fruit/impl/util/lambda_invoker.h new file mode 100644 index 0000000..2c8d63e --- /dev/null +++ b/include/fruit/impl/util/lambda_invoker.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_LAMBDA_INVOKER_H +#define FRUIT_LAMBDA_INVOKER_H + +#include <fruit/impl/fruit-config.h> +#include <fruit/impl/injection_errors.h> +#include <fruit/impl/meta/errors.h> +#include <fruit/impl/meta/metaprogramming.h> +#include <fruit/impl/meta/signatures.h> +#include <fruit/impl/meta/wrappers.h> + +#include <cstddef> +#include <functional> +#include <type_traits> + +namespace fruit { +namespace impl { + +class LambdaInvoker { +public: + template <typename F, typename... Args> + FRUIT_ALWAYS_INLINE static auto invoke(Args&&... args) + -> decltype(std::declval<const F&>()(std::declval<Args>()...)) { + // We reinterpret-cast a char[] to avoid de-referencing nullptr, which would technically be + // undefined behavior (even though we would not access any data there anyway). + // Sharing this buffer for different types F would also be undefined behavior since we'd break + // strict aliasing between those types. + alignas(alignof(F)) static char buf[1]; + + FruitStaticAssert(fruit::impl::meta::IsEmpty(fruit::impl::meta::Type<F>)); + FruitStaticAssert(fruit::impl::meta::IsTriviallyCopyable(fruit::impl::meta::Type<F>)); + // Since `F' is empty, a valid value of type F is already stored at the beginning of buf. + F* f = reinterpret_cast<F*>(buf); + return (*f)(std::forward<Args>(args)...); + } +}; + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_LAMBDA_INVOKER_H diff --git a/include/fruit/impl/util/type_info.defn.h b/include/fruit/impl/util/type_info.defn.h new file mode 100644 index 0000000..8b8df69 --- /dev/null +++ b/include/fruit/impl/util/type_info.defn.h @@ -0,0 +1,178 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_TYPE_INFO_DEFN_H +#define FRUIT_TYPE_INFO_DEFN_H + +#include <fruit/impl/util/type_info.h> + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/data_structures/arena_allocator.h> +#include <fruit/impl/data_structures/memory_pool.h> +#include <fruit/impl/fruit-config.h> +#include <fruit/impl/fruit_assert.h> + +namespace fruit { +namespace impl { + +template <typename T, bool is_abstract = std::is_abstract<T>::value> +struct GetConcreteTypeInfo { + constexpr TypeInfo::ConcreteTypeInfo operator()() const { + return TypeInfo::ConcreteTypeInfo{ + sizeof(T), alignof(T), std::is_trivially_destructible<T>::value, +#ifdef FRUIT_EXTRA_DEBUG + false /* is_abstract */, +#endif + }; + } +}; + +// For abstract types we don't need the real information. +// Also, some compilers might report compile errors in this case, for example alignof(T) doesn't work in Visual Studio +// when T is an abstract type. +template <typename T> +struct GetConcreteTypeInfo<T, true> { + constexpr TypeInfo::ConcreteTypeInfo operator()() const { + return TypeInfo::ConcreteTypeInfo{ + 0 /* type_size */, 0 /* type_alignment */, false /* is_trivially_destructible */, +#ifdef FRUIT_EXTRA_DEBUG + true /* is_abstract */, +#endif + }; + } +}; + +// This should only be used if RTTI is disabled. Use the other constructor if possible. +inline constexpr TypeInfo::TypeInfo(ConcreteTypeInfo concrete_type_info) + : info(nullptr), concrete_type_info(concrete_type_info) {} + +inline constexpr TypeInfo::TypeInfo(const std::type_info& info, ConcreteTypeInfo concrete_type_info) + : info(&info), concrete_type_info(concrete_type_info) {} + +inline std::string TypeInfo::name() const { + if (info != nullptr) // LCOV_EXCL_BR_LINE + return demangleTypeName(info->name()); + else + return "<unknown> (type name not accessible because RTTI is disabled)"; // LCOV_EXCL_LINE +} + +inline size_t TypeInfo::size() const { +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(!concrete_type_info.is_abstract); +#endif + return concrete_type_info.type_size; +} + +inline size_t TypeInfo::alignment() const { +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(!concrete_type_info.is_abstract); +#endif + return concrete_type_info.type_alignment; +} + +inline bool TypeInfo::isTriviallyDestructible() const { +#ifdef FRUIT_EXTRA_DEBUG + FruitAssert(!concrete_type_info.is_abstract); +#endif + return concrete_type_info.is_trivially_destructible; +} + +inline TypeId::operator std::string() const { + return type_info->name(); +} + +inline bool TypeId::operator==(TypeId x) const { + return type_info == x.type_info; +} + +inline bool TypeId::operator!=(TypeId x) const { + return type_info != x.type_info; +} + +inline bool TypeId::operator<(TypeId x) const { + return type_info < x.type_info; +} + +template <typename T> +struct GetTypeInfoForType { + constexpr TypeInfo operator()() const { +#ifdef FRUIT_HAS_TYPEID + return TypeInfo(typeid(T), GetConcreteTypeInfo<T>()()); +#else + return TypeInfo(GetConcreteTypeInfo<T>()()); +#endif + }; +}; + +template <typename Annotation, typename T> +struct GetTypeInfoForType<fruit::Annotated<Annotation, T>> { + constexpr TypeInfo operator()() const { +#ifdef FRUIT_HAS_TYPEID + return TypeInfo(typeid(fruit::Annotated<Annotation, T>), GetConcreteTypeInfo<T>()()); +#else + return TypeInfo(GetConcreteTypeInfo<T>()()); +#endif + }; +}; + +template <typename T> +inline TypeId getTypeId() { +#if defined(FRUIT_HAS_TYPEID) && !defined(FRUIT_HAS_CONSTEXPR_TYPEID) + // We can't use constexpr here because TypeInfo contains a `const std::type_info&` and that's not constexpr with the + // current compiler/STL. + static TypeInfo info = GetTypeInfoForType<T>()(); +#else + // Usual case. The `constexpr' ensures compile-time evaluation. + static constexpr TypeInfo info = GetTypeInfoForType<T>()(); +#endif + return TypeId{&info}; +} + +template <typename L> +struct GetTypeIdsForListHelper; + +template <typename... Ts> +struct GetTypeIdsForListHelper<fruit::impl::meta::Vector<Ts...>> { + std::vector<TypeId, ArenaAllocator<TypeId>> operator()(MemoryPool& memory_pool) { + return std::vector<TypeId, ArenaAllocator<TypeId>>(std::initializer_list<TypeId>{getTypeId<Ts>()...}, memory_pool); + } +}; + +template <typename L> +std::vector<TypeId, ArenaAllocator<TypeId>> getTypeIdsForList(MemoryPool& memory_pool) { + return GetTypeIdsForListHelper<L>()(memory_pool); +} + +#ifdef FRUIT_EXTRA_DEBUG + +inline std::ostream& operator<<(std::ostream& os, TypeId type) { + return os << std::string(type); +} + +#endif // FRUIT_EXTRA_DEBUG + +} // namespace impl +} // namespace fruit + +namespace std { + +inline std::size_t hash<fruit::impl::TypeId>::operator()(fruit::impl::TypeId type) const { + return hash<const fruit::impl::TypeInfo*>()(type.type_info); +} + +} // namespace std + +#endif // FRUIT_TYPE_INFO_DEFN_H diff --git a/include/fruit/impl/util/type_info.h b/include/fruit/impl/util/type_info.h new file mode 100644 index 0000000..7ffc8b3 --- /dev/null +++ b/include/fruit/impl/util/type_info.h @@ -0,0 +1,113 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_TYPE_INFO_H +#define FRUIT_TYPE_INFO_H + +#include <fruit/impl/meta/vector.h> +#include <fruit/impl/util/demangle_type_name.h> +#include <typeinfo> + +#include <vector> + +namespace fruit { +namespace impl { + +// Similar to std::type_index, but with a constexpr constructor and also storing the type size and alignment. +// Also guaranteed to be aligned, to allow storing a TypeInfo and 1 bit together in the size of a void*. +struct alignas(1) alignas(void*) TypeInfo { + + struct ConcreteTypeInfo { + // These fields are allowed to have dummy values for abstract types. + std::size_t type_size; + std::size_t type_alignment; + bool is_trivially_destructible; + +#ifdef FRUIT_EXTRA_DEBUG + bool is_abstract; +#endif + }; + + // This should only be used if RTTI is disabled. Use the other constructor if possible. + constexpr TypeInfo(ConcreteTypeInfo concrete_type_info); + + constexpr TypeInfo(const std::type_info& info, ConcreteTypeInfo concrete_type_info); + + std::string name() const; + + size_t size() const; + + size_t alignment() const; + + bool isTriviallyDestructible() const; + +private: + // The std::type_info struct associated with the type, or nullptr if RTTI is disabled. + // This is only used for the type name. + const std::type_info* info; + ConcreteTypeInfo concrete_type_info; +}; + +struct TypeId { + const TypeInfo* type_info; + + operator std::string() const; + + bool operator==(TypeId x) const; + bool operator!=(TypeId x) const; + bool operator<(TypeId x) const; +}; + +// Returns the TypeId for the type T. +// Multiple invocations for the same type return the same value. +// This has special support for types of the form Annotated<SomeAnnotation, SomeType>, it reports +// data for SomeType (except the name, that is "Annotated<SomeAnnotation, SomeType>"). +template <typename T> +TypeId getTypeId(); + +// A convenience function that returns an std::vector of TypeId values for the given meta-vector of types. +template <typename V> +std::vector<TypeId> getTypeIdsForList(); + +} // namespace impl +} // namespace fruit + +#ifdef FRUIT_EXTRA_DEBUG + +#include <ostream> + +namespace fruit { +namespace impl { + +inline std::ostream& operator<<(std::ostream& os, TypeId type); + +} // namespace impl +} // namespace fruit + +#endif // FRUIT_EXTRA_DEBUG + +namespace std { + +template <> +struct hash<fruit::impl::TypeId> { + std::size_t operator()(fruit::impl::TypeId type) const; +}; + +} // namespace std + +#include <fruit/impl/util/type_info.defn.h> + +#endif // FRUIT_TYPE_INFO_H diff --git a/include/fruit/injector.h b/include/fruit/injector.h new file mode 100644 index 0000000..8f8e5ae --- /dev/null +++ b/include/fruit/injector.h @@ -0,0 +1,260 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_INJECTOR_H +#define FRUIT_INJECTOR_H + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/component.h> +#include <fruit/normalized_component.h> +#include <fruit/provider.h> + +namespace fruit { + +/** + * An injector is a class constructed from a component that performs the needed injections and manages the lifetime of + * the created objects. + * An injector does *not* need to specify all types bound in the component; you can only specify the "root" type(s) and + * the injector will also create and store the instances of classes that are needed (directly or indirectly) to inject + * the root types. + * + * Example usage: + * + * Component<Foo, Bar> getFooBarComponent() { + * ... + * } + * + * Injector<Foo, Bar> injector(getFooBarComponent); + * Foo* foo = injector.get<Foo*>(); + * Bar* bar(injector); // Equivalent to: Bar* bar = injector.get<Bar*>(); + */ +template <typename... P> +class Injector { +private: + template <typename T> + struct RemoveAnnotationsHelper { + using type = fruit::impl::meta::UnwrapType< + fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<T>)>>; + }; + + template <typename T> + using RemoveAnnotations = typename RemoveAnnotationsHelper<T>::type; + +public: + // Moving injectors is allowed. + Injector(Injector&&) = default; + + // Copying injectors is forbidden. + Injector(const Injector&) = delete; + + /** + * This creates an injector from a component function (that can optionally have parameters). + * + * Args and FormalArgs (if any) must be the same types; or to be precise, each type in Args must be convertible into + * the corresponding type in FormalArgs. + * + * Example usage: + * + * Component<Foo, Bar> getFooBarComponent() { + * ... + * } + * + * Injector<Foo, Bar> injector(getFooBarComponent); + * Foo* foo = injector.get<Foo*>(); + * Bar* bar(injector); // Equivalent to: Bar* bar = injector.get<Bar*>(); + * + * Example usage with arguments: + * + * Component<Foo, Bar> getFooBarComponent(int n, double d) { + * ... + * } + * + * Injector<Foo, Bar> injector(getFooBarComponent, 10, 3.14); + * Foo* foo = injector.get<Foo*>(); + */ + template <typename... FormalArgs, typename... Args> + Injector(Component<P...> (*)(FormalArgs...), Args&&... args); + + /** + * This creates an injector from a normalized component and a component function. + * See the documentation of NormalizedComponent for more details. + * + * Args and FormalArgs (if any) must be the same types; or to be precise, each type in Args must be convertible into + * the corresponding type in FormalArgs. + * + * The NormalizedComponent can have requirements, but the Component can't. + * The NormalizedComponent must remain valid during the lifetime of any Injector object constructed with it. + * + * Example usage: + * + * // In the global scope. + * Component<Request> getRequestComponent(Request* request) { + * return fruit::createComponent() + * .bindInstance(*request); + * } + * + * // At startup (e.g. inside main()). + * NormalizedComponent<Required<Request>, Bar, Bar2> normalizedComponent = ...; + * + * ... + * for (...) { + * // For each request. + * Request request = ...; + * + * Injector<Foo, Bar> injector(normalizedComponent, getRequestComponent, &request); + * Foo* foo = injector.get<Foo*>(); + * ... + * } + */ + template <typename... NormalizedComponentParams, typename... ComponentParams, typename... FormalArgs, + typename... Args> + Injector(const NormalizedComponent<NormalizedComponentParams...>& normalized_component, + Component<ComponentParams...> (*)(FormalArgs...), Args&&... args); + + /** + * Deleted constructor, to ensure that constructing an Injector from a temporary NormalizedComponent doesn't compile. + */ + template <typename... NormalizedComponentParams, typename... ComponentParams, typename... FormalArgs, + typename... Args> + Injector(NormalizedComponent<NormalizedComponentParams...>&& normalized_component, + Component<ComponentParams...> (*)(FormalArgs...), Args&&... args) = delete; + + /** + * Returns an instance of the specified type. For any class C in the Injector's template parameters, the following + * variations are allowed: + * + * get<C>() + * get<C*>() + * get<C&>() + * get<const C*>() + * get<const C&>() + * get<shared_ptr<C>>() + * get<Provider<C>>() + * get<Provider<const C>>() + * get<Annotated<Annotation, C>>() (for any type `Annotation') + * get<Annotated<Annotation, C*>>() (for any type `Annotation') + * get<Annotated<Annotation, C&>>() (for any type `Annotation') + * get<Annotated<Annotation, const C*>>() (for any type `Annotation') + * get<Annotated<Annotation, const C&>>() (for any type `Annotation') + * get<Annotated<Annotation, shared_ptr<C>>>() (for any type `Annotation') + * get<Annotated<Annotation, Provider<C>>>() (for any type `Annotation') + * get<Annotated<Annotation, Provider<const C>>>() (for any type `Annotation') + * + * For any "const C" in the Injector's template parameters, only a subset of those are allowed, specifically: + * + * get<C>() + * get<const C*>() + * get<const C&>() + * get<Provider<const C>>() + * get<Annotated<Annotation, C>>() (for any type `Annotation') + * get<Annotated<Annotation, const C*>>() (for any type `Annotation') + * get<Annotated<Annotation, const C&>>() (for any type `Annotation') + * get<Annotated<Annotation, Provider<const C>>>() (for any type `Annotation') + * + * With a non-annotated parameter T, this returns a T. + * With an annotated parameter AnnotatedT=Annotated<Annotation, T>, this returns a T. + * E.g. if you want to inject a pointer for an annotated type, you can use this as follows: + * + * T* instance = injector.get<Annotated<Annotation, T*>>(); + * + * The shared_ptr versions come with a slight performance hit, prefer injecting pointers or references if possible. + * Calling get<> repeatedly for the same class with the same injector will return the same instance. + */ + template <typename T> + typename Injector<P...>::template RemoveAnnotations<T> get(); + + /** + * This is a convenient way to call get(). E.g.: + * + * MyInterface* x(injector); + * + * is equivalent to: + * + * MyInterface* x = injector.get<MyInterface*>(); + * + * Note that this can't be used to inject an annotated type, i.e. this does NOT work: + * + * fruit::Annotated<SomeAnnotation, SomeClass> foo(injector); + * + * Because foo would be of type fruit::Annotated, not of type SomeClass. In that case you must use get() instead, + * e.g.: + * + * SomeClass* foo = injector.get<fruit::Annotated<SomeAnnotation, SomeClass*>>();; + */ + template <typename T> + explicit operator T(); + + /** + * Gets all multibindings for a type T. + * + * Multibindings are independent from bindings; so if there is a (normal) binding for T, that is not returned. + * This returns an empty vector if there are no multibindings. + * + * With a non-annotated parameter T, this returns a const std::vector<T*>&. + * With an annotated parameter AnnotatedT=Annotated<Annotation, T>, this returns a const std::vector<T*>&. + */ + template <typename T> + const std::vector<RemoveAnnotations<T>*>& getMultibindings(); + + /** + * This method is deprecated since Fruit injectors can now be accessed concurrently by multiple threads. This will be + * removed in a future Fruit release. + * + * Eagerly injects all reachable bindings and multibindings of this injector. + * This only creates instances of the types that are either: + * - exposed by this Injector (i.e. in the Injector's type parameters) + * - bound by a multibinding + * - needed to inject one of the above (directly or indirectly) + * + * Unreachable bindings (i.e. bindings that are not exposed by this Injector, and that are not used by any reachable + * binding) are not processed. Bindings that are only used lazily, using a Provider, are NOT eagerly injected. + * + * Also note that this guarantee doesn't apply to Providers. + */ + FRUIT_DEPRECATED_DECLARATION(void eagerlyInjectAll()); + +private: + using Check1 = typename fruit::impl::meta::CheckIfError<fruit::impl::meta::Eval< + fruit::impl::meta::CheckNoRequiredTypesInInjectorArguments(fruit::impl::meta::Type<P>...)>>::type; + // Force instantiation of Check1. + static_assert(true || sizeof(Check1), ""); + + using Comp = fruit::impl::meta::Eval<fruit::impl::meta::ConstructComponentImpl(fruit::impl::meta::Type<P>...)>; + + using Check2 = typename fruit::impl::meta::CheckIfError<Comp>::type; + using VoidType = fruit::impl::meta::Type<void>; + // Force instantiation of Check2. + static_assert(true || sizeof(Check2), ""); + using Check3 = typename fruit::impl::meta::CheckIfError<fruit::impl::meta::Eval<fruit::impl::meta::If( + fruit::impl::meta::Not(fruit::impl::meta::IsEmptySet(typename Comp::RsSuperset)), + fruit::impl::meta::ConstructErrorWithArgVector(fruit::impl::InjectorWithRequirementsErrorTag, + fruit::impl::meta::SetToVector(typename Comp::RsSuperset)), + VoidType)>>::type; + // Force instantiation of Check3. + static_assert(true || sizeof(Check3), ""); + + friend struct fruit::impl::InjectorAccessorForTests; + + std::unique_ptr<fruit::impl::InjectorStorage> storage; +}; + +} // namespace fruit + +#include <fruit/impl/injector.defn.h> + +#endif // FRUIT_INJECTOR_H diff --git a/include/fruit/macro.h b/include/fruit/macro.h new file mode 100644 index 0000000..6ce59c8 --- /dev/null +++ b/include/fruit/macro.h @@ -0,0 +1,114 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_MACRO_H +#define FRUIT_MACRO_H + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/fruit_forward_decls.h> + +/** + * A convenience macro to define the Inject typedef while declaring/defining the constructor that will be used for + * injection. + * It also supports assisted injection and injection of annotated types. + * + * Example usage: + * + * class MyClass { + * public: + * INJECT(MyClass(Foo* foo, Bar* bar)) {...} + * }; + * + * is equivalent to: + * + * class MyClass { + * public: + * using Inject = MyClass(Foo*, Bar*); + * + * MyClass(Foo* foo, Bar* y) {...} + * }; + * + * Example usage for assisted injection (see PartialComponent::registerFactory): + * + * class MyClass { + * public: + * INJECT(MyClass(Foo* foo, ASSISTED(int) n) {...} + * }; + * + * is equivalent to: + * + * class MyClass { + * public: + * using Inject = MyClass(Foo*, Assisted<int>); + * + * MyClass(Foo* foo, int n) {...} + * }; + * + * Example usage for annotated types: + * + * class MyClass { + * public: + * INJECT(MyClass(ANNOTATED(SomeAnnotation, Foo*) foo, Bar* bar)) {...} + * }; + * + * ASSISTED and ANNOTATED *can* be used together in the same INJECT() annotation, but they can't both be used for a + * single parameter (as this wouldn't make sense, parameters that use assisted injection are user-supplied, they aren't + * injected from a binding). + * + * NOTE: This can't be used if the constructor is templated (the class can be templated, however), if there are any + * default arguments or if the constructor is marked `explicit'. + * In those cases, define the Inject annotation manually or use registerConstructor()/registerFactory() instead. + * + * NOTE: ASSISTED takes just one argument, but it's declared as variadic to make sure that the preprocessor doesn't + * choke on multi-argument templates like the map above, that the processor is unable to parse correctly. + * + * NOTE: ASSISTED takes just 2 arguments, but it's declared as variadic to make sure that the preprocessor doesn't choke + * on multi-argument templates, that the processor is unable to parse correctly. + * + * NOTE: In addition to the public Inject typedef, two typedefs (FruitAssistedTypedef and FruitAnnotatedTypedef) will be + * defined inside the class, make sure you don't define another typedef/field/method with the same name if you use the + * INJECT macro (unlikely but possible) these typedefs are an implementation detail of Fruit and should not be used. + * + * NOTE: The return type (MyClass in this case) should not be annotated. However an annotated + * MyClass (or MyClass factory) can be injected from any INJECT declaration. + */ +#define INJECT(Signature) \ + using Inject = Signature; \ + \ + template <typename FruitAssistedDeclarationParam> \ + using FruitAssistedTypedef = FruitAssistedDeclarationParam; \ + template <typename Annotation, typename FruitAnnotatedDeclarationParam> \ + using FruitAnnotatedTypedef = FruitAnnotatedDeclarationParam; \ + \ + Signature + +#define ASSISTED(...) FruitAssistedTypedef<__VA_ARGS__> +#define ANNOTATED(Annotation, ...) FruitAnnotatedTypedef<Annotation, __VA_ARGS__> + +/** + * These are intentionally NOT in the fruit namespace, they can't be there for technical reasons. + * + * NOTE: don't use these directly, they're only used to implement the INJECT macro. + * Consider them part of fruit::impl. + */ +template <typename T> +using FruitAssistedTypedef = fruit::Assisted<T>; +template <typename Annotation, typename T> +using FruitAnnotatedTypedef = fruit::Annotated<Annotation, T>; + +#endif // FRUIT_MACRO_H diff --git a/include/fruit/normalized_component.h b/include/fruit/normalized_component.h new file mode 100644 index 0000000..105edf8 --- /dev/null +++ b/include/fruit/normalized_component.h @@ -0,0 +1,143 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_NORMALIZED_COMPONENT_H +#define FRUIT_NORMALIZED_COMPONENT_H + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/fruit_forward_decls.h> +#include <fruit/impl/fruit_internal_forward_decls.h> +#include <fruit/impl/meta/component.h> +#include <fruit/impl/normalized_component_storage/normalized_component_storage_holder.h> +#include <memory> + +namespace fruit { + +/** + * This class allows for fast creation of multiple injectors that share most (or all) the bindings. + * + * This is an advanced feature of Fruit that allows to reduce injection time in some cases; if you're just starting to + * use Fruit you might want to ignore this for now (just construct an Injector from your root Component function). + * + * Using a NormalizedComponent only helps if: + * + * - You create multiple injectors during the lifetime of a process. E.g. if you only create one injector at startup you + * won't benefit from using NormalizedComponent. + * - Some of those injectors share all (or almost all) their bindings. + * + * When both of those requirements apply, you can switch to using NormalizedComponent in the "similar" injectors by + * first refactoring the injectors' root components to be of the form: + * + * fruit::Component<...> getRootComponent(...) { + * return fruit::createComponent() + * // This contains the bindings common to the group of similar injectors. + * .install(getSharedComponent, ...) + * // This contains the bindings specific to this injector. + * .install(getSpecificComponent, ...); + * } + * + * Then you can change your injector construction from: + * + * fruit::Injector<...> injector(getRootComponent, ...); + * + * To: + * + * fruit::NormalizedComponent<fruit::Required<...>, ...> normalized_component(getSharedComponent, ...); + * fruit::Injector<...> injector(normalized_component, getSpecificComponent, ...); + * + * This splits the work of constructing the Injector in two phases: normalization (where Fruit will call the Component + * functions to collect all the bindings and check for some classes of runtime errors) and the actual creation of the + * injector, during which Fruit will also collect/check the additional bindings specific to that injector. + * + * Then you can share the same normalized_component object across all those injectors (also in different threads, + * NormalizedComponent is thread-safe), so that the normalization step only occurs once (i.e., you should only construct + * NormalizedComponent from getSharedComponent once, otherwise you'd pay the normalization cost multiple times). + * + * Creating an Injector from a NormalizedComponent and injecting separate instances is very cheap, on the order of 2 us + * for an injection graph with 100 classes and 900 edges (for more details see the Benchmarks page of the Fruit wiki: + * https://github.com/google/fruit/wiki/benchmarks ). + * This might (depending of course on your performance requirements) allow you to create injectors where it would + * otherwise be unthinkable, e.g. creating a separate injector for each request in a server. + * + * Injectors that share the same NormalizedComponent are still independent; for example, if you call injector.get<Foo>() + * in two injectors, each injector will construct its own instance of Foo. + * + * Example usage in a server: + * + * // In the global scope. + * Component<Request> getRequestComponent(Request* request) { + * return fruit::createComponent() + * .bindInstance(*request); + * } + * + * // At startup (e.g. inside main()). + * NormalizedComponent<Required<Request>, Bar, Bar2> normalizedComponent = ...; + * + * ... + * for (...) { + * // For each request. + * Request request = ...; + * + * Injector<Foo, Bar> injector(normalizedComponent, getRequestComponent, &request); + * Foo* foo = injector.get<Foo*>(); + * ... + * } + * + * See also the documentation for the Injector constructor that takes a NormalizedComponent. + */ +template <typename... Params> +class NormalizedComponent { +public: + /** + * The Component used as parameter can have (and usually has) unsatisfied requirements, so it's usually of the form + * Component<Required<...>, ...>. + * + * The given component function is called with the provided arguments to construct the root component. + * The constraints on the argument types (if there are any) are the same as the ones for PartialComponent::install(). + */ + template <typename... FormalArgs, typename... Args> + NormalizedComponent(Component<Params...> (*)(FormalArgs...), Args&&... args); + + NormalizedComponent(NormalizedComponent&&) = default; + NormalizedComponent(const NormalizedComponent&) = delete; + + NormalizedComponent& operator=(NormalizedComponent&&) = delete; + NormalizedComponent& operator=(const NormalizedComponent&) = delete; + +private: + NormalizedComponent(fruit::impl::ComponentStorage&& storage, fruit::impl::MemoryPool memory_pool); + + // This is held via a unique_ptr to avoid including normalized_component_storage.h + // in fruit.h. + fruit::impl::NormalizedComponentStorageHolder storage; + + template <typename... OtherParams> + friend class Injector; + + using Comp = fruit::impl::meta::Eval<fruit::impl::meta::ConstructComponentImpl(fruit::impl::meta::Type<Params>...)>; + + using Check1 = typename fruit::impl::meta::CheckIfError<Comp>::type; + // Force instantiation of Check1. + static_assert(true || sizeof(Check1), ""); +}; + +} // namespace fruit + +#include <fruit/impl/normalized_component.defn.h> + +#endif // FRUIT_NORMALIZED_COMPONENT_H diff --git a/include/fruit/provider.h b/include/fruit/provider.h new file mode 100644 index 0000000..9284d0b --- /dev/null +++ b/include/fruit/provider.h @@ -0,0 +1,156 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_PROVIDER_H +#define FRUIT_PROVIDER_H + +// This include is not required here, but having it here shortens the include trace in error messages. +#include <fruit/impl/injection_errors.h> + +#include <fruit/component.h> + +namespace fruit { + +/** + * A Provider is a class that allows access to instances of the types used as parameters of the Provider template. + * It's possible to inject a Provider<MyClass> instead of MyClass itself, and this allows lazy injection. + * For example: + * + * class S { + * private: + * Bar* bar = nullptr; + * + * public: + * INJECT(S(Foo* foo, Provider<Bar> barProvider)) { + * if (foo->needsBar()) { + * bar = barProvider.get(); + * } + * } + * }; + * + * In the example above, Bar will only be created if get<Bar*> is called. + * This can be useful if Bar is expensive to create (or some other types that need to be injected when a Bar is injected + * are) or if there are other side effects of the Bar constructor that are undesirable when !foo->needsBar(). + * It's also possible to store the Provider object in a field, and create the Bar instance when the first method that + * needs it is called: + * + * class S { + * private: + * Provider<Bar> barProvider; + * + * public: + * INJECT(S(Provider<Bar> barProvider)) + * : barProvider(barProvider) { + * } + * + * void execute() { + * if (...) { + * Bar* bar = barProvider.get(); + * ... + * } + * } + * }; + * + * As usual, Fruit ensures that (at most) one instance is ever created in a given injector; so if the Bar object was + * already constructed, the get() will simply return it. + * + * Note that you can inject a Provider<Foo> whenever you could have injected a Foo. + * It doesn't matter if Foo was bound using PartialComponent::registerProvider() or not. + */ +template <typename C> +class Provider { +private: + using Check1 = + typename fruit::impl::meta::CheckIfError<fruit::impl::meta::Eval<fruit::impl::meta::CheckNormalizedTypes( + fruit::impl::meta::RemoveConstFromTypes(fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>))>>::type; + // Force instantiation of Check1. + static_assert(true || sizeof(Check1), ""); + + using Check2 = + typename fruit::impl::meta::CheckIfError<fruit::impl::meta::Eval<fruit::impl::meta::CheckNotAnnotatedTypes( + fruit::impl::meta::Vector<fruit::impl::meta::Type<C>>)>>::type; + // Force instantiation of Check2. + static_assert(true || sizeof(Check2), ""); + +public: + /** + * Returns an instance of the specified type. The following variations are allowed: + * + * On a Provider<Foo>, you can call: + * + * - provider.get<Foo>() + * - provider.get<Foo*>() + * - provider.get<Foo&>() + * - provider.get<const Foo*>() + * - provider.get<const Foo&>() + * - provider.get<std::shared_ptr<Foo>>() + * - provider.get<Provider<Foo>>() + * - provider.get<Provider<const Foo>>() + * + * On a Provider<const Foo>, you can call: + * + * - provider.get<Foo>() + * - provider.get<const Foo*>() + * - provider.get<const Foo&>() + * - provider.get<Provider<const Foo>>() + * + * The shared_ptr version is slightly slower than the ones returning a reference/pointer, use those if possible. + * + * Calling get<> repeatedly for the same class with the same injector will return the same instance (except for the + * first variation above, that returns a value; in that case, another copy of the same instance will be returned). + */ + template <typename T> + T get(); + + /** + * This is a convenient way to call get(). E.g.: + * + * C& x(provider); + * + * is equivalent to: + * + * C& x = provider.get<C&>(); + */ + template <typename T> + explicit operator T(); + + /** + * This is equivalent to get<C*>(), it's provided for convenience. + */ + C* get(); + +private: + // This is NOT owned by the provider object. It is not deleted on destruction. + // This is never nullptr. + fruit::impl::InjectorStorage* storage; + fruit::impl::InjectorStorage::Graph::node_iterator itr; + + Provider(fruit::impl::InjectorStorage* storage, fruit::impl::InjectorStorage::Graph::node_iterator itr); + + friend class fruit::impl::InjectorStorage; + + template <typename T> + friend struct fruit::impl::GetFirstStage; + + template <typename... OtherPs> + friend class Injector; +}; + +} // namespace fruit + +#include <fruit/impl/provider.defn.h> + +#endif // FRUIT_PROVIDER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..66bf79f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,26 @@ + +set(FRUIT_SOURCES + memory_pool.cpp +binding_normalization.cpp +demangle_type_name.cpp +component.cpp +fixed_size_allocator.cpp +injector_storage.cpp +normalized_component_storage.cpp +normalized_component_storage_holder.cpp +semistatic_map.cpp +semistatic_graph.cpp) + +if("${BUILD_SHARED_LIBS}") + add_library(fruit SHARED ${FRUIT_SOURCES}) + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + set_target_properties(fruit PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + endif() +else() + add_library(fruit STATIC ${FRUIT_SOURCES}) +endif() + +install(TARGETS fruit + ARCHIVE DESTINATION "${INSTALL_LIBRARY_DIR}" + LIBRARY DESTINATION "${INSTALL_LIBRARY_DIR}") diff --git a/src/binding_normalization.cpp b/src/binding_normalization.cpp new file mode 100644 index 0000000..5ffe8ef --- /dev/null +++ b/src/binding_normalization.cpp @@ -0,0 +1,520 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <algorithm> +#include <cstdlib> +#include <fruit/impl/util/type_info.h> +#include <iostream> +#include <memory> +#include <vector> + +#include <fruit/impl/data_structures/semistatic_graph.templates.h> +#include <fruit/impl/injector/injector_storage.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.templates.h> +#include <fruit/impl/normalized_component_storage/normalized_component_storage.h> + +using std::cout; +using std::endl; + +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +void BindingNormalization::printLazyComponentInstallationLoop( + const std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& entries_to_process, + const ComponentStorageEntry& last_entry) { + std::cerr << "Found a loop while expanding components passed to PartialComponent::install()." << std::endl; + std::cerr << "Component installation trace (from top-level to the most deeply-nested):" << std::endl; + for (const ComponentStorageEntry& entry : entries_to_process) { + switch (entry.kind) { + case ComponentStorageEntry::Kind::COMPONENT_WITH_ARGS_END_MARKER: + if (entry.type_id == last_entry.type_id && + last_entry.kind == ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS && + *entry.lazy_component_with_args.component == *last_entry.lazy_component_with_args.component) { + std::cerr << "<-- The loop starts here" << std::endl; + } + std::cerr << std::string(entry.lazy_component_with_args.component->getFunTypeId()) << std::endl; + break; + + case ComponentStorageEntry::Kind::COMPONENT_WITHOUT_ARGS_END_MARKER: + if (entry.type_id == last_entry.type_id && + last_entry.kind == ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS && + entry.lazy_component_with_no_args.erased_fun == last_entry.lazy_component_with_no_args.erased_fun) { + std::cerr << "<-- The loop starts here" << std::endl; + } + std::cerr << std::string(entry.type_id) << std::endl; + break; + + default: + break; + } + } + + switch (last_entry.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_ARGS: + std::cerr << std::string(last_entry.lazy_component_with_args.component->getFunTypeId()) << std::endl; + break; + + case ComponentStorageEntry::Kind::LAZY_COMPONENT_WITH_NO_ARGS: + std::cerr << std::string(last_entry.type_id) << std::endl; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + exit(1); +} + +void BindingNormalization::printMultipleBindingsError(TypeId type) { + std::cerr << "Fatal injection error: the type " << type.type_info->name() + << " was provided more than once, with different bindings." << std::endl + << "This was not caught at compile time because at least one of the involved components bound this type " + << "but didn't expose it in the component signature." << std::endl + << "If the type has a default constructor or an Inject annotation, this problem may arise even if this " + << "type is bound/provided by only one component (and then hidden), if this type is auto-injected in " + << "another component." << std::endl + << "If the source of the problem is unclear, try exposing this type in all the component signatures where " + << "it's bound; if no component hides it this can't happen." << std::endl; + exit(1); +} + +void BindingNormalization::printIncompatibleComponentReplacementsError( + const ComponentStorageEntry& replaced_component_entry, const ComponentStorageEntry& replacement_component_entry1, + const ComponentStorageEntry& replacement_component_entry2) { + using fun_t = void (*)(); + + fun_t replaced_fun_address; + switch (replaced_component_entry.kind) { + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS: + replaced_fun_address = replaced_component_entry.lazy_component_with_args.component->erased_fun; + break; + + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS: + replaced_fun_address = replaced_component_entry.lazy_component_with_no_args.erased_fun; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + fun_t replacement_fun_address1; + switch (replacement_component_entry1.kind) { + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + replacement_fun_address1 = replacement_component_entry1.lazy_component_with_args.component->erased_fun; + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + replacement_fun_address1 = replacement_component_entry1.lazy_component_with_no_args.erased_fun; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + fun_t replacement_fun_address2; + switch (replacement_component_entry2.kind) { + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + replacement_fun_address2 = replacement_component_entry2.lazy_component_with_args.component->erased_fun; + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + replacement_fun_address2 = replacement_component_entry2.lazy_component_with_no_args.erased_fun; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + constexpr static bool function_pointers_have_same_size = sizeof(void*) == sizeof(fun_t); + if (function_pointers_have_same_size) { + std::cerr << "Fatal injection error: the component function at " << reinterpret_cast<void*>(replaced_fun_address) + << " with signature " << std::string(replaced_component_entry.type_id) + << " was replaced (using .replace(...).with(...)) with both the component function at " + << reinterpret_cast<void*>(replacement_fun_address1) << " with signature " + << std::string(replacement_component_entry1.type_id) << " and the component function at " + << reinterpret_cast<void*>(replacement_fun_address2) << " with signature " + << std::string(replacement_component_entry2.type_id) << " ." << std::endl; + } else { + std::cerr << "Fatal injection error: a component function with signature " + << std::string(replaced_component_entry.type_id) + << " was replaced (using .replace(...).with(...)) with both a component function with signature " + << std::string(replacement_component_entry1.type_id) << " and another component function with signature " + << std::string(replacement_component_entry2.type_id) << " ." << std::endl; + } + exit(1); +} + +void BindingNormalization::printComponentReplacementFailedBecauseTargetAlreadyExpanded( + const ComponentStorageEntry& replaced_component_entry, const ComponentStorageEntry& replacement_component_entry) { + using fun_t = void (*)(); + + fun_t replaced_fun_address; + switch (replaced_component_entry.kind) { + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_ARGS: + replaced_fun_address = replaced_component_entry.lazy_component_with_args.component->erased_fun; + break; + + case ComponentStorageEntry::Kind::REPLACED_LAZY_COMPONENT_WITH_NO_ARGS: + replaced_fun_address = replaced_component_entry.lazy_component_with_no_args.erased_fun; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + fun_t replacement_fun_address1; + switch (replacement_component_entry.kind) { + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + replacement_fun_address1 = replacement_component_entry.lazy_component_with_args.component->erased_fun; + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + replacement_fun_address1 = replacement_component_entry.lazy_component_with_no_args.erased_fun; + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + constexpr static bool function_pointers_have_same_size = sizeof(void*) == sizeof(fun_t); + if (function_pointers_have_same_size) { + std::cerr << "Fatal injection error: unable to replace (using .replace(...).with(...)) the component function at " + << reinterpret_cast<void*>(replaced_fun_address) << " with signature " + << std::string(replaced_component_entry.type_id) << " with the component function at " + << reinterpret_cast<void*>(replacement_fun_address1) << " with signature " + << std::string(replacement_component_entry.type_id) + << " because the former component function was installed before the .replace(...).with(...)." << std::endl + << "You should change the order of installation of subcomponents so that .replace(...).with(...) is " + << "processed before the installation of the component to replace."; + } else { + std::cerr << "Fatal injection error: unable to replace (using .replace(...).with(...)) a component function with " + << "signature " << std::string(replaced_component_entry.type_id) + << " with a component function at with signature " << std::string(replacement_component_entry.type_id) + << " because the former component function was installed before the .replace(...).with(...)." << std::endl + << "You should change the order of installation of subcomponents so that .replace(...).with(...) is " + << "processed before the installation of the component to replace."; + } + exit(1); +} + +void BindingNormalization::addMultibindings(std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + const multibindings_vector_t& multibindingsVector) { + +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: adding multibindings:" << std::endl; +#endif + // Now we must merge multiple bindings for the same type. + for (auto i = multibindingsVector.begin(); i != multibindingsVector.end(); ++i) { + const ComponentStorageEntry& multibinding_entry = i->first; + const ComponentStorageEntry& multibinding_vector_creator_entry = i->second; + FruitAssert(multibinding_entry.kind == + ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION || + multibinding_entry.kind == + ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION || + multibinding_entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT); + FruitAssert(multibinding_vector_creator_entry.kind == ComponentStorageEntry::Kind::MULTIBINDING_VECTOR_CREATOR); + NormalizedMultibindingSet& b = multibindings[multibinding_entry.type_id]; + + // Might be set already, but we need to set it if there was no multibinding for this type. + b.get_multibindings_vector = multibinding_vector_creator_entry.multibinding_vector_creator.get_multibindings_vector; + + switch (i->first.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_CONSTRUCTED_OBJECT: { + NormalizedMultibinding normalized_multibinding; + normalized_multibinding.is_constructed = true; + normalized_multibinding.object = i->first.multibinding_for_constructed_object.object_ptr; + b.elems.push_back(std::move(normalized_multibinding)); + } break; + + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION: { + fixed_size_allocator_data.addExternallyAllocatedType(i->first.type_id); + NormalizedMultibinding normalized_multibinding; + normalized_multibinding.is_constructed = false; + normalized_multibinding.create = i->first.multibinding_for_object_to_construct.create; + b.elems.push_back(std::move(normalized_multibinding)); + } break; + + case ComponentStorageEntry::Kind::MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION: { + fixed_size_allocator_data.addType(i->first.type_id); + NormalizedMultibinding normalized_multibinding; + normalized_multibinding.is_constructed = false; + normalized_multibinding.create = i->first.multibinding_for_object_to_construct.create; + b.elems.push_back(std::move(normalized_multibinding)); + } break; + + default: +#ifdef FRUIT_EXTRA_DEBUG + std::cerr << "Unexpected kind: " << (std::size_t)i->first.kind << std::endl; +#endif + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + } +} + +void BindingNormalization::normalizeBindingsWithUndoableBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + MemoryPool& memory_pool_for_fully_expanded_components_maps, MemoryPool& memory_pool_for_component_replacements_maps, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings, + BindingCompressionInfoMap& bindingCompressionInfoMap, + LazyComponentWithNoArgsSet& fully_expanded_components_with_no_args, + LazyComponentWithArgsSet& fully_expanded_components_with_args, + LazyComponentWithNoArgsReplacementMap& component_with_no_args_replacements, + LazyComponentWithArgsReplacementMap& component_with_args_replacements) { + + FruitAssert(bindingCompressionInfoMap.empty()); + + normalizeBindingsWithBindingCompression( + std::move(toplevel_entries), fixed_size_allocator_data, memory_pool, + memory_pool_for_fully_expanded_components_maps, memory_pool_for_component_replacements_maps, exposed_types, + bindings_vector, multibindings, + [&bindingCompressionInfoMap](TypeId c_type_id, NormalizedComponentStorage::CompressedBindingUndoInfo undo_info) { + bindingCompressionInfoMap[c_type_id] = undo_info; + }, + [&fully_expanded_components_with_no_args](LazyComponentWithNoArgsSet& fully_expanded_components) { + fully_expanded_components_with_no_args = std::move(fully_expanded_components); + fully_expanded_components.clear(); + }, + [&fully_expanded_components_with_args](LazyComponentWithArgsSet& fully_expanded_components) { + fully_expanded_components_with_args = std::move(fully_expanded_components); + fully_expanded_components.clear(); + }, + [&component_with_no_args_replacements](LazyComponentWithNoArgsReplacementMap& component_replacements) { + component_with_no_args_replacements = std::move(component_replacements); + component_replacements.clear(); + }, + [&component_with_args_replacements](LazyComponentWithArgsReplacementMap& component_replacements) { + component_with_args_replacements = std::move(component_replacements); + component_replacements.clear(); + }); +} + +void BindingNormalization::normalizeBindingsWithPermanentBindingCompression( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, MemoryPool& memory_pool, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings) { + normalizeBindingsWithBindingCompression( + std::move(toplevel_entries), fixed_size_allocator_data, memory_pool, memory_pool, memory_pool, exposed_types, + bindings_vector, multibindings, [](TypeId, NormalizedComponentStorage::CompressedBindingUndoInfo) {}, + [](LazyComponentWithNoArgsSet&) {}, [](LazyComponentWithArgsSet&) {}, + [](LazyComponentWithNoArgsReplacementMap&) {}, [](LazyComponentWithArgsReplacementMap&) {}); +} + +void BindingNormalization::normalizeBindingsAndAddTo( + FixedSizeVector<ComponentStorageEntry>&& toplevel_entries, MemoryPool& memory_pool, + const NormalizedComponentStorage& base_normalized_component, + FixedSizeAllocator::FixedSizeAllocatorData& fixed_size_allocator_data, + std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>& new_bindings_vector, + std::unordered_map<TypeId, NormalizedMultibindingSet>& multibindings) { + + multibindings = base_normalized_component.multibindings; + + fixed_size_allocator_data = base_normalized_component.fixed_size_allocator_data; + + multibindings_vector_t multibindings_vector = + multibindings_vector_t(ArenaAllocator<multibindings_vector_elem_t>(memory_pool)); + + HashMapWithArenaAllocator<TypeId, ComponentStorageEntry> binding_data_map = + createHashMapWithArenaAllocator<TypeId, ComponentStorageEntry>(20 /* capacity */, memory_pool); + + using Graph = NormalizedComponentStorage::Graph; + + normalizeBindings( + std::move(toplevel_entries), fixed_size_allocator_data, memory_pool, memory_pool, memory_pool, binding_data_map, + [](ComponentStorageEntry) {}, + [&multibindings_vector](ComponentStorageEntry multibinding, ComponentStorageEntry multibinding_vector_creator) { + multibindings_vector.emplace_back(multibinding, multibinding_vector_creator); + }, + [&base_normalized_component](TypeId type_id) { return base_normalized_component.bindings.find(type_id); }, + [&base_normalized_component](Graph::const_node_iterator itr) { + return !(itr == base_normalized_component.bindings.end()); + }, + [](Graph::const_node_iterator itr) { return itr.isTerminal(); }, + [](Graph::const_node_iterator itr) { return itr.getNode().object; }, + [](Graph::const_node_iterator itr) { return itr.getNode().create; }, + [&base_normalized_component](const LazyComponentWithNoArgs& lazy_component) { + return base_normalized_component.fully_expanded_components_with_no_args.count(lazy_component) != 0; + }, + [&base_normalized_component](const LazyComponentWithArgs& lazy_component) { + return base_normalized_component.fully_expanded_components_with_args.count(lazy_component) != 0; + }, + [](LazyComponentWithNoArgsSet&) {}, [](LazyComponentWithArgsSet&) {}, + [&base_normalized_component](const LazyComponentWithNoArgs& lazy_component) { + return base_normalized_component.component_with_no_args_replacements.find(lazy_component); + }, + [&base_normalized_component](const LazyComponentWithArgs& lazy_component) { + return base_normalized_component.component_with_args_replacements.find(lazy_component); + }, + [&base_normalized_component](typename LazyComponentWithNoArgsReplacementMap::const_iterator itr) { + return itr != base_normalized_component.component_with_no_args_replacements.end(); + }, + [&base_normalized_component](typename LazyComponentWithArgsReplacementMap::const_iterator itr) { + return itr != base_normalized_component.component_with_args_replacements.end(); + }, + [](typename LazyComponentWithNoArgsReplacementMap::const_iterator itr) { return itr->second; }, + [](typename LazyComponentWithArgsReplacementMap::const_iterator itr) { return itr->second; }, + [](LazyComponentWithNoArgsReplacementMap&) {}, [](LazyComponentWithArgsReplacementMap&) {}); + + // Copy the normalized bindings into the result vector. + new_bindings_vector.clear(); + new_bindings_vector.reserve(binding_data_map.size()); + for (auto& p : binding_data_map) { + new_bindings_vector.push_back(p.second); + } + + // Determine what binding compressions must be undone. + + HashSetWithArenaAllocator<TypeId> binding_compressions_to_undo = + createHashSetWithArenaAllocator<TypeId>(20 /* capacity */, memory_pool); + for (const ComponentStorageEntry& entry : new_bindings_vector) { + switch (entry.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::BINDING_FOR_CONSTRUCTED_OBJECT: + break; + + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION: + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION: + case ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION: { + const BindingDeps* entry_deps = entry.binding_for_object_to_construct.deps; + for (std::size_t i = 0; i < entry_deps->num_deps; ++i) { + auto binding_compression_itr = base_normalized_component.binding_compression_info_map.find(entry_deps->deps[i]); + if (binding_compression_itr != base_normalized_component.binding_compression_info_map.end() && + binding_compression_itr->second.i_type_id != entry.type_id) { + // The binding compression for `p.second.getDeps()->deps[i]' must be undone because something + // different from binding_compression_itr->iTypeId is now bound to it. + binding_compressions_to_undo.insert(entry_deps->deps[i]); + } + } + } break; + + default: +#ifdef FRUIT_EXTRA_DEBUG + std::cerr << "Unexpected kind: " << (std::size_t)entry.kind << std::endl; +#endif + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + break; + } + } + + // Step 3: undo any binding compressions that can no longer be applied. + for (TypeId cTypeId : binding_compressions_to_undo) { + auto binding_compression_itr = base_normalized_component.binding_compression_info_map.find(cTypeId); + FruitAssert(binding_compression_itr != base_normalized_component.binding_compression_info_map.end()); + FruitAssert(!(base_normalized_component.bindings.find(binding_compression_itr->second.i_type_id) == + base_normalized_component.bindings.end())); + + ComponentStorageEntry c_binding; + c_binding.type_id = cTypeId; + c_binding.kind = ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION; + c_binding.binding_for_object_to_construct = binding_compression_itr->second.c_binding; + + ComponentStorageEntry i_binding; + i_binding.type_id = binding_compression_itr->second.i_type_id; + i_binding.kind = ComponentStorageEntry::Kind::BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION; + i_binding.binding_for_object_to_construct = binding_compression_itr->second.i_binding; + + new_bindings_vector.push_back(std::move(c_binding)); + // This TypeId is already in normalized_component.bindings, we overwrite it here. + new_bindings_vector.push_back(std::move(i_binding)); + +#ifdef FRUIT_EXTRA_DEBUG + std::cout << "InjectorStorage: undoing binding compression for: " << binding_compression_itr->second.i_type_id + << "->" << cTypeId << std::endl; +#endif + } + + // Step 4: Add multibindings. + BindingNormalization::addMultibindings(multibindings, fixed_size_allocator_data, multibindings_vector); +} + +void BindingNormalization::handlePreexistingLazyComponentWithArgsReplacement( + ComponentStorageEntry& replaced_component_entry, const ComponentStorageEntry& preexisting_replacement, + ComponentStorageEntry& new_replacement) { + switch (new_replacement.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + if (preexisting_replacement.kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS || + preexisting_replacement.lazy_component_with_no_args.erased_fun != + new_replacement.lazy_component_with_no_args.erased_fun) { + printIncompatibleComponentReplacementsError(replaced_component_entry, new_replacement, preexisting_replacement); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + // Duplicate but consistent replacement, we'll ignore it. + replaced_component_entry.lazy_component_with_args.destroy(); + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + if (preexisting_replacement.kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS || + !(*preexisting_replacement.lazy_component_with_args.component == + *new_replacement.lazy_component_with_args.component)) { + printIncompatibleComponentReplacementsError(replaced_component_entry, new_replacement, preexisting_replacement); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + // Duplicate but consistent replacement, we'll ignore it. + replaced_component_entry.lazy_component_with_args.destroy(); + new_replacement.lazy_component_with_args.destroy(); + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } +} + +void BindingNormalization::handlePreexistingLazyComponentWithNoArgsReplacement( + ComponentStorageEntry& replaced_component_entry, const ComponentStorageEntry& preexisting_replacement, + ComponentStorageEntry& new_replacement) { + switch (new_replacement.kind) { // LCOV_EXCL_BR_LINE + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS: + if (preexisting_replacement.kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS || + preexisting_replacement.lazy_component_with_no_args.erased_fun != + new_replacement.lazy_component_with_no_args.erased_fun) { + printIncompatibleComponentReplacementsError(replaced_component_entry, new_replacement, preexisting_replacement); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + // Duplicate but consistent replacement, we'll ignore it. + break; + + case ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS: + if (new_replacement.kind != ComponentStorageEntry::Kind::REPLACEMENT_LAZY_COMPONENT_WITH_ARGS || + !(*preexisting_replacement.lazy_component_with_args.component == + *new_replacement.lazy_component_with_args.component)) { + printIncompatibleComponentReplacementsError(replaced_component_entry, new_replacement, preexisting_replacement); + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } + + // Duplicate but consistent replacement, we'll ignore it. + new_replacement.lazy_component_with_args.destroy(); + break; + + default: + FRUIT_UNREACHABLE; // LCOV_EXCL_LINE + } +} + +} // namespace impl +// We need a LCOV_EXCL_BR_LINE below because for some reason gcov/lcov think there's a branch there. +} // namespace fruit LCOV_EXCL_BR_LINE diff --git a/src/component.cpp b/src/component.cpp new file mode 100644 index 0000000..69e971b --- /dev/null +++ b/src/component.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/component.h> + +#include <exception> +#include <iostream> + +namespace fruit { + +// TODO: reimplement this check somehow. +/* +EmptyPartialComponent::~EmptyPartialComponent() { + // If the user of Fruit didn't cast the result of createComponent() (possibly after adding some bindings) to a +Component<>, we abort + // because that's a misuse of the Fruit API. If we went ahead, there might be some PartialComponent<> instances that +point + // to the ComponentStorage in this EmptyComponent, and any use of those would cause undefined behavior. + // If an exception is in flight, don't abort; that's likely to be an unexpected flow so we don't want to alert the +user of Fruit, + // and there can't be any leftover instances of PartialComponent<> referring to this EmptyComponent anyway. + if (!already_converted_to_component && !std::uncaught_exception()) { + std::cerr << "The result of fruit::createComponent() was not converted to a Component before the end of the +expression! " + << "This is a misuse of the Fruit API. This is likely to cause undefined behavior, aborting now to be safe." << +std::endl; + std::abort(); + } +} +*/ + +// We need a LCOV_EXCL_BR_LINE below because for some reason gcov/lcov think there's a branch there. +} // namespace fruit LCOV_EXCL_BR_LINE diff --git a/src/demangle_type_name.cpp b/src/demangle_type_name.cpp new file mode 100644 index 0000000..d0a72dc --- /dev/null +++ b/src/demangle_type_name.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/fruit-config.h> +#include <fruit/impl/util/demangle_type_name.h> + +#if FRUIT_HAS_CXA_DEMANGLE + +#include <cstdlib> +#include <cxxabi.h> +#include <string> + +std::string demangleTypeName(const char* name) { + int status; + std::string result; + char* demangled_name = abi::__cxa_demangle(name, nullptr, nullptr, &status); + if (status == 0) { // LCOV_EXCL_BR_LINE + result = demangled_name; + std::free(demangled_name); + } + return result; +} + +#else // !FRUIT_HAS_CXA_DEMANGLE + +// For other compilers, fall back on returning demangled names. This might not be the appropriate behavior, +std::string demangleTypeName(const char* name) { + return std::string(name); +} + +#endif // !FRUIT_HAS_CXA_DEMANGLE diff --git a/src/fixed_size_allocator.cpp b/src/fixed_size_allocator.cpp new file mode 100644 index 0000000..e1a89ab --- /dev/null +++ b/src/fixed_size_allocator.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/data_structures/fixed_size_allocator.h> +#include <fruit/impl/data_structures/fixed_size_vector.templates.h> + +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +FixedSizeAllocator::~FixedSizeAllocator() { + // Destroy all objects in reverse order. + std::pair<destroy_t, void*>* p = on_destruction.end(); + while (p != on_destruction.begin()) { + --p; + p->first(p->second); + } + delete[] storage_begin; +} + +} // namespace impl +} // namespace fruit diff --git a/src/injector_storage.cpp b/src/injector_storage.cpp new file mode 100644 index 0000000..840416a --- /dev/null +++ b/src/injector_storage.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <algorithm> +#include <cstdlib> +#include <fruit/impl/util/type_info.h> +#include <iostream> +#include <memory> +#include <vector> + +#include <fruit/impl/component_storage/component_storage.h> +#include <fruit/impl/data_structures/semistatic_graph.templates.h> +#include <fruit/impl/injector/injector_storage.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.templates.h> + +using std::cout; +using std::endl; + +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +void InjectorStorage::fatal(const std::string& error) { + std::cerr << "Fatal injection error: " << error << std::endl; + exit(1); +} + +// LCOV_EXCL_START +namespace { +template <typename Id, typename Value> +struct DummyNode { + Id getId() { + return Id(); + } + bool isTerminal() { + return false; + } + Id* getEdgesBegin() { + return nullptr; + } + Id* getEdgesEnd() { + return nullptr; + } + Value getValue() { + return Value(); + } +}; +} +// LCOV_EXCL_STOP + +InjectorStorage::InjectorStorage(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool) + : normalized_component_storage_ptr(new NormalizedComponentStorage( + std::move(component), exposed_types, memory_pool, NormalizedComponentStorage::WithPermanentCompression())), + allocator(normalized_component_storage_ptr->fixed_size_allocator_data), + bindings(normalized_component_storage_ptr->bindings, (DummyNode<TypeId, NormalizedBinding>*)nullptr, + (DummyNode<TypeId, NormalizedBinding>*)nullptr, memory_pool), + multibindings(std::move(normalized_component_storage_ptr->multibindings)) { + +#ifdef FRUIT_EXTRA_DEBUG + bindings.checkFullyConstructed(); +#endif +} + +InjectorStorage::InjectorStorage(const NormalizedComponentStorage& normalized_component, ComponentStorage&& component, + MemoryPool& memory_pool) { + + FixedSizeAllocator::FixedSizeAllocatorData fixed_size_allocator_data; + using new_bindings_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + new_bindings_vector_t new_bindings_vector = new_bindings_vector_t(ArenaAllocator<ComponentStorageEntry>(memory_pool)); + + BindingNormalization::normalizeBindingsAndAddTo(std::move(component).release(), memory_pool, normalized_component, + fixed_size_allocator_data, new_bindings_vector, multibindings); + + allocator = FixedSizeAllocator(fixed_size_allocator_data); + + bindings = Graph(normalized_component.bindings, BindingDataNodeIter{new_bindings_vector.begin()}, + BindingDataNodeIter{new_bindings_vector.end()}, memory_pool); +#ifdef FRUIT_EXTRA_DEBUG + bindings.checkFullyConstructed(); +#endif +} + +InjectorStorage::~InjectorStorage() {} + +void InjectorStorage::ensureConstructedMultibinding(NormalizedMultibindingSet& multibinding_set) { + for (NormalizedMultibinding& multibinding : multibinding_set.elems) { + if (!multibinding.is_constructed) { + multibinding.object = multibinding.create(*this); + multibinding.is_constructed = true; + } + } +} + +void* InjectorStorage::getMultibindings(TypeId typeInfo) { + NormalizedMultibindingSet* multibinding_set = getNormalizedMultibindingSet(typeInfo); + if (multibinding_set == nullptr) { + // Not registered. + return nullptr; + } + return multibinding_set->get_multibindings_vector(*this).get(); +} + +void InjectorStorage::eagerlyInjectMultibindings() { + std::lock_guard<std::recursive_mutex> lock(mutex); + for (auto& typeInfoInfoPair : multibindings) { + typeInfoInfoPair.second.get_multibindings_vector(*this); + } +} + +} // namespace impl +// We need a LCOV_EXCL_BR_LINE below because for some reason gcov/lcov think there's a branch there. +} // namespace fruit LCOV_EXCL_BR_LINE diff --git a/src/memory_pool.cpp b/src/memory_pool.cpp new file mode 100644 index 0000000..c4105b2 --- /dev/null +++ b/src/memory_pool.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/data_structures/memory_pool.h> + +using namespace fruit::impl; + +void MemoryPool::destroy() { + for (void* p : allocated_chunks) { + operator delete(p); + } +} diff --git a/src/normalized_component_storage.cpp b/src/normalized_component_storage.cpp new file mode 100644 index 0000000..b483326 --- /dev/null +++ b/src/normalized_component_storage.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <algorithm> +#include <cstdlib> +#include <fruit/impl/util/type_info.h> +#include <iostream> +#include <memory> +#include <vector> + +#include <fruit/impl/normalized_component_storage/normalized_component_storage.h> + +#include <fruit/impl/component_storage/component_storage.h> +#include <fruit/impl/data_structures/semistatic_graph.templates.h> +#include <fruit/impl/data_structures/semistatic_map.templates.h> +#include <fruit/impl/injector/injector_storage.h> +#include <fruit/impl/normalized_component_storage/binding_normalization.h> + +using std::cout; +using std::endl; + +using namespace fruit; +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +NormalizedComponentStorage::NormalizedComponentStorage(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool, WithPermanentCompression) + : normalized_component_memory_pool(), + binding_compression_info_map(createHashMapWithArenaAllocator<TypeId, CompressedBindingUndoInfo>( + 0 /* capacity */, normalized_component_memory_pool)), + fully_expanded_components_with_no_args( + createLazyComponentWithNoArgsSet(0 /* capacity */, normalized_component_memory_pool)), + fully_expanded_components_with_args( + createLazyComponentWithArgsSet(0 /* capacity */, normalized_component_memory_pool)), + component_with_no_args_replacements( + createLazyComponentWithNoArgsReplacementMap(0 /* capacity */, normalized_component_memory_pool)), + component_with_args_replacements( + createLazyComponentWithArgsReplacementMap(0 /* capacity */, normalized_component_memory_pool)) { + + using bindings_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + bindings_vector_t bindings_vector = bindings_vector_t(ArenaAllocator<ComponentStorageEntry>(memory_pool)); + BindingNormalization::normalizeBindingsWithPermanentBindingCompression(std::move(component).release(), + fixed_size_allocator_data, memory_pool, + exposed_types, bindings_vector, multibindings); + + bindings = SemistaticGraph<TypeId, NormalizedBinding>(InjectorStorage::BindingDataNodeIter{bindings_vector.begin()}, + InjectorStorage::BindingDataNodeIter{bindings_vector.end()}, + memory_pool); +} + +NormalizedComponentStorage::NormalizedComponentStorage(ComponentStorage&& component, + const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool, WithUndoableCompression) + : normalized_component_memory_pool(), + binding_compression_info_map(createHashMapWithArenaAllocator<TypeId, CompressedBindingUndoInfo>( + 20 /* capacity */, normalized_component_memory_pool)), + fully_expanded_components_with_no_args( + createLazyComponentWithNoArgsSet(20 /* capacity */, normalized_component_memory_pool)), + fully_expanded_components_with_args( + createLazyComponentWithArgsSet(20 /* capacity */, normalized_component_memory_pool)), + component_with_no_args_replacements( + createLazyComponentWithNoArgsReplacementMap(20 /* capacity */, normalized_component_memory_pool)), + component_with_args_replacements( + createLazyComponentWithArgsReplacementMap(20 /* capacity */, normalized_component_memory_pool)) { + + using bindings_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>; + bindings_vector_t bindings_vector = bindings_vector_t(ArenaAllocator<ComponentStorageEntry>(memory_pool)); + BindingNormalization::normalizeBindingsWithUndoableBindingCompression( + std::move(component).release(), fixed_size_allocator_data, memory_pool, normalized_component_memory_pool, + normalized_component_memory_pool, exposed_types, bindings_vector, multibindings, binding_compression_info_map, + fully_expanded_components_with_no_args, fully_expanded_components_with_args, component_with_no_args_replacements, + component_with_args_replacements); + + bindings = SemistaticGraph<TypeId, NormalizedBinding>(InjectorStorage::BindingDataNodeIter{bindings_vector.begin()}, + InjectorStorage::BindingDataNodeIter{bindings_vector.end()}, + memory_pool); +} + +NormalizedComponentStorage::~NormalizedComponentStorage() { + for (auto& x : fully_expanded_components_with_args) { + x.destroy(); + } + + for (const auto& pair : component_with_args_replacements) { + const LazyComponentWithArgs& replaced_component = pair.first; + const ComponentStorageEntry& replacement_component = pair.second; + replaced_component.destroy(); + replacement_component.destroy(); + } + + for (const auto& pair : component_with_no_args_replacements) { + const ComponentStorageEntry& replacement_component = pair.second; + replacement_component.destroy(); + } + + // We must free all the memory in these before the normalized_component_memory_pool is destroyed. + binding_compression_info_map = createHashMapWithArenaAllocator<TypeId, CompressedBindingUndoInfo>( + 0 /* capacity */, normalized_component_memory_pool); + fully_expanded_components_with_no_args = + createLazyComponentWithNoArgsSet(0 /* capacity */, normalized_component_memory_pool); + fully_expanded_components_with_args = + createLazyComponentWithArgsSet(0 /* capacity */, normalized_component_memory_pool); + component_with_no_args_replacements = + createLazyComponentWithNoArgsReplacementMap(0 /* capacity */, normalized_component_memory_pool); + component_with_args_replacements = + createLazyComponentWithArgsReplacementMap(0 /* capacity */, normalized_component_memory_pool); +} + +} // namespace impl +// We need a LCOV_EXCL_BR_LINE below because for some reason gcov/lcov think there's a branch there. +} // namespace fruit LCOV_EXCL_BR_LINE diff --git a/src/normalized_component_storage_holder.cpp b/src/normalized_component_storage_holder.cpp new file mode 100644 index 0000000..b138ebd --- /dev/null +++ b/src/normalized_component_storage_holder.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/normalized_component_storage/normalized_component_storage.h> +#include <fruit/impl/normalized_component_storage/normalized_component_storage_holder.h> + +using namespace fruit; +using namespace fruit::impl; + +namespace fruit { +namespace impl { + +NormalizedComponentStorageHolder::NormalizedComponentStorageHolder( + ComponentStorage&& component, const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types, + MemoryPool& memory_pool, WithUndoableCompression) + : storage(new NormalizedComponentStorage(std::move(component), exposed_types, memory_pool, + NormalizedComponentStorage::WithUndoableCompression())) {} + +NormalizedComponentStorageHolder::~NormalizedComponentStorageHolder() {} + +} // namespace impl +} // namespace fruit diff --git a/src/semistatic_graph.cpp b/src/semistatic_graph.cpp new file mode 100644 index 0000000..c018bbd --- /dev/null +++ b/src/semistatic_graph.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/data_structures/semistatic_graph.h> +#include <fruit/impl/data_structures/semistatic_graph.templates.h> + +#include <fruit/impl/normalized_component_storage/normalized_bindings.h> +#include <fruit/impl/util/type_info.h> + +using namespace fruit::impl; + +// Clang requires the following instantiation to be in its namespace. +namespace fruit { +namespace impl { + +template class SemistaticGraph<TypeId, NormalizedBinding>; + +} // namespace impl +} // namespace fruit diff --git a/src/semistatic_map.cpp b/src/semistatic_map.cpp new file mode 100644 index 0000000..57811f1 --- /dev/null +++ b/src/semistatic_map.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +#define IN_FRUIT_CPP_FILE + +#include <fruit/impl/data_structures/semistatic_graph.h> +#include <fruit/impl/data_structures/semistatic_map.h> +#include <fruit/impl/data_structures/semistatic_map.templates.h> + +#include <fruit/impl/util/type_info.h> + +using namespace fruit::impl; + +// Clang requires the following instantiation to be in its namespace. +namespace fruit { +namespace impl { + +template class SemistaticMap<TypeId, SemistaticGraphInternalNodeId>; + +} // namespace impl +} // namespace fruit diff --git a/tests/BUILD b/tests/BUILD new file mode 100644 index 0000000..fa6349f --- /dev/null +++ b/tests/BUILD @@ -0,0 +1,98 @@ + +licenses(["notice"]) + +TEST_HEADERS = [ + "test_macros.h", + "test_common.h", + "class_construction_tracker.h", +] + +filegroup( + name = "test_headers_filegroup", + srcs = TEST_HEADERS, + visibility = ["//third_party/fruit/tests:__subpackages__"], +) + +cc_library( + name = "test_headers", + srcs = [], + hdrs = TEST_HEADERS, + visibility = ["//third_party/fruit/tests:__subpackages__"], + includes = ["."], +) + +[cc_test( + name = filename[:-4], + srcs = [filename], + deps = [ + ":test_headers", + "//third_party/fruit", + ] +) for filename in glob( + ["*.cpp"], + exclude = ["include_test.cpp"])] + +FRUIT_PUBLIC_HEADERS = [ + "component", + "fruit", + "fruit_forward_decls", + "injector", + "macro", + "normalized_component", + "provider", +] + +genrule( + name = "fruit_test_config_genrule", + srcs = [ + "//third_party/fruit", + "//third_party/fruit:fruit_headers", + ":test_headers_filegroup", + ], + # Here we copy libfruit.so to work around an issue with py_test where the outputs of a cc_library in the data + # attribute of a py_test are not taken into account. + outs = [ + "fruit_test_config.py", + "libfruit.so" + ], + visibility = ["//third_party/fruit/tests:__subpackages__"], + cmd = "" + + "FRUIT_HEADERS_LOCATION=`for f in $(locations //third_party/fruit:fruit_headers); do echo \"$$f\"; done | fgrep configuration/bazel/ | head -n 1 | sed 's|configuration/bazel/.*|./|'`;" + + "TEST_HEADERS_LOCATION=`for f in $(locations :test_headers_filegroup); do echo \"$$f\"; done | fgrep test_macros.h | sed 's|test_macros.h|./|'`;" + + "LIBFRUIT_LOCATION=`for f in $(locations //third_party/fruit); do echo \"$$f\"; done | fgrep libfruit.so | head -n 1 | sed 's|libfruit.so|./|'`;" + + "cp $${LIBFRUIT_LOCATION}/libfruit.so $(location libfruit.so);" + # The removal of ".*/genfiles" from the location is a bit of a hack, but that's how the path will look like in the py_tests + # below. + + "LIBFRUIT_COPY_DIR_LOCATION=`dirname $(location libfruit.so) | sed 's|.*/genfiles/|./|'`;" + + "LIBFRUIT_COPY_LOCATION=`echo $(location libfruit.so) | sed 's|.*/genfiles/|./|'`;" + + "echo -e \"" + + "CXX='g++'\n" + + "CXX_COMPILER_NAME='GNU'\n" + + "CXX_COMPILER_VERSION='5.0.0'\n" + + "FRUIT_COMPILE_FLAGS='$(CC_FLAGS) -std=c++0x -W -Wall -Wno-missing-braces -g -Werror'\n" + + "ADDITIONAL_INCLUDE_DIRS=''\n" + + "CMAKE_BUILD_TYPE=None\n" + + "PATH_TO_COMPILED_FRUIT='$${LIBFRUIT_COPY_DIR_LOCATION}'\n" + + "PATH_TO_COMPILED_FRUIT_LIB='$${LIBFRUIT_COPY_LOCATION}'\n" + + "PATH_TO_FRUIT_STATIC_HEADERS='$${FRUIT_HEADERS_LOCATION}/include'\n" + + "PATH_TO_FRUIT_GENERATED_HEADERS='$${FRUIT_HEADERS_LOCATION}/configuration/bazel'\n" + + "PATH_TO_FRUIT_TEST_HEADERS='$${TEST_HEADERS_LOCATION}'\n" + + "ADDITIONAL_LINKER_FLAGS=''\n" + + "RUN_TESTS_UNDER_VALGRIND='0'\n" + + "VALGRIND_FLAGS=''\n" + + "ENABLE_COVERAGE=False\n" + + "\" > $(location fruit_test_config.py)", +) + +py_library( + name = "fruit_test_common", + srcs = ["fruit_test_common.py", "fruit_test_config.py"], + imports = ["."], + visibility = ["//third_party/fruit/tests:__subpackages__"], +) + +load("//third_party/fruit/tests:build_defs.bzl", "fruit_py_tests") + +fruit_py_tests( + srcs = glob(["test_*.py"]), +) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b5cbfdb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,109 @@ + +include(CMakeParseArguments) + +if("${WIN32}") + # This defaults to OFF on Windows, since we don't support PCHs there. + option(FRUIT_TESTS_USE_PRECOMPILED_HEADERS "Whether to use pre-compiled headers (PCHs) in Fruit tests." OFF) + + if ("${FRUIT_TESTS_USE_PRECOMPILED_HEADERS}") + # TODO: consider adding support for PCHs on Windows (at least when using MinGW). + message(FATAL_ERROR "Please rerun CMake without -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS, precompiled headers are not supported on Windows.") + endif() +else() + option(FRUIT_TESTS_USE_PRECOMPILED_HEADERS "Whether to use pre-compiled headers (PCHs) in Fruit tests." ON) +endif() + +if("${WIN32}") + # No timeout on windows, the `timeout' executable has a different syntax. + set(TIMEOUT_COMMAND_PREFIX "") + set(TIMEOUT_COMMAND_PREFIX_STR "") +else() + set(TIMEOUT_COMMAND_PREFIX "timeout" "30") + set(TIMEOUT_COMMAND_PREFIX_STR "timeout 30") +endif() + +set(VALGRIND_FLAGS + --leak-check=full --malloc-fill=AA --track-origins=yes --read-var-info=yes --num-callers=50 --error-exitcode=1 --gen-suppressions=all --suppressions=${CMAKE_SOURCE_DIR}/tests/valgrind_suppressions.supp) +string(REPLACE ";" " " VALGRIND_FLAGS_STR "${VALGRIND_FLAGS}") + +if(NOT "${WIN32}") + function(check_all_python_tests_listed DIRNAME) + # Join the list with " ". + string(REPLACE ";" " " STR "${ARGN}") + add_test(NAME check-all-python-tests-listed-${DIRNAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND bash -c -x "pwd; for f in \$(ls test_*.py); do echo \" ${STR} \" | fgrep -q \" \$f \" || { echo \"\$f not listed.\" && exit 1; }; done") + endfunction(check_all_python_tests_listed) +endif() + +if ("${FRUIT_TESTS_USE_PRECOMPILED_HEADERS}") + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_custom_command( + OUTPUT test_common-precompiled.h.gch + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS test_common.h fruit + COMMAND bash -c "${CMAKE_CXX_COMPILER} -x c++-header ${FRUIT_COMPILE_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/../include -I${CMAKE_CURRENT_SOURCE_DIR} -I${CMAKE_CURRENT_BINARY_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/test_common.h -o test_common-precompiled.h.gch") + add_custom_target(test-common-precompiled-header ALL DEPENDS test_common-precompiled.h.gch) + # Note that the "test_common-precompiled.h" header doesn't exist, but it's ok because GCC looks for + # test_common-precompiled.h.gch first. We don't call the precompiled header test_common.h.gch so that if GCC doesn't + # find it it reports an error instead of using the normal header. + set(FRUIT_TESTONLY_CXXFLAGS "-include${CMAKE_CURRENT_BINARY_DIR}/test_common-precompiled.h") + + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(Clang|AppleClang)$") + add_custom_command( + OUTPUT test_common.pch + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS test_common.h fruit + COMMAND bash -c "${CMAKE_CXX_COMPILER} -x c++-header ${FRUIT_COMPILE_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/../include -I${CMAKE_CURRENT_SOURCE_DIR} -I${CMAKE_CURRENT_BINARY_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/test_common.h -o test_common.pch") + add_custom_target(test-common-precompiled-header ALL DEPENDS test_common.pch) + set(FRUIT_TESTONLY_CXXFLAGS "-include-pch ${CMAKE_CURRENT_BINARY_DIR}/test_common.pch") + else() + + message(ERROR "Using pre-compiled headers in tests is only supported with GCC and Clang. Please add -DFRUIT_TESTS_USE_PRECOMPILED_HEADERS=OFF to your cmake invocation and try again.") + endif() +else() + set(FRUIT_TESTONLY_CXXFLAGS "") +endif() + +if("${FRUIT_ENABLE_COVERAGE}") + set(FRUIT_ENABLE_COVERAGE_PYTHON_BOOL "True") +else() + set(FRUIT_ENABLE_COVERAGE_PYTHON_BOOL "False") +endif() + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "^(MSVC)$") + # These warnings are disabled for tests only, since they can only be produced when using fruit as a client. Also, they cannot be disabled via pragma pushes/pops, + # so we leave it up to clients to disable them if desired. + # The warning C4702 is disabled because if MSVC optimizes the call to InvokeLambdaWithInjectedArgVector::operator() when cPtr is null, it will inline + # a FRUIT_UNREACHABLE statement, which makes all statements succeeding the operator() call unreachable. + # The warning C4503 is disabled because some of the test_class_destruction.py tests suchs as "test_injector_creation_and_injection" + # produce extremely long decorator names. This has no effect on the actual results of the test. + set(FRUIT_TESTONLY_CXXFLAGS "${FRUIT_TESTONLY_CXXFLAGS} /wd4702 /wd4503") +endif() + +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/fruit_test_config.py" + CONTENT " +CXX='${CMAKE_CXX_COMPILER}' +CXX_COMPILER_NAME='${CMAKE_CXX_COMPILER_ID}' +CXX_COMPILER_VERSION='${CMAKE_CXX_COMPILER_VERSION}' +FRUIT_COMPILE_FLAGS='${FRUIT_COMPILE_FLAGS} ${FRUIT_TESTONLY_CXXFLAGS}' +ADDITIONAL_INCLUDE_DIRS='${BOOST_DIR}' +ADDITIONAL_LINKER_FLAGS='${CMAKE_EXE_LINKER_FLAGS}' +RUN_TESTS_UNDER_VALGRIND='${RUN_TESTS_UNDER_VALGRIND_FLAG}' +VALGRIND_FLAGS='${VALGRIND_FLAGS_STR}' +CMAKE_BUILD_TYPE='${CMAKE_BUILD_TYPE}' + +PATH_TO_COMPILED_FRUIT='$<TARGET_FILE_DIR:fruit>' +PATH_TO_COMPILED_FRUIT_LIB='$<TARGET_FILE:fruit>' +PATH_TO_FRUIT_STATIC_HEADERS='${CMAKE_CURRENT_SOURCE_DIR}/../include' +PATH_TO_FRUIT_GENERATED_HEADERS='${CMAKE_CURRENT_BINARY_DIR}/../include' +PATH_TO_FRUIT_TEST_HEADERS='${CMAKE_CURRENT_SOURCE_DIR}' +ENABLE_COVERAGE=${FRUIT_ENABLE_COVERAGE_PYTHON_BOOL} +") + +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pytest.ini" + CONTENT " +[pytest] +testpaths = \"${CMAKE_CURRENT_SOURCE_DIR}\" +addopts = -r a +") diff --git a/tests/build_defs.bzl b/tests/build_defs.bzl new file mode 100644 index 0000000..6cc71c2 --- /dev/null +++ b/tests/build_defs.bzl @@ -0,0 +1,22 @@ + +def fruit_py_tests(srcs, data=[]): + for filename in srcs: + native.py_test( + name = filename[:-3], + srcs = [filename], + imports = ["."], + deps = [ + "//third_party/fruit/tests:fruit_test_common", + ], + data = data + [ + "//third_party/fruit:fruit_headers", + "//third_party/fruit/tests:libfruit.so", + "//third_party/fruit/tests:test_headers_filegroup", + ], + args = [ + "-p", + "no:cacheprovider", + "-n", + "4", + ], + ) diff --git a/tests/class_construction_tracker.h b/tests/class_construction_tracker.h new file mode 100644 index 0000000..7bf1bfd --- /dev/null +++ b/tests/class_construction_tracker.h @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_CLASS_CONSTRUCTION_TRACKER_H +#define FRUIT_CLASS_CONSTRUCTION_TRACKER_H + +#include <cstddef> + +/** + * This class is useful to keep track of how many instances of a given type are created during the entire program + * execution. + * + * Example use: + * class Foo : public ConstructionTracker<Foo> { + * ... + * }; + * + * int main() { + * ... + * assert(Foo::num_objects_constructed == 3); + * } + */ +template <typename T> +struct ConstructionTracker { + static std::size_t num_objects_constructed; + + ConstructionTracker() { + ++num_objects_constructed; + } +}; + +template <typename T> +std::size_t ConstructionTracker<T>::num_objects_constructed = 0; + +#endif // FRUIT_CLASS_CONSTRUCTION_TRACKER_H diff --git a/tests/data_structures/BUILD b/tests/data_structures/BUILD new file mode 100644 index 0000000..764f877 --- /dev/null +++ b/tests/data_structures/BUILD @@ -0,0 +1,8 @@ + +licenses(["notice"]) + +load("//third_party/fruit/tests:build_defs.bzl", "fruit_py_tests") + +fruit_py_tests( + srcs = glob(["test_*.py"]), +) diff --git a/tests/data_structures/test_fixed_size_allocator.py b/tests/data_structures/test_fixed_size_allocator.py new file mode 100644 index 0000000..7f73f1c --- /dev/null +++ b/tests/data_structures/test_fixed_size_allocator.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + #define IN_FRUIT_CPP_FILE + #include <fruit/impl/data_structures/fixed_size_vector.templates.h> + + using namespace std; + using namespace fruit::impl; + + struct X { + int y; + + static int num_instances; + + X(int y) : y(y) { + Assert(std::uintptr_t(this) % alignof(X) == 0); + ++num_instances; + } + + ~X() { + --num_instances; + } + }; + + int X::num_instances = 0; + + struct Y { + static int num_instances; + + Y() { + Assert(std::uintptr_t(this) % alignof(Y) == 0); + ++num_instances; + } + + ~Y() { + --num_instances; + } + }; + + int Y::num_instances = 0; + + template <int n> + struct alignas(n) TypeWithAlignment { + TypeWithAlignment() { + Assert(std::uintptr_t(this) % n == 0); + } + }; + ''' + +def test_empty_allocator(): + source = ''' + int main() { + FixedSizeAllocator allocator; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_2_types(): + source = ''' + int main() { + { + FixedSizeAllocator::FixedSizeAllocatorData allocator_data; + allocator_data.addType(getTypeId<X>()); + allocator_data.addType(getTypeId<Y>()); + FixedSizeAllocator allocator(allocator_data); + allocator.constructObject<X>(15); + allocator.constructObject<Y>(); + Assert(X::num_instances == 1); + Assert(Y::num_instances == 1); + } + Assert(X::num_instances == 0); + Assert(Y::num_instances == 0); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_externally_allocated_only(): + source = ''' + int main() { + { + FixedSizeAllocator::FixedSizeAllocatorData allocator_data; + allocator_data.addExternallyAllocatedType(getTypeId<X>()); + FixedSizeAllocator allocator(allocator_data); + allocator.registerExternallyAllocatedObject(new X(15)); + // The allocator takes ownership. Valgrind will report an error if X is not deleted. + Assert(X::num_instances == 1); + } + Assert(X::num_instances == 0); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_mix(): + source = ''' + int main() { + { + FixedSizeAllocator::FixedSizeAllocatorData allocator_data; + allocator_data.addExternallyAllocatedType(getTypeId<X>()); + allocator_data.addType(getTypeId<Y>()); + FixedSizeAllocator allocator(allocator_data); + allocator.registerExternallyAllocatedObject(new X(15)); + // The allocator takes ownership. Valgrind will report an error if X is not deleted. + allocator.constructObject<Y>(); + Assert(X::num_instances == 1); + Assert(Y::num_instances == 1); + } + Assert(X::num_instances == 0); + Assert(Y::num_instances == 0); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_alignment(): + source = ''' + int main() { + FixedSizeAllocator::FixedSizeAllocatorData allocator_data; + allocator_data.addType(getTypeId<TypeWithAlignment<1>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<8>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<2>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<128>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<2>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<8>>()); + allocator_data.addType(getTypeId<TypeWithAlignment<1>>()); + FixedSizeAllocator allocator(allocator_data); + // TypeWithLargeAlignment::TypeWithLargeAlignment() will assert that the alignment is correct. + allocator.constructObject<TypeWithAlignment<2>>(); + allocator.constructObject<TypeWithAlignment<8>>(); + allocator.constructObject<TypeWithAlignment<1>>(); + allocator.constructObject<TypeWithAlignment<128>>(); + allocator.constructObject<TypeWithAlignment<1>>(); + allocator.constructObject<TypeWithAlignment<8>>(); + allocator.constructObject<TypeWithAlignment<2>>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_constructor(): + source = ''' + int main() { + { + FixedSizeAllocator::FixedSizeAllocatorData allocator_data; + allocator_data.addType(getTypeId<X>()); + allocator_data.addType(getTypeId<Y>()); + FixedSizeAllocator allocator(allocator_data); + allocator.constructObject<X>(15); + FixedSizeAllocator allocator2(std::move(allocator)); + allocator2.constructObject<Y>(); + Assert(X::num_instances == 1); + Assert(Y::num_instances == 1); + } + Assert(X::num_instances == 0); + Assert(Y::num_instances == 0); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/data_structures/test_fixed_size_vector.py b/tests/data_structures/test_fixed_size_vector.py new file mode 100644 index 0000000..721598a --- /dev/null +++ b/tests/data_structures/test_fixed_size_vector.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + #define IN_FRUIT_CPP_FILE + #include <fruit/impl/data_structures/fixed_size_vector.templates.h> + + using namespace std; + using namespace fruit::impl; + ''' + +def test_empty_capacity_0(): + source = ''' + int main() { + FixedSizeVector<int> v; + const FixedSizeVector<int>& const_v = v; + Assert(v.size() == 0); + Assert(v.data() == nullptr); + Assert(v.begin() == v.end()); + Assert(const_v.data() == nullptr); + Assert(const_v.begin() == const_v.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_empty_capacity_nonzero(): + source = ''' + int main() { + FixedSizeVector<int> v(15); + const FixedSizeVector<int>& const_v = v; + Assert(v.size() == 0); + Assert(v.data() != nullptr); + Assert(v.begin() == v.end()); + Assert(const_v.data() != nullptr); + Assert(const_v.begin() == const_v.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_push_back(): + source = ''' + int main() { + FixedSizeVector<int> v(2); + v.push_back(1000); + v.push_back(2000); + const FixedSizeVector<int>& const_v = v; + Assert(v.size() == 2); + Assert(v.data() == &(v[0])); + Assert(v.end() - v.begin() == 2); + Assert(&*(v.begin()) == &(v[0])); + Assert(&*(v.begin() + 1) == &(v[1])); + Assert(const_v.data() != nullptr); + Assert(const_v.begin() == &(const_v[0])); + Assert(const_v.end() - const_v.begin() == 2); + Assert(&*(const_v.begin()) == &(const_v[0])); + Assert(&*(const_v.begin() + 1) == &(const_v[1])); + Assert(v[0] == 1000); + Assert(v[1] == 2000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_2arg_constructor(): + source = ''' + int main() { + FixedSizeVector<int> v(2, 1000); + const FixedSizeVector<int>& const_v = v; + Assert(v.size() == 2); + Assert(v.data() == &(v[0])); + Assert(v.end() - v.begin() == 2); + Assert(&*(v.begin()) == &(v[0])); + Assert(&*(v.begin() + 1) == &(v[1])); + Assert(const_v.data() != nullptr); + Assert(const_v.begin() == &(const_v[0])); + Assert(const_v.end() - const_v.begin() == 2); + Assert(&*(const_v.begin()) == &(const_v[0])); + Assert(&*(const_v.begin() + 1) == &(const_v[1])); + Assert(v[0] == 1000); + Assert(v[1] == 1000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_constructor(): + source = ''' + int main() { + FixedSizeVector<int> v1(2); + v1.push_back(1000); + v1.push_back(2000); + FixedSizeVector<int> v = std::move(v1); + Assert(v.size() == 2); + Assert(v.data() == &(v[0])); + Assert(v.end() - v.begin() == 2); + Assert(&*(v.begin()) == &(v[0])); + Assert(&*(v.begin() + 1) == &(v[1])); + Assert(v[0] == 1000); + Assert(v[1] == 2000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_copy_constructor(): + source = ''' + int main() { + FixedSizeVector<int> v1(2); + v1.push_back(1000); + v1.push_back(2000); + FixedSizeVector<int> v(v1, 3); + Assert(v.size() == 2); + Assert(v.data() == &(v[0])); + Assert(v.end() - v.begin() == 2); + Assert(&*(v.begin()) == &(v[0])); + Assert(&*(v.begin() + 1) == &(v[1])); + Assert(v[0] == 1000); + Assert(v[1] == 2000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_assignment(): + source = ''' + int main() { + FixedSizeVector<int> v1(2); + v1.push_back(1000); + v1.push_back(2000); + FixedSizeVector<int> v; + v = std::move(v1); + Assert(v.size() == 2); + Assert(v.data() == &(v[0])); + Assert(v.end() - v.begin() == 2); + Assert(&*(v.begin()) == &(v[0])); + Assert(&*(v.begin() + 1) == &(v[1])); + Assert(v[0] == 1000); + Assert(v[1] == 2000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_swap(): + source = ''' + int main() { + FixedSizeVector<int> v1(2); + v1.push_back(1000); + v1.push_back(2000); + FixedSizeVector<int> v2(1); + v2.push_back(3000); + std::swap(v1, v2); + Assert(v1.size() == 1); + Assert(v1[0] == 3000); + Assert(v2.size() == 2); + Assert(v2[0] == 1000); + Assert(v2[1] == 2000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_clear(): + source = ''' + int main() { + FixedSizeVector<int> v(2); + v.push_back(1000); + v.push_back(2000); + v.clear(); + Assert(v.size() == 0); + Assert(v.data() != nullptr); + Assert(v.begin() == v.end()); + // This must not blow up, clear() must preserve the capacity. + v.push_back(1000); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/data_structures/test_semistatic_graph.py b/tests/data_structures/test_semistatic_graph.py new file mode 100644 index 0000000..e51577a --- /dev/null +++ b/tests/data_structures/test_semistatic_graph.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + #define IN_FRUIT_CPP_FILE + #include <fruit/impl/data_structures/semistatic_graph.templates.h> + + using namespace std; + using namespace fruit::impl; + + using Graph = SemistaticGraph<int, const char*>; + using node_iterator = Graph::node_iterator; + using edge_iterator = Graph::edge_iterator; + + vector<int> no_neighbors{}; + + struct SimpleNode { + int id; + const char* value; + const vector<int>* neighbors; + bool is_terminal; + + int getId() { return id; } + const char* getValue() { return value; } + bool isTerminal() { return is_terminal; } + vector<int>::const_iterator getEdgesBegin() { return neighbors->begin(); } + vector<int>::const_iterator getEdgesEnd() { return neighbors->end(); } + }; + ''' + +def test_empty(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<SimpleNode> values{}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(graph.find(2) == graph.end()); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(cgraph.find(2) == cgraph.end()); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_node_no_edges(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == false); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_node_no_edges_terminal(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<SimpleNode> values{{2, "foo", &no_neighbors, true}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == true); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == true); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_node_self_edge(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2}; + vector<SimpleNode> values{{2, "foo", &neighbors, false}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + edge_iterator itr = graph.at(2).neighborsBegin(); + (void)itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == false); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_2_nodes_one_edge(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2}; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}, {3, "bar", &neighbors, false}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == false); + edge_iterator itr = graph.at(3).neighborsBegin(); + (void)itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == false); + Assert(cgraph.find(3).getNode() == string("bar")); + Assert(cgraph.find(3).isTerminal() == false); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_3_nodes_two_edges(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2, 4}; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}, {3, "bar", &neighbors, false}, {4, "baz", &no_neighbors, true}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == false); + edge_iterator itr = graph.at(3).neighborsBegin(); + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + ++itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("baz")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == true); + Assert(graph.at(4).getNode() == string("baz")); + Assert(graph.at(4).isTerminal() == true); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == false); + Assert(cgraph.find(3).getNode() == string("bar")); + Assert(cgraph.find(3).isTerminal() == false); + Assert(cgraph.find(4).getNode() == string("baz")); + Assert(cgraph.find(4).isTerminal() == true); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_add_node(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<SimpleNode> old_values{{2, "foo", &no_neighbors, false}, {4, "baz", &no_neighbors, true}}; + + Graph old_graph(old_values.begin(), old_values.end(), memory_pool); + vector<int> neighbors = {2, 4}; + vector<SimpleNode> new_values{{3, "bar", &neighbors, false}}; + + Graph graph(old_graph, new_values.begin(), new_values.end(), memory_pool); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == false); + edge_iterator itr = graph.at(3).neighborsBegin(); + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + ++itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("baz")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == true); + Assert(graph.at(4).getNode() == string("baz")); + Assert(graph.at(4).isTerminal() == true); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(2).isTerminal() == false); + Assert(cgraph.find(3).getNode() == string("bar")); + Assert(cgraph.find(3).isTerminal() == false); + Assert(cgraph.find(4).getNode() == string("baz")); + Assert(cgraph.find(4).isTerminal() == true); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_set_terminal(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2, 4}; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}, {3, "bar", &neighbors, false}, {4, "baz", &no_neighbors, true}}; + + Graph graph(values.begin(), values.end(), memory_pool); + graph.find(3).setTerminal(); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == true); + Assert(graph.at(4).getNode() == string("baz")); + Assert(graph.at(4).isTerminal() == true); + Assert(graph.find(5) == graph.end()); + const Graph& cgraph = graph; + Assert(cgraph.find(0) == cgraph.end()); + Assert(!(cgraph.find(2) == cgraph.end())); + Assert(cgraph.find(2).getNode() == string("foo")); + Assert(cgraph.find(3).getNode() == string("bar")); + Assert(cgraph.find(4).getNode() == string("baz")); + Assert(cgraph.find(5) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_constructor(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2}; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}, {3, "bar", &neighbors, false}}; + + Graph graph1(values.begin(), values.end(), memory_pool); + Graph graph = std::move(graph1); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == false); + edge_iterator itr = graph.at(3).neighborsBegin(); + (void)itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + Assert(graph.find(5) == graph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_assignment(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2}; + vector<SimpleNode> values{{2, "foo", &no_neighbors, false}, {3, "bar", &neighbors, false}}; + + Graph graph1(values.begin(), values.end(), memory_pool); + Graph graph; + graph = std::move(graph1); + Assert(graph.find(0) == graph.end()); + Assert(!(graph.find(2) == graph.end())); + Assert(graph.at(2).getNode() == string("foo")); + Assert(graph.at(2).isTerminal() == false); + Assert(graph.at(3).getNode() == string("bar")); + Assert(graph.at(3).isTerminal() == false); + edge_iterator itr = graph.at(3).neighborsBegin(); + (void)itr; + Assert(itr.getNodeIterator(graph.begin()).getNode() == string("foo")); + Assert(itr.getNodeIterator(graph.begin()).isTerminal() == false); + Assert(graph.find(5) == graph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_incomplete_graph(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<int> neighbors = {2}; + vector<SimpleNode> values{{1, "foo", &neighbors, false}}; + + Graph graph(values.begin(), values.end(), memory_pool); + Assert(!(graph.find(1) == graph.end())); + Assert(graph.at(1).getNode() == string("foo")); + Assert(graph.at(1).isTerminal() == false); + Assert(graph.find(2) == graph.end()); + const Graph& cgraph = graph; + Assert(!(cgraph.find(1) == cgraph.end())); + Assert(cgraph.find(1).getNode() == string("foo")); + Assert(cgraph.find(1).isTerminal() == false); + Assert(cgraph.find(2) == cgraph.end()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/data_structures/test_semistatic_map.py b/tests/data_structures/test_semistatic_map.py new file mode 100644 index 0000000..c8936af --- /dev/null +++ b/tests/data_structures/test_semistatic_map.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + #define IN_FRUIT_CPP_FILE + #include <fruit/impl/data_structures/semistatic_map.templates.h> + + using namespace std; + using namespace fruit::impl; + ''' + +def test_empty(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{}; + + SemistaticMap<int, std::string> map(values.begin(), values.size(), memory_pool); + Assert(map.find(0) == nullptr); + Assert(map.find(2) == nullptr); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_elem(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{2, "foo"}}; + + SemistaticMap<int, std::string> map(values.begin(), values.size(), memory_pool); + Assert(map.find(0) == nullptr); + Assert(map.find(2) != nullptr); + Assert(map.at(2) == "foo"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_inserted_elem(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{}; + + SemistaticMap<int, std::string> old_map(values.begin(), values.size(), memory_pool); + vector<pair<int, std::string>, ArenaAllocator<pair<int, std::string>>> new_values( + {{2, "bar"}}, + ArenaAllocator<pair<int, std::string>>(memory_pool)); + SemistaticMap<int, std::string> map(old_map, std::move(new_values)); + Assert(map.find(0) == nullptr); + Assert(map.find(2) != nullptr); + Assert(map.at(2) == "bar"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_3_elem(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{1, "foo"}, {3, "bar"}, {4, "baz"}}; + + SemistaticMap<int, std::string> map(values.begin(), values.size(), memory_pool); + Assert(map.find(0) == nullptr); + Assert(map.find(1) != nullptr); + Assert(map.at(1) == "foo"); + Assert(map.find(2) == nullptr); + Assert(map.find(3) != nullptr); + Assert(map.at(3) == "bar"); + Assert(map.find(4) != nullptr); + Assert(map.at(4) == "baz"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_1_elem_2_inserted(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{1, "foo"}}; + + SemistaticMap<int, std::string> old_map(values.begin(), values.size(), memory_pool); + vector<pair<int, std::string>, ArenaAllocator<pair<int, std::string>>> new_values( + {{3, "bar"}, {4, "baz"}}, + ArenaAllocator<pair<int, std::string>>(memory_pool)); + SemistaticMap<int, std::string> map(old_map, std::move(new_values)); + Assert(map.find(0) == nullptr); + Assert(map.find(1) != nullptr); + Assert(map.at(1) == "foo"); + Assert(map.find(2) == nullptr); + Assert(map.find(3) != nullptr); + Assert(map.at(3) == "bar"); + Assert(map.find(4) != nullptr); + Assert(map.at(4) == "baz"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_3_elem_3_inserted(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{1, "1"}, {3, "3"}, {5, "5"}}; + SemistaticMap<int, std::string> old_map(values.begin(), values.size(), memory_pool); + vector<pair<int, std::string>, ArenaAllocator<pair<int, std::string>>> new_values( + {{2, "2"}, {4, "4"}, {16, "16"}}, + ArenaAllocator<pair<int, std::string>>(memory_pool)); + SemistaticMap<int, std::string> map(old_map, std::move(new_values)); + Assert(map.find(0) == nullptr); + Assert(map.find(1) != nullptr); + Assert(map.at(1) == "1"); + Assert(map.find(2) != nullptr); + Assert(map.at(2) == "2"); + Assert(map.find(3) != nullptr); + Assert(map.at(3) == "3"); + Assert(map.find(4) != nullptr); + Assert(map.at(4) == "4"); + Assert(map.find(5) != nullptr); + Assert(map.at(5) == "5"); + Assert(map.find(6) == nullptr); + Assert(map.find(16) != nullptr); + Assert(map.at(16) == "16"); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_constructor(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{1, "foo"}, {3, "bar"}, {4, "baz"}}; + SemistaticMap<int, std::string> map1(values.begin(), values.size(), memory_pool); + SemistaticMap<int, std::string> map = std::move(map1); + Assert(map.find(0) == nullptr); + Assert(map.find(1) != nullptr); + Assert(map.at(1) == "foo"); + Assert(map.find(2) == nullptr); + Assert(map.find(3) != nullptr); + Assert(map.at(3) == "bar"); + Assert(map.find(4) != nullptr); + Assert(map.at(4) == "baz"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_move_assignment(): + source = ''' + int main() { + MemoryPool memory_pool; + vector<pair<int, std::string>> values{{1, "foo"}, {3, "bar"}, {4, "baz"}}; + SemistaticMap<int, std::string> map1(values.begin(), values.size(), memory_pool); + SemistaticMap<int, std::string> map; + map = std::move(map1); + Assert(map.find(0) == nullptr); + Assert(map.find(1) != nullptr); + Assert(map.at(1) == "foo"); + Assert(map.find(2) == nullptr); + Assert(map.find(3) != nullptr); + Assert(map.at(3) == "bar"); + Assert(map.find(4) != nullptr); + Assert(map.at(4) == "baz"); + Assert(map.find(5) == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/fruit_test_common.py b/tests/fruit_test_common.py new file mode 100644 index 0000000..ac9b3bd --- /dev/null +++ b/tests/fruit_test_common.py @@ -0,0 +1,580 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import os +import tempfile +import unittest +import textwrap +import re +import sys + +import itertools + +import subprocess + +import pytest + +from fruit_test_config import * + +run_under_valgrind = RUN_TESTS_UNDER_VALGRIND.lower() not in ('false', 'off', 'no', '0', '') + +def pretty_print_command(command): + return ' '.join('"' + x + '"' for x in command) + +class CommandFailedException(Exception): + def __init__(self, command, stdout, stderr, error_code): + self.command = command + self.stdout = stdout + self.stderr = stderr + self.error_code = error_code + + def __str__(self): + return textwrap.dedent('''\ + Ran command: {command} + Exit code {error_code} + Stdout: + {stdout} + + Stderr: + {stderr} + ''').format(command=pretty_print_command(self.command), error_code=self.error_code, stdout=self.stdout, stderr=self.stderr) + +def run_command(executable, args=[], modify_env=lambda env: env): + command = [executable] + args + modified_env = modify_env(os.environ) + print('Executing command:', pretty_print_command(command)) + try: + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=modified_env) + (stdout, stderr) = p.communicate() + except Exception as e: + raise Exception("While executing: %s" % command) + if p.returncode != 0: + raise CommandFailedException(command, stdout, stderr, p.returncode) + print('Execution successful.') + print('stdout:') + print(stdout) + print('') + print('stderr:') + print(stderr) + print('') + return (stdout, stderr) + +def run_compiled_executable(executable): + if run_under_valgrind: + args = VALGRIND_FLAGS.split() + [executable] + run_command('valgrind', args = args, modify_env = modify_env_for_compiled_executables) + else: + run_command(executable, modify_env = modify_env_for_compiled_executables) + +class CompilationFailedException(Exception): + def __init__(self, command, error_message): + self.command = command + self.error_message = error_message + + def __str__(self): + return textwrap.dedent('''\ + Ran command: {command} + Error message: + {error_message} + ''').format(command=pretty_print_command(self.command), error_message=self.error_message) + +class PosixCompiler: + def __init__(self): + self.executable = CXX + self.name = CXX_COMPILER_NAME + + def compile_discarding_output(self, source, include_dirs, args=[]): + try: + args = args + ['-c', source, '-o', os.path.devnull] + self._compile(include_dirs, args=args) + except CommandFailedException as e: + raise CompilationFailedException(e.command, e.stderr) + + def compile_and_link(self, source, include_dirs, output_file_name, args=[]): + self._compile( + include_dirs, + args = ( + [source] + + ADDITIONAL_LINKER_FLAGS.split() + + args + + ['-o', output_file_name] + )) + + def _compile(self, include_dirs, args): + include_flags = ['-I%s' % include_dir for include_dir in include_dirs] + args = ( + FRUIT_COMPILE_FLAGS.split() + + include_flags + + ['-g0', '-Werror'] + + args + ) + run_command(self.executable, args) + + def get_disable_deprecation_warning_flags(self): + return ['-Wno-deprecated-declarations'] + + def get_disable_all_warnings_flags(self): + return ['-Wno-error'] + +class MsvcCompiler: + def __init__(self): + self.executable = CXX + self.name = CXX_COMPILER_NAME + + def compile_discarding_output(self, source, include_dirs, args=[]): + try: + args = args + ['/c', source] + self._compile(include_dirs, args = args) + except CommandFailedException as e: + # Note that we use stdout here, unlike above. MSVC reports compilation warnings and errors on stdout. + raise CompilationFailedException(e.command, e.stdout) + + def compile_and_link(self, source, include_dirs, output_file_name, args=[]): + self._compile( + include_dirs, + args = ( + [source] + + ADDITIONAL_LINKER_FLAGS.split() + + args + + ['/Fe' + output_file_name] + )) + + def _compile(self, include_dirs, args): + include_flags = ['-I%s' % include_dir for include_dir in include_dirs] + args = ( + FRUIT_COMPILE_FLAGS.split() + + include_flags + + ['/WX'] + + args + ) + run_command(self.executable, args) + + def get_disable_deprecation_warning_flags(self): + return ['/wd4996'] + + def get_disable_all_warnings_flags(self): + return ['/WX:NO'] + +if CXX_COMPILER_NAME == 'MSVC': + compiler = MsvcCompiler() + if PATH_TO_COMPILED_FRUIT_LIB.endswith('.dll'): + path_to_fruit_lib = PATH_TO_COMPILED_FRUIT_LIB[:-4] + '.lib' + else: + path_to_fruit_lib = PATH_TO_COMPILED_FRUIT_LIB + fruit_tests_linker_flags = [path_to_fruit_lib] + fruit_error_message_extraction_regex = 'error C2338: (.*)' +else: + compiler = PosixCompiler() + fruit_tests_linker_flags = [ + '-lfruit', + '-L' + PATH_TO_COMPILED_FRUIT, + '-Wl,-rpath,' + PATH_TO_COMPILED_FRUIT, + ] + fruit_error_message_extraction_regex = 'static.assert(.*)' + +fruit_tests_include_dirs = ADDITIONAL_INCLUDE_DIRS.splitlines() + [ + PATH_TO_FRUIT_TEST_HEADERS, + PATH_TO_FRUIT_STATIC_HEADERS, + PATH_TO_FRUIT_GENERATED_HEADERS, +] + +_assert_helper = unittest.TestCase() + +def modify_env_for_compiled_executables(env): + env = env.copy() + path_to_fruit_lib_dir = os.path.dirname(PATH_TO_COMPILED_FRUIT_LIB) + print('PATH_TO_COMPILED_FRUIT_LIB:', PATH_TO_COMPILED_FRUIT_LIB) + print('Adding directory to PATH:', path_to_fruit_lib_dir) + env["PATH"] += os.pathsep + path_to_fruit_lib_dir + return env + +def _create_temporary_file(file_content, file_name_suffix=''): + file_descriptor, file_name = tempfile.mkstemp(text=True, suffix=file_name_suffix) + file = os.fdopen(file_descriptor, mode='w') + file.write(file_content) + file.close() + return file_name + +def _cap_to_lines(s, n): + lines = s.splitlines() + if len(lines) <= n: + return s + else: + return '\n'.join(lines[0:n] + ['...']) + +def _replace_using_test_params(s, test_params): + for var_name, value in test_params.items(): + if isinstance(value, str): + s = re.sub(r'\b%s\b' % var_name, value, s) + return s + +def _construct_final_source_code(setup_source_code, source_code, test_params): + setup_source_code = textwrap.dedent(setup_source_code) + source_code = textwrap.dedent(source_code) + source_code = _replace_using_test_params(source_code, test_params) + return setup_source_code + source_code + +def try_remove_temporary_file(filename): + try: + os.remove(filename) + except: + # When running Fruit tests on Windows using Appveyor, the remove command fails for temporary files sometimes. + # This shouldn't cause the tests to fail, so we ignore the exception and go ahead. + pass + +def expect_compile_error_helper( + check_error_fun, + setup_source_code, + source_code, + test_params={}, + ignore_deprecation_warnings=False, + ignore_warnings=False): + source_code = _construct_final_source_code(setup_source_code, source_code, test_params) + + source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') + + try: + args = [] + if ignore_deprecation_warnings: + args += compiler.get_disable_deprecation_warning_flags() + if ignore_warnings: + args += compiler.get_disable_all_warnings_flags() + if ENABLE_COVERAGE: + # When collecting coverage these arguments are enabled by default; however we must disable them in tests + # expected to fail at compile-time because GCC would otherwise fail with an error like: + # /tmp/tmp4m22cey7.cpp:1:0: error: cannot open /dev/null.gcno + args += ['-fno-profile-arcs', '-fno-test-coverage'] + compiler.compile_discarding_output( + source=source_file_name, + include_dirs=fruit_tests_include_dirs, + args=args) + raise Exception('The test should have failed to compile, but it compiled successfully') + except CompilationFailedException as e1: + e = e1 + + error_message = e.error_message + error_message_lines = error_message.splitlines() + # Different compilers output a different number of spaces when pretty-printing types. + # When using libc++, sometimes std::foo identifiers are reported as std::__1::foo. + normalized_error_message = error_message.replace(' ', '').replace('std::__1::', 'std::') + normalized_error_message_lines = normalized_error_message.splitlines() + error_message_head = _cap_to_lines(error_message, 40) + + check_error_fun(e, error_message_lines, error_message_head, normalized_error_message_lines) + + try_remove_temporary_file(source_file_name) + +def expect_generic_compile_error(expected_error_regex, setup_source_code, source_code, test_params={}): + """ + Tests that the given source produces the expected error during compilation. + + :param expected_fruit_error_regex: A regex used to match the Fruit error type, + e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'. + Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). + :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description, + e.g. 'No explicit binding was found for C, and C is an abstract class'. + :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's + *not* subject to test_params, unlike source_code. + :param source_code: The second part of the source code. Any identifiers will be replaced using test_params + (where a replacement is defined). This will be dedented. + :param test_params: A dict containing the definition of some identifiers. Each identifier in + expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition + was provided). + """ + + expected_error_regex = _replace_using_test_params(expected_error_regex, test_params) + expected_error_regex = expected_error_regex.replace(' ', '') + + def check_error(e, error_message_lines, error_message_head, normalized_error_message_lines): + for line in normalized_error_message_lines: + if re.search(expected_error_regex, line): + return + raise Exception(textwrap.dedent('''\ + Expected error {expected_error} but the compiler output did not contain that. + Compiler command line: {compiler_command} + Error message was: + {error_message} + ''').format(expected_error = expected_error_regex, compiler_command=e.command, error_message = error_message_head)) + + expect_compile_error_helper(check_error, setup_source_code, source_code, test_params) + + +def expect_compile_error( + expected_fruit_error_regex, + expected_fruit_error_desc_regex, + setup_source_code, + source_code, + test_params={}, + ignore_deprecation_warnings=False, + ignore_warnings=False, + disable_error_line_number_check=False): + """ + Tests that the given source produces the expected error during compilation. + + :param expected_fruit_error_regex: A regex used to match the Fruit error type, + e.g. 'NoBindingFoundForAbstractClassError<ScalerImpl>'. + Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). + :param expected_fruit_error_desc_regex: A regex used to match the Fruit error description, + e.g. 'No explicit binding was found for C, and C is an abstract class'. + :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's + *not* subject to test_params, unlike source_code. + :param source_code: The second part of the source code. Any identifiers will be replaced using test_params + (where a replacement is defined). This will be dedented. + :param test_params: A dict containing the definition of some identifiers. Each identifier in + expected_fruit_error_regex and source_code will be replaced (textually) with its definition (if a definition + was provided). + :param ignore_deprecation_warnings: A boolean. If True, deprecation warnings will be ignored. + :param ignore_warnings: A boolean. If True, all warnings will be ignored. + :param disable_error_line_number_check: A boolean. If True, the test will not fail if there are other diagnostic + lines before the expected error. + """ + if '\n' in expected_fruit_error_regex: + raise Exception('expected_fruit_error_regex should not contain newlines') + if '\n' in expected_fruit_error_desc_regex: + raise Exception('expected_fruit_error_desc_regex should not contain newlines') + + expected_fruit_error_regex = _replace_using_test_params(expected_fruit_error_regex, test_params) + expected_fruit_error_regex = expected_fruit_error_regex.replace(' ', '') + + def check_error(e, error_message_lines, error_message_head, normalized_error_message_lines): + for line_number, line in enumerate(normalized_error_message_lines): + match = re.search('fruit::impl::(.*Error<.*>)', line) + if match: + actual_fruit_error_line_number = line_number + actual_fruit_error = match.groups()[0] + if CXX_COMPILER_NAME == 'MSVC': + # MSVC errors are of the form: + # + # C:\Path\To\header\foo.h(59): note: see reference to class template instantiation 'fruit::impl::NoBindingFoundError<fruit::Annotated<Annotation,U>>' being compiled + # with + # [ + # Annotation=Annotation1, + # U=std::function<std::unique_ptr<ScalerImpl,std::default_delete<ScalerImpl>> (double)> + # ] + # + # So we need to parse the following few lines and use them to replace the placeholder types in the Fruit error type. + try: + replacement_lines = [] + if normalized_error_message_lines[line_number + 1].strip() == 'with': + for line in itertools.islice(normalized_error_message_lines, line_number + 3, None): + line = line.strip() + if line == ']': + break + if line.endswith(','): + line = line[:-1] + replacement_lines.append(line) + for replacement_line in replacement_lines: + match = re.search('([A-Za-z0-9_-]*)=(.*)', replacement_line) + if not match: + raise Exception('Failed to parse replacement line: %s' % replacement_line) from e + (type_variable, type_expression) = match.groups() + actual_fruit_error = re.sub(r'\b' + type_variable + r'\b', type_expression, actual_fruit_error) + except Exception: + raise Exception('Failed to parse MSVC template type arguments') + break + else: + raise Exception(textwrap.dedent('''\ + Expected error {expected_error} but the compiler output did not contain user-facing Fruit errors. + Compiler command line: {compiler_command} + Error message was: + {error_message} + ''').format(expected_error = expected_fruit_error_regex, compiler_command = e.command, error_message = error_message_head)) + + for line_number, line in enumerate(error_message_lines): + match = re.search(fruit_error_message_extraction_regex, line) + if match: + actual_static_assert_error_line_number = line_number + actual_static_assert_error = match.groups()[0] + break + else: + raise Exception(textwrap.dedent('''\ + Expected error {expected_error} but the compiler output did not contain static_assert errors. + Compiler command line: {compiler_command} + Error message was: + {error_message} + ''').format(expected_error = expected_fruit_error_regex, compiler_command=e.command, error_message = error_message_head)) + + try: + regex_search_result = re.search(expected_fruit_error_regex, actual_fruit_error) + except Exception as e: + raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_regex) from e + if not regex_search_result: + raise Exception(textwrap.dedent('''\ + The compilation failed as expected, but with a different error type. + Expected Fruit error type: {expected_fruit_error_regex} + Error type was: {actual_fruit_error} + Expected static assert error: {expected_fruit_error_desc_regex} + Static assert was: {actual_static_assert_error} + Error message was: + {error_message} + '''.format( + expected_fruit_error_regex = expected_fruit_error_regex, + actual_fruit_error = actual_fruit_error, + expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, + actual_static_assert_error = actual_static_assert_error, + error_message = error_message_head))) + try: + regex_search_result = re.search(expected_fruit_error_desc_regex, actual_static_assert_error) + except Exception as e: + raise Exception('re.search() failed for regex \'%s\'' % expected_fruit_error_desc_regex) from e + if not regex_search_result: + raise Exception(textwrap.dedent('''\ + The compilation failed as expected, but with a different error message. + Expected Fruit error type: {expected_fruit_error_regex} + Error type was: {actual_fruit_error} + Expected static assert error: {expected_fruit_error_desc_regex} + Static assert was: {actual_static_assert_error} + Error message: + {error_message} + '''.format( + expected_fruit_error_regex = expected_fruit_error_regex, + actual_fruit_error = actual_fruit_error, + expected_fruit_error_desc_regex = expected_fruit_error_desc_regex, + actual_static_assert_error = actual_static_assert_error, + error_message = error_message_head))) + + # 6 is just a constant that works for both g++ (<=6.0.0 at least) and clang++ (<=4.0.0 at least). + # It might need to be changed. + if not disable_error_line_number_check and (actual_fruit_error_line_number > 6 or actual_static_assert_error_line_number > 6): + raise Exception(textwrap.dedent('''\ + The compilation failed with the expected message, but the error message contained too many lines before the relevant ones. + The error type was reported on line {actual_fruit_error_line_number} of the message (should be <=6). + The static assert was reported on line {actual_static_assert_error_line_number} of the message (should be <=6). + Error message: + {error_message} + '''.format( + actual_fruit_error_line_number = actual_fruit_error_line_number, + actual_static_assert_error_line_number = actual_static_assert_error_line_number, + error_message = error_message_head))) + + for line in error_message_lines[:max(actual_fruit_error_line_number, actual_static_assert_error_line_number)]: + if re.search('fruit::impl::meta', line): + raise Exception( + 'The compilation failed with the expected message, but the error message contained some metaprogramming types in the output (besides Error). Error message:\n%s' + error_message_head) + + expect_compile_error_helper(check_error, setup_source_code, source_code, test_params, ignore_deprecation_warnings, ignore_warnings) + + +def expect_runtime_error( + expected_error_regex, + setup_source_code, + source_code, + test_params={}, + ignore_deprecation_warnings=False): + """ + Tests that the given source (compiles successfully and) produces the expected error at runtime. + + :param expected_error_regex: A regex used to match the content of stderr. + Any identifiers contained in the regex will be replaced using test_params (where a replacement is defined). + :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's + *not* subject to test_params, unlike source_code. + :param source_code: The second part of the source code. Any identifiers will be replaced using test_params + (where a replacement is defined). This will be dedented. + :param test_params: A dict containing the definition of some identifiers. Each identifier in + expected_error_regex and source_code will be replaced (textually) with its definition (if a definition + was provided). + """ + expected_error_regex = _replace_using_test_params(expected_error_regex, test_params) + source_code = _construct_final_source_code(setup_source_code, source_code, test_params) + + source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') + executable_suffix = {'posix': '', 'nt': '.exe'}[os.name] + output_file_name = _create_temporary_file('', executable_suffix) + + args = fruit_tests_linker_flags.copy() + if ignore_deprecation_warnings: + args += compiler.get_disable_deprecation_warning_flags() + compiler.compile_and_link( + source=source_file_name, + include_dirs=fruit_tests_include_dirs, + output_file_name=output_file_name, + args=args) + + try: + run_compiled_executable(output_file_name) + raise Exception('The test should have failed at runtime, but it ran successfully') + except CommandFailedException as e1: + e = e1 + + stderr = e.stderr + stderr_head = _cap_to_lines(stderr, 40) + + if '\n' in expected_error_regex: + regex_flags = re.MULTILINE + else: + regex_flags = 0 + + try: + regex_search_result = re.search(expected_error_regex, stderr, flags=regex_flags) + except Exception as e: + raise Exception('re.search() failed for regex \'%s\'' % expected_error_regex) from e + if not regex_search_result: + raise Exception(textwrap.dedent('''\ + The test failed as expected, but with a different message. + Expected: {expected_error_regex} + Was: + {stderr} + '''.format(expected_error_regex = expected_error_regex, stderr = stderr_head))) + + # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure. + if not ENABLE_COVERAGE: + try_remove_temporary_file(source_file_name) + try_remove_temporary_file(output_file_name) + + +def expect_success(setup_source_code, source_code, test_params={}, ignore_deprecation_warnings=False): + """ + Tests that the given source compiles and runs successfully. + + :param setup_source_code: The first part of the source code. This is dedented separately from source_code and it's + *not* subject to test_params, unlike source_code. + :param source_code: The second part of the source code. Any identifiers will be replaced using test_params + (where a replacement is defined). This will be dedented. + :param test_params: A dict containing the definition of some identifiers. Each identifier in + source_code will be replaced (textually) with its definition (if a definition was provided). + """ + source_code = _construct_final_source_code(setup_source_code, source_code, test_params) + + if 'main(' not in source_code: + source_code += textwrap.dedent(''' + int main() { + } + ''') + + source_file_name = _create_temporary_file(source_code, file_name_suffix='.cpp') + executable_suffix = {'posix': '', 'nt': '.exe'}[os.name] + output_file_name = _create_temporary_file('', executable_suffix) + + args = fruit_tests_linker_flags.copy() + if ignore_deprecation_warnings: + args += compiler.get_disable_deprecation_warning_flags() + compiler.compile_and_link( + source=source_file_name, + include_dirs=fruit_tests_include_dirs, + output_file_name=output_file_name, + args=args) + + run_compiled_executable(output_file_name) + + # Note that we don't delete the temporary files if the test failed. This is intentional, keeping them around helps debugging the failure. + if not ENABLE_COVERAGE: + try_remove_temporary_file(source_file_name) + try_remove_temporary_file(output_file_name) + + +# Note: this is not the main function of this file, it's meant to be used as main function from test_*.py files. +def main(file): + code = pytest.main(args = sys.argv + [os.path.realpath(file)]) + exit(code) diff --git a/tests/meta/BUILD b/tests/meta/BUILD new file mode 100644 index 0000000..1623eed --- /dev/null +++ b/tests/meta/BUILD @@ -0,0 +1,15 @@ + +licenses(["notice"]) + +load("//third_party/fruit/tests:build_defs.bzl", "fruit_py_tests") + +filegroup( + name = "additional_test_headers_filegroup", + srcs = ["common.h"], + visibility = ["//third_party/fruit/tests:__subpackages__"], +) + +fruit_py_tests( + srcs = glob(["test_*.py"]), + data = [":additional_test_headers_filegroup"], +) diff --git a/tests/meta/common.h b/tests/meta/common.h new file mode 100644 index 0000000..f2e3078 --- /dev/null +++ b/tests/meta/common.h @@ -0,0 +1,102 @@ + +#ifndef FRUIT_TESTS_META_COMMON_H +#define FRUIT_TESTS_META_COMMON_H + +#define FRUIT_IN_META_TEST + +#include <fruit/impl/injection_debug_errors.h> +#include <fruit/impl/injection_errors.h> +#include <fruit/impl/meta/basics.h> +#include <fruit/impl/meta/errors.h> +#include <fruit/impl/meta/immutable_map.h> +#include <fruit/impl/meta/set.h> +#include <fruit/impl/meta/vector.h> + +using namespace std; +using namespace fruit; +using namespace fruit::impl; +using namespace fruit::impl::meta; + +template <typename T, typename U> +struct DifferentError { + static_assert(AlwaysFalse<T>::value, "T and U are different, but should have been equal/equivalent."); +}; + +template <typename T, typename U> +struct SameError { + static_assert(AlwaysFalse<T>::value, "T and U are equal/equivalent but should have been different."); +}; + +struct DifferentErrorTag { + template <typename T, typename U> + using apply = DifferentError<T, U>; +}; + +struct SameErrorTag { + template <typename T, typename U> + using apply = SameError<T, U>; +}; + +template <typename... Types> +using ToSet = Vector<Types...>; + +struct ConstructErrorWithoutUnwrapping { + template <typename ErrorTag, typename... Args> + struct apply { + using type = ConstructError(ErrorTag, Type<Args>...); + }; +}; + +using True = Bool<true>; +using False = Bool<false>; + +#undef Assert + +#define Assert(...) static_assert(Eval<__VA_ARGS__>::value, "") +#define AssertNot(...) Assert(Not(__VA_ARGS__)) +#define AssertSame(...) \ + static_assert( \ + true || \ + sizeof( \ + typename CheckIfError<Eval<If(IsSame(__VA_ARGS__), True, \ + ConstructErrorWithoutUnwrapping(DifferentErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertSameType(...) \ + static_assert( \ + true || sizeof(typename CheckIfError< \ + Eval<If(IsSame(__VA_ARGS__), True, ConstructError(DifferentErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertSameSet(...) \ + static_assert( \ + true || sizeof(typename CheckIfError< \ + Eval<If(IsSameSet(__VA_ARGS__), True, ConstructError(DifferentErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertSameProof(...) \ + static_assert(true || sizeof(typename CheckIfError<Eval<If(IsProofTreeEqualTo(__VA_ARGS__), True, \ + ConstructError(DifferentErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertSameForest(...) \ + static_assert(true || sizeof(typename CheckIfError<Eval<CheckForestEqualTo(__VA_ARGS__)>>::type), "") +#define AssertNotSame(...) \ + static_assert( \ + true || \ + sizeof(typename CheckIfError<Eval<If(Not(IsSame(__VA_ARGS__)), True, \ + ConstructErrorWithoutUnwrapping(SameErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertNotSameType(...) \ + static_assert( \ + true || sizeof(typename CheckIfError< \ + Eval<If(Not(IsSame(__VA_ARGS__)), True, ConstructError(SameErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertNotSameProof(...) \ + static_assert(true || sizeof(typename CheckIfError<Eval<If(Not(IsProofTreeEqualTo(__VA_ARGS__)), True, \ + ConstructError(SameErrorTag, __VA_ARGS__))>>::type), \ + "") +#define AssertNotSameForest(...) \ + static_assert( \ + true || \ + sizeof(typename CheckIfError< \ + Eval<If(Not(IsForestEqualTo(__VA_ARGS__)), True, ConstructError(SameErrorTag, __VA_ARGS__))>>::type), \ + "") + +#endif // FRUIT_TESTS_META_COMMON_H diff --git a/tests/meta/test_algos.py b/tests/meta/test_algos.py new file mode 100644 index 0000000..5ac1ced --- /dev/null +++ b/tests/meta/test_algos.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/algos.h> + ''' + +def test_HasDuplicates(): + source = ''' + int main() { + AssertNot(HasDuplicates(Vector<>)); + AssertNot(HasDuplicates(Vector<Int<0>>)); + AssertNot(HasDuplicates(Vector<Int<0>, Int<1>>)); + Assert(HasDuplicates(Vector<Int<0>, Int<0>>)); + Assert(HasDuplicates(Vector<Int<2>, Int<0>, Int<1>, Int<0>, Int<3>>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_basics.py b/tests/meta/test_basics.py new file mode 100644 index 0000000..1508eaf --- /dev/null +++ b/tests/meta/test_basics.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/vector.h> + + #include <vector> + + struct A {}; + struct B {}; + struct C {}; + + struct Select1st { + template <typename T, typename U> + struct apply { + using type = T; + }; + }; + + struct Select2nd { + template <typename T, typename U> + struct apply { + using type = U; + }; + }; + ''' + +def test_ImplicitCall(): + source = ''' + int main() { + AssertSameType(Type<int>, Id<Select1st(Type<int>, Type<float>)>); + AssertSameType(Type<float>, Id<Select2nd(Type<int>, Type<float>)>); + AssertSameType(Type<int>, Id<Select1st(Type<int>, Type<float>)>); + AssertSameType(Type<float>, Id<Select2nd(Type<int>, Type<float>)>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_Call(): + source = ''' + int main() { + AssertSameType(Type<int>, Id<Call(Select1st, Type<int>, Type<float>)>); + AssertSameType(Type<float>, Id<Call(Select2nd, Type<int>, Type<float>)>); + AssertSameType(Type<int>, Id<Call(Select1st, Type<int>, Type<float>)>); + AssertSameType(Type<float>, Id<Call(Select2nd, Type<int>, Type<float>)>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_DeferArgs(): + source = ''' + int main() { + AssertSameType(Type<int>, Id<Call(Id<Call(Id<DeferArgs(Select1st)>, Type<int>)>, Type<float>)>); + AssertSameType(Type<float>, Id<Call(Id<Call(Id<DeferArgs(Select2nd)>, Type<int>)>, Type<float>)>); + AssertSameType(Type<int>, Id<Call(Id<Call(Id<DeferArgs(Select1st)>, Type<int>)>, Type<float>)>); + AssertSameType(Type<float>, Id<Call(Id<Call(Id<DeferArgs(Select2nd)>, Type<int>)>, Type<float>)>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_graph.py b/tests/meta/test_graph.py new file mode 100644 index 0000000..91eb36f --- /dev/null +++ b/tests/meta/test_graph.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/graph.h> + + struct A1 {}; + struct B1 {}; + struct C1 {}; + struct D1 {}; + struct E1 {}; + + using A = Type<A1>; + using B = Type<B1>; + using C = Type<C1>; + using D = Type<D1>; + using E = Type<E1>; + ''' + +def test_GraphFindLoop(): + source = ''' + int main() { + // A -> B, D + // C -> D + AssertSameType(Id<GraphFindLoop(Vector<Pair<A, Vector<B, D>>, Pair<C, Vector<D>>, Pair<B, Vector<>>>)>, None); + + // A -> B + // B -> B + // C -> B + AssertSameType(Id<GraphFindLoop(Vector<Pair<A, Vector<B>>, Pair<B, Vector<B>>, Pair<C, Vector<B>>>)>, Vector<B>); + + // A -> D, B + // B -> C + // C -> A + // The order in the result here *does* matter, but rotations of the correct (A,B,C) sequence are also ok. + // Fix this test as appropriate. + AssertSameType(Id<GraphFindLoop(Vector<Pair<A, Vector<D, B>>, Pair<B, Vector<C>>, Pair<C, Vector<A>>>)>, Vector<B, C, A>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_list.py b/tests/meta/test_list.py new file mode 100644 index 0000000..b930838 --- /dev/null +++ b/tests/meta/test_list.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/list.h> + #include <fruit/impl/meta/metaprogramming.h> + ''' + +def test_FoldList(): + source = ''' + struct Helper { + template <typename CurrentResult, typename N> + struct apply { + using type = Int<(CurrentResult::value + 1) * N::value>; + }; + }; + + int main() { + AssertSameType(Id<FoldList(EmptyList, Helper, Int<4>)>, Int<4>); + AssertSameType(Id<FoldList(Cons<Int<2>, EmptyList>, Helper, Int<4>)>, Int<10>); + AssertSameType(Id<FoldList(Cons<Int<3>, Cons<Int<2>, EmptyList>>, Helper, Int<4>)>, Int<32>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_map.py b/tests/meta/test_map.py new file mode 100644 index 0000000..e943b9e --- /dev/null +++ b/tests/meta/test_map.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/map.h> + #include <fruit/impl/meta/metaprogramming.h> + ''' + +def test_FindInMap(): + source = ''' + int main() { + AssertSameType(Id<FindInMap(ToSet<>, Int<2>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>>, Int<7>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>>, Int<2>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<2>, Int<1>>>, Int<2>)>, Int<1>); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<7>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<2>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<20>)>, None); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<1>)>, Int<2>); + AssertSameType(Id<FindInMap(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<10>)>, Int<20>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_MapContainsKey(): + source = ''' + int main() { + AssertNot(MapContainsKey(ToSet<>, Int<2>)); + AssertNot(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>>, Int<7>)); + AssertNot(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>>, Int<2>)); + Assert(MapContainsKey(ToSet<Pair<Int<2>, Int<1>>>, Int<2>)); + AssertNot(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<7>)); + AssertNot(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<2>)); + AssertNot(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<20>)); + Assert(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<1>)); + Assert(MapContainsKey(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>, Int<10>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_GetMapKeys(): + source = ''' + int main() { + AssertSameSet(Id<GetMapKeys(ToSet<>)>, ToSet<>); + AssertSameSet(Id<GetMapKeys(ToSet<Pair<Int<1>, Int<2>>>)>, ToSet<Int<1>>); + AssertSameSet(Id<GetMapKeys(ToSet<Pair<Int<1>, Int<2>>, Pair<Int<10>, Int<20>>>)>, ToSet<Int<1>, Int<10>>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_meta_component.py b/tests/meta/test_meta_component.py new file mode 100644 index 0000000..b8910dd --- /dev/null +++ b/tests/meta/test_meta_component.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/component.h> + + struct A1 {}; + struct B1 {}; + + using A = Type<A1>; + using B = Type<B1>; + + using AssistedA = Type<Assisted<A1>>; + using AssistedB = Type<Assisted<B1>>; + ''' + +def test_NumAssisted(): + source = ''' + int main() { + AssertSame(Int<0>, NumAssisted(Vector<>)); + AssertSame(Int<0>, NumAssisted(Vector<A>)); + AssertSame(Int<1>, NumAssisted(Vector<AssistedA>)); + AssertSame(Int<0>, NumAssisted(Vector<A, B>)); + AssertSame(Int<1>, NumAssisted(Vector<AssistedA, B>)); + AssertSame(Int<1>, NumAssisted(Vector<A, AssistedB>)); + AssertSame(Int<2>, NumAssisted(Vector<AssistedA, AssistedB>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_NumAssistedBefore(): + source = ''' + int main() { + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<A>)); + AssertSame(Int<0>, NumAssistedBefore(Int<1>, Vector<A>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<AssistedA>)); + AssertSame(Int<1>, NumAssistedBefore(Int<1>, Vector<AssistedA>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<A, B>)); + AssertSame(Int<0>, NumAssistedBefore(Int<1>, Vector<A, B>)); + AssertSame(Int<0>, NumAssistedBefore(Int<2>, Vector<A, B>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<AssistedA, B>)); + AssertSame(Int<1>, NumAssistedBefore(Int<1>, Vector<AssistedA, B>)); + AssertSame(Int<1>, NumAssistedBefore(Int<2>, Vector<AssistedA, B>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<A, AssistedB>)); + AssertSame(Int<0>, NumAssistedBefore(Int<1>, Vector<A, AssistedB>)); + AssertSame(Int<1>, NumAssistedBefore(Int<2>, Vector<A, AssistedB>)); + + AssertSame(Int<0>, NumAssistedBefore(Int<0>, Vector<AssistedA, AssistedB>)); + AssertSame(Int<1>, NumAssistedBefore(Int<1>, Vector<AssistedA, AssistedB>)); + AssertSame(Int<2>, NumAssistedBefore(Int<2>, Vector<AssistedA, AssistedB>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_metaprogramming.py b/tests/meta/test_metaprogramming.py new file mode 100644 index 0000000..6e0bfab --- /dev/null +++ b/tests/meta/test_metaprogramming.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/component.h> + + struct A1 {}; + struct B1 {}; + + using A = Type<A1>; + using B = Type<B1>; + ''' + +def test_GetNthType(): + source = ''' + int main() { + AssertSameType(A, GetNthType(Int<0>, Vector<A>)); + + AssertSameType(A, GetNthType(Int<0>, Vector<A, B>)); + AssertSameType(B, GetNthType(Int<1>, Vector<A, B>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_proof_trees.py b/tests/meta/test_proof_trees.py new file mode 100644 index 0000000..380b64a --- /dev/null +++ b/tests/meta/test_proof_trees.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/metaprogramming.h> + #include <fruit/impl/meta/proof_trees.h> + #include <fruit/impl/meta/proof_tree_comparison.h> + + #include <vector> + + struct A1 {}; + struct B1 {}; + struct C1 {}; + struct D1 {}; + struct X1 {}; + struct Y1 {}; + + using A = Type<A1>; + using B = Type<B1>; + using C = Type<C1>; + using D = Type<D1>; + using X = Type<X1>; + using Y = Type<Y1>; + + using Proof1 = Pair<X, ToSet<A, B>>; + using Proof1b = Pair<X, ToSet<B, A>>; + using Proof2 = Pair<Y, ToSet<B, C>>; + ''' + +def test_IsProofTreeEqualTo(): + source = ''' + int main() { + AssertNotSameProof(Pair<X, ToSet<A>>, Pair<X, ToSet<B>>); + AssertNotSameProof(Proof1, Proof2); + AssertSameProof(Proof1, Proof1b); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsForestEqualTo(): + source = ''' + int main() { + AssertSameForest(Vector<>, Vector<>); + AssertNotSameForest(Vector<Proof1>, Vector<Proof2>); + AssertSameForest(Vector<Proof1, Proof2>, Vector<Proof2, Proof1b>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_set.py b/tests/meta/test_set.py new file mode 100644 index 0000000..bc73672 --- /dev/null +++ b/tests/meta/test_set.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/set.h> + #include <fruit/impl/meta/metaprogramming.h> + + struct A1 {}; + struct B1 {}; + struct C1 {}; + + using A = Type<A1>; + using B = Type<B1>; + using C = Type<C1>; + + struct Square { + template <typename N> + struct apply { + using type = Int<N::value * N::value>; + }; + }; + ''' + +def test_EmptySet(): + source = ''' + int main() { + AssertNot(IsInSet(A, EmptySet)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_ToSet1(): + source = ''' + int main() { + Assert(IsInSet(A, ToSet1<A>)); + AssertNot(IsInSet(A, ToSet1<B>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_ToSet2(): + source = ''' + int main() { + Assert(IsInSet(A, ToSet2<A, B>)); + Assert(IsInSet(B, ToSet2<A, B>)); + AssertNot(IsInSet(C, ToSet2<A, B>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsSameSet(): + source = ''' + int main() { + AssertSameSet(EmptySet, EmptySet); + AssertSameSet(ToSet<A, B>, ToSet<B, A>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_FoldSet(): + source = ''' + int main() { + AssertSameType(Id<FoldSet(ToSet<>, Sum, Int<3>)>, Int<3>); + AssertSameType(Id<FoldSet(ToSet<Int<2>>, Sum, Int<3>)>, Int<5>); + AssertSameType(Id<FoldSet(ToSet<Int<3>, Int<2>, Int<5>, Int<9>, Int<13>>, Sum, Int<7>)>, Int<39>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_FoldSetWithCombine(): + source = ''' + int main() { + AssertSameType(Id<FoldSetWithCombine(ToSet<>, Square, Sum, Int<0>)>, Int<0>); + AssertSameType(Id<FoldSetWithCombine(ToSet<Int<2>>, Square, Sum, Int<0>)>, Int<4>); + AssertSameType(Id<FoldSetWithCombine(ToSet<Int<3>, Int<2>, Int<5>, Int<9>, Int<13>>, Square, Sum, Int<0>)>, Int<288>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_AddToSet(): + source = ''' + int main() { + AssertSameSet(Id<AddToSet(EmptySet, A)>, ToSet<A>); + AssertSameSet(Id<AddToSet(ToSet<A, B>, A)>, ToSet<A, B>); + AssertSameSet(Id<AddToSet(ToSet<C, B>, A)>, ToSet<A, C, B>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_TransformSet(): + source = ''' + int main() { + AssertSameSet(Id<TransformSet(ToSet<>, Square)>, ToSet<>); + AssertSameSet(Id<TransformSet(ToSet<Int<2>>, Square)>, ToSet<Int<4>>); + AssertSameSet(Id<TransformSet(ToSet<Int<3>, Int<2>, Int<5>, Int<9>, Int<13>>, Square)>, ToSet<Int<9>, Int<4>, Int<25>, Int<81>, Int<169>>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_SetSize(): + source = ''' + int main() { + AssertSameType(Id<SetSize(ToSet<>)>, Int<0>); + AssertSameType(Id<SetSize(ToSet<Int<2>>)>, Int<1>); + AssertSameType(Id<SetSize(ToSet<Int<3>, Int<2>, Int<5>, Int<9>, Int<13>>)>, Int<5>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsEmptySet(): + source = ''' + int main() { + Assert(IsEmptySet(ToSet<>)); + AssertNot(IsEmptySet(ToSet<Int<2>>)); + AssertNot(IsEmptySet(ToSet<Int<3>, Int<2>, Int<5>, Int<9>, Int<13>>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_SetDifference(): + source = ''' + int main() { + AssertSameSet(Id<SetDifference(ToSet<>, ToSet<>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<>, ToSet<A>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<>, ToSet<A, B, C>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<A>, ToSet<>)>, ToSet<A>); + AssertSameSet(Id<SetDifference(ToSet<A>, ToSet<A>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<A>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<A>, ToSet<A, B, C>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<B>, ToSet<>)>, ToSet<B>); + AssertSameSet(Id<SetDifference(ToSet<B>, ToSet<A>)>, ToSet<B>); + AssertSameSet(Id<SetDifference(ToSet<B>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<B>, ToSet<A, B, C>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<B, C>, ToSet<>)>, ToSet<B, C>); + AssertSameSet(Id<SetDifference(ToSet<B, C>, ToSet<A>)>, ToSet<B, C>); + AssertSameSet(Id<SetDifference(ToSet<B, C>, ToSet<A, B>)>, ToSet<C>); + AssertSameSet(Id<SetDifference(ToSet<B, C>, ToSet<A, B, C>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<A, B>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetDifference(ToSet<A>, ToSet<A, B>)>, EmptySet); + AssertSameSet(Id<SetDifference(ToSet<A, B, C>, ToSet<A>)>, ToSet<B, C>); + AssertSameSet(Id<SetDifference(ToSet<A, B, C>, ToSet<B>)>, ToSet<A, C>); + AssertSameSet(Id<SetDifference(ToSet<A, B, C>, ToSet<C>)>, ToSet<A, B>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_SetIntersection(): + source = ''' + int main() { + AssertSameSet(Id<SetIntersection(ToSet<A, B>, ToSet<A, B>)>, ToSet<A, B>); + AssertSameSet(Id<SetIntersection(ToSet<A>, ToSet<A, B>)>, ToSet<A>); + AssertSameSet(Id<SetIntersection(ToSet<A, B>, ToSet<A>)>, ToSet<A>); + AssertSameSet(Id<SetIntersection(ToSet<A>, ToSet<B>)>, ToSet<>); + AssertSameSet(Id<SetIntersection(ToSet<>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetIntersection(ToSet<C>, ToSet<A, B>)>, ToSet<>); + AssertSameSet(Id<SetIntersection(ToSet<A, B>, ToSet<>)>, ToSet<>); + AssertSameSet(Id<SetIntersection(ToSet<A, B>, ToSet<C>)>, ToSet<>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsContained(): + source = ''' + int main() { + Assert(IsContained(ToSet<>, ToSet<>)); + Assert(IsContained(ToSet<>, ToSet<A>)); + Assert(IsContained(ToSet<A, B>, ToSet<A, B>)); + Assert(IsContained(ToSet<A, B>, ToSet<B, A>)); + Assert(IsContained(ToSet<A>, ToSet<A, B, C>)); + Assert(IsContained(ToSet<B>, ToSet<A, B, C>)); + Assert(IsContained(ToSet<C>, ToSet<A, B, C>)); + AssertNot(IsContained(ToSet<A, B, C>, ToSet<A, B>)); + AssertNot(IsContained(ToSet<A, B, C>, ToSet<A, C>)); + AssertNot(IsContained(ToSet<A, B, C>, ToSet<B, C>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsDisjoint(): + source = ''' + int main() { + AssertNot(IsDisjoint(ToSet<A, B>, ToSet<A, B>)); + AssertNot(IsDisjoint(ToSet<A>, ToSet<A, B>)); + AssertNot(IsDisjoint(ToSet<A, B>, ToSet<A>)); + Assert(IsDisjoint(ToSet<A>, ToSet<B>)); + Assert(IsDisjoint(ToSet<>, ToSet<A, B>)); + Assert(IsDisjoint(ToSet<C>, ToSet<A, B>)); + Assert(IsDisjoint(ToSet<A, B>, ToSet<>)); + Assert(IsDisjoint(ToSet<A, B>, ToSet<C>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_SetUnion(): + source = ''' + int main() { + AssertSameSet(Id<SetUnion(ToSet<A, B>, ToSet<A, B>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<A>, ToSet<A, B>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<A, B>, ToSet<A>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<A>, ToSet<B>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<>, ToSet<A, B>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<C>, ToSet<A, B>)>, ToSet<A, B, C>); + AssertSameSet(Id<SetUnion(ToSet<A, B>, ToSet<>)>, ToSet<A, B>); + AssertSameSet(Id<SetUnion(ToSet<A, B>, ToSet<C>)>, ToSet<A, B, C>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/meta/test_vector.py b/tests/meta/test_vector.py new file mode 100644 index 0000000..12a39e9 --- /dev/null +++ b/tests/meta/test_vector.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #define IN_FRUIT_CPP_FILE + + #include "meta/common.h" + #include <fruit/impl/meta/vector.h> + #include <fruit/impl/meta/metaprogramming.h> + + struct A1 {}; + struct B1 {}; + struct C1 {}; + + using A = A1; + using B = B1; + using C = C1; + ''' + +def test_IsInVector(): + source = ''' + int main() { + AssertNot(IsInVector(A, Vector<>)); + AssertNot(IsInVector(A, Vector<B>)); + Assert(IsInVector(A, Vector<A>)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_IsSameVector(): + source = ''' + int main() { + AssertNotSameType(Vector<A, B>, Vector<B, A>); + AssertNotSameType(Vector<A>, Vector<>); + AssertNotSameType(Vector<>, Vector<A>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_VectorSize(): + source = ''' + int main() { + AssertSameType(Id<VectorSize(Vector<>)>, Int<0>); + AssertSameType(Id<VectorSize(Vector<A>)>, Int<1>); + AssertSameType(Id<VectorSize(Vector<A, B>)>, Int<2>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_ConcatVectors(): + source = ''' + int main() { + AssertSameType(Id<ConcatVectors(Vector<>, Vector<>)>, Vector<>); + AssertSameType(Id<ConcatVectors(Vector<>, Vector<A, B>)>, Vector<A, B>); + AssertSameType(Id<ConcatVectors(Vector<A, B>, Vector<>)>, Vector<A, B>); + AssertSameType(Id<ConcatVectors(Vector<A>, Vector<A, B>)>, Vector<A, A, B>); + AssertSameType(Id<ConcatVectors(Vector<A, B>, Vector<A, C>)>, Vector<A, B, A, C>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_VectorEndsWith(): + source = ''' + int main() { + Assert(VectorEndsWith(Vector<A, B>, B)); + AssertNot(VectorEndsWith(Vector<A, B>, A)); + AssertNot(VectorEndsWith(Vector<>, A)); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_VectorRemoveFirstN(): + source = ''' + int main() { + AssertSameType(Id<VectorRemoveFirstN(Vector<>, Int<0>)>, Vector<>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A>, Int<0>)>, Vector<A>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A>, Int<1>)>, Vector<>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A, B, C>, Int<0>)>, Vector<A, B, C>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A, B, C>, Int<1>)>, Vector<B, C>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A, B, C>, Int<2>)>, Vector<C>); + AssertSameType(Id<VectorRemoveFirstN(Vector<A, B, C>, Int<3>)>, Vector<>); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_bind_interface.py b/tests/test_bind_interface.py new file mode 100755 index 0000000..14f1b35 --- /dev/null +++ b/tests/test_bind_interface.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + struct Annotation2 {}; + ''' + +@pytest.mark.parametrize('XAnnot,MaybeConstXAnnot,XConstRefAnnot,YAnnot', [ + ('X', 'X', 'const X&', 'Y'), + ('X', 'const X', 'const X&', 'Y'), + ('X', 'X', 'const X&', 'fruit::Annotated<Annotation1, Y>'), + ('X', 'const X', 'const X&', 'fruit::Annotated<Annotation1, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X&>', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation1, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation1, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation2, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_interface(XAnnot, MaybeConstXAnnot, XConstRefAnnot, YAnnot): + source = ''' + struct X { + virtual void f() const = 0; + }; + + struct Y : public X { + INJECT(Y()) = default; + + void f() const override { + } + }; + + fruit::Component<MaybeConstXAnnot> getComponent() { + return fruit::createComponent() + .bind<XAnnot, YAnnot>(); + } + + int main() { + fruit::Injector<MaybeConstXAnnot> injector(getComponent); + const X& x = injector.get<XConstRefAnnot>(); + x.f(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot,XConstRefAnnot,YAnnot', [ + ('X', 'const X', 'const X&', 'Y'), + ('X', 'const X', 'const X&', 'fruit::Annotated<Annotation1, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation1, Y>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_interface_to_constant(XAnnot, ConstXAnnot, XConstRefAnnot, YAnnot): + source = ''' + struct X { + virtual void f() const = 0; + }; + + struct Y : public X { + void f() const override { + } + }; + + const Y y{}; + + fruit::Component<ConstXAnnot> getComponent() { + return fruit::createComponent() + .bindInstance<YAnnot, Y>(y) + .bind<XAnnot, YAnnot>(); + } + + int main() { + fruit::Injector<ConstXAnnot> injector(getComponent); + const X& x = injector.get<XConstRefAnnot>(); + x.f(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XRefAnnot,YAnnot', [ + ('X', 'X&', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X&>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_interface_target_bound_in_other_component(XAnnot, XRefAnnot, YAnnot): + source = ''' + struct X { + virtual void f() = 0; + }; + + struct Y : public X { + void f() override { + } + }; + + fruit::Component<fruit::Required<YAnnot>, XAnnot> getComponent() { + return fruit::createComponent() + .bind<XAnnot, YAnnot>(); + } + + fruit::Component<XAnnot> getRootComponent() { + return fruit::createComponent() + .registerConstructor<YAnnot()>() + .install(getComponent); + } + + int main() { + fruit::Injector<XAnnot> injector(getRootComponent); + X& x = injector.get<XRefAnnot>(); + x.f(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XRefAnnot,YAnnot', [ + ('X', 'X&', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X&>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_nonconst_interface_requires_nonconst_target(XAnnot, XRefAnnot, YAnnot): + source = ''' + struct X { + virtual void f() = 0; + }; + + struct Y : public X { + void f() override { + } + }; + + fruit::Component<fruit::Required<const YAnnot>, XAnnot> getComponent() { + return fruit::createComponent() + .bind<XAnnot, YAnnot>(); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<YAnnot>', + 'The type T was declared as a const Required type in the returned Component, however a non-const binding', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,YAnnot', [ + ('X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_interface_to_constant_nonconst_required_const_bound_error(XAnnot, YAnnot): + source = ''' + struct X { + virtual void f() const = 0; + }; + + struct Y : public X { + void f() const override { + } + }; + + const Y y{}; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent() + .bindInstance<YAnnot, Y>(y) + .bind<XAnnot, YAnnot>(); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnot>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XRefAnnot,YAnnot', [ + ('X', 'X&', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X&>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_bind_nonconst_interface_requires_nonconst_target_abstract(XAnnot, XRefAnnot, YAnnot): + source = ''' + struct X { + virtual void f() = 0; + }; + + struct Y : public X {}; + + fruit::Component<fruit::Required<const YAnnot>, XAnnot> getComponent() { + return fruit::createComponent() + .bind<XAnnot, YAnnot>(); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<YAnnot>', + 'The type T was declared as a const Required type in the returned Component, however a non-const binding', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,intAnnot', [ + ('X', 'int'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, int>'), +]) +def test_error_not_base(XAnnot, intAnnot): + source = ''' + struct X {}; + + fruit::Component<intAnnot> getComponent() { + return fruit::createComponent() + .bind<XAnnot, intAnnot>(); + } + ''' + expect_compile_error( + 'NotABaseClassOfError<X,int>', + 'I is not a base class of C.', + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: maybe the error should include the annotation here. +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_error_bound_to_itself(XAnnot): + source = ''' + struct X {}; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .bind<XAnnot, XAnnot>(); + } + ''' + expect_compile_error( + 'InterfaceBindingToSelfError<X>', + 'The type C was bound to itself.', + COMMON_DEFINITIONS, + source, + locals()) + +def test_bound_to_itself_with_annotation_error(): + source = ''' + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerConstructor<X()>() + .bind<fruit::Annotated<Annotation1, X>, X>(); + } + ''' + expect_compile_error( + 'InterfaceBindingToSelfError<X>', + 'The type C was bound to itself.', + COMMON_DEFINITIONS, + source) + +def test_bound_chain_ok(): + source = ''' + struct X { + virtual void f() = 0; + }; + + struct Y : public X {}; + + struct Z : public Y { + INJECT(Z()) = default; + void f() override { + } + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .bind<X, Y>() + .bind<Y, Z>(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + X& x = injector.get<X&>(); + x.f(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_bind_non_normalized_types_error(): + source = ''' + struct X {}; + + struct Y : public std::shared_ptr<X> {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .bind<std::shared_ptr<X>, Y>(); + } + ''' + expect_compile_error( + 'NonClassTypeError<std::shared_ptr<X>,X>', + 'A non-class type T was specified. Use C instead', + COMMON_DEFINITIONS, + source) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_binding_clash.py b/tests/test_binding_clash.py new file mode 100755 index 0000000..aad02b7 --- /dev/null +++ b/tests/test_binding_clash.py @@ -0,0 +1,522 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + struct Y; + struct Z; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + using YAnnot1 = fruit::Annotated<Annotation1, Y>; + using ZAnnot1 = fruit::Annotated<Annotation1, Z>; + using ConstXAnnot1 = fruit::Annotated<Annotation1, const X>; + using ConstYAnnot1 = fruit::Annotated<Annotation1, const Y>; + using ConstZAnnot1 = fruit::Annotated<Annotation1, const Z>; + + struct Annotation2 {}; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + using YAnnot2 = fruit::Annotated<Annotation2, Y>; + using ZAnnot2 = fruit::Annotated<Annotation2, Z>; + using ConstXAnnot2 = fruit::Annotated<Annotation2, const X>; + using ConstYAnnot2 = fruit::Annotated<Annotation2, const Y>; + using ConstZAnnot2 = fruit::Annotated<Annotation2, const Z>; + + struct Annotation3 {}; + ''' + +CONSTRUCTOR_BINDING=( + '', + '.registerConstructor<XAnnot()>()') +INTERFACE_BINDING=( + ''' + struct Y : public X {}; + ''', + ''' + .bind<XAnnot, YAnnot>() + .registerConstructor<YAnnot()>() + ''') +INTERFACE_BINDING2=( + ''' + struct Y2 : public X {}; + ''', + ''' + .bind<XAnnot, Y2Annot>() + .registerConstructor<Y2Annot()>() + ''') +INSTALL=( + ''' + fruit::Component<XAnnot> getParentComponent() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + ''', + '.install(getParentComponent)') +INSTALL2=( + ''' + fruit::Component<XAnnot> getParentComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + ''', + '.install(getParentComponent2)') +CONST_BINDING_FROM_INSTALL=( + ''' + fruit::Component<const XAnnot> getParentComponent() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + ''', + '.install(getParentComponent)') +CONST_BINDING_FROM_INSTALL2=( + ''' + fruit::Component<const XAnnot> getParentComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + ''', + '.install(getParentComponent2)') +CONST_BINDING=( + ''' + const X x{}; + ''', + '.bindInstance<XAnnot, X>(x)') +CONST_BINDING2=( + ''' + const X x2{}; + ''', + '.bindInstance<XAnnot, X>(x2)') + +@pytest.mark.parametrize( + 'binding1_preparation,binding1,binding2_preparation,binding2', + [ + CONSTRUCTOR_BINDING + INSTALL, + INTERFACE_BINDING + INSTALL, + INSTALL + INSTALL2, + CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL, + INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL, + INSTALL2 + CONST_BINDING_FROM_INSTALL, + CONST_BINDING_FROM_INSTALL + INSTALL2, + CONST_BINDING + INSTALL2, + CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2, + CONST_BINDING + CONST_BINDING_FROM_INSTALL, + ], + ids = [ + 'CONSTRUCTOR_BINDING + INSTALL', + 'INTERFACE_BINDING + INSTALL', + 'INSTALL + INSTALL2', + 'CONSTRUCTOR_BINDING + CONST_BINDING_FROM_INSTALL', + 'INTERFACE_BINDING + CONST_BINDING_FROM_INSTALL', + 'INSTALL2 + CONST_BINDING_FROM_INSTALL', + 'CONST_BINDING_FROM_INSTALL + INSTALL2', + 'CONST_BINDING + INSTALL2', + 'CONST_BINDING_FROM_INSTALL + CONST_BINDING_FROM_INSTALL2', + 'CONST_BINDING + CONST_BINDING_FROM_INSTALL', + ]) +@pytest.mark.parametrize('XAnnot,YAnnot,Y2Annot', [ + ('X', 'Y', 'Y2'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation3, Y2>'), +]) +def test_clash_with_install( + binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot): + source = ''' + struct X{}; + + %s + %s + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent() + %s + %s; + } + ''' % (binding1_preparation, binding2_preparation, binding1, binding2) + expect_compile_error( + 'DuplicateTypesInComponentError<XAnnot>', + 'The installed component provides some types that are already provided by the current component.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize( + 'binding1_preparation,binding1,binding2_preparation,binding2', + [ + CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING, + CONSTRUCTOR_BINDING + INTERFACE_BINDING, + INTERFACE_BINDING + CONSTRUCTOR_BINDING, + INTERFACE_BINDING + INTERFACE_BINDING2, + INSTALL + CONSTRUCTOR_BINDING, + INSTALL + INTERFACE_BINDING, + CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING, + CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING, + CONST_BINDING + CONSTRUCTOR_BINDING, + CONST_BINDING + INTERFACE_BINDING, + CONSTRUCTOR_BINDING + CONST_BINDING, + INTERFACE_BINDING + CONST_BINDING, + INSTALL2 + CONST_BINDING, + CONST_BINDING_FROM_INSTALL + CONST_BINDING, + CONST_BINDING + CONST_BINDING2, + ], + ids= [ + 'CONSTRUCTOR_BINDING + CONSTRUCTOR_BINDING', + 'CONSTRUCTOR_BINDING + INTERFACE_BINDING', + 'INTERFACE_BINDING + CONSTRUCTOR_BINDING', + 'INTERFACE_BINDING + INTERFACE_BINDING2', + 'INSTALL + CONSTRUCTOR_BINDING', + 'INSTALL + INTERFACE_BINDING', + 'CONST_BINDING_FROM_INSTALL + CONSTRUCTOR_BINDING', + 'CONST_BINDING_FROM_INSTALL + INTERFACE_BINDING', + 'CONST_BINDING + CONSTRUCTOR_BINDING', + 'CONST_BINDING + INTERFACE_BINDING', + 'CONSTRUCTOR_BINDING + CONST_BINDING', + 'INTERFACE_BINDING + CONST_BINDING', + 'INSTALL2 + CONST_BINDING', + 'CONST_BINDING_FROM_INSTALL + CONST_BINDING', + 'CONST_BINDING + CONST_BINDING2', + ]) +@pytest.mark.parametrize('XAnnot,YAnnot,Y2Annot', [ + ('X', 'Y', 'Y2'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation3, Y2>'), +]) +def test_clash_with_binding(binding1_preparation, binding1, binding2_preparation, binding2, XAnnot, YAnnot, Y2Annot): + source = ''' + struct X{}; + + %s + %s + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent() + %s + %s; + } + + ''' % (binding1_preparation, binding2_preparation, binding1, binding2) + expect_compile_error( + 'TypeAlreadyBoundError<XAnnot>', + 'Trying to bind C but it is already bound.', + COMMON_DEFINITIONS, + source, + locals()) + +CONSTRUCTOR_BINDING_ANNOT1=( + '', + '.registerConstructor<XAnnot1()>()') +CONSTRUCTOR_BINDING_ANNOT2=( + '', + '.registerConstructor<XAnnot2()>()') +INTERFACE_BINDING_ANNOT1=( + ''' + struct Y : public X {}; + ''', + ''' + .bind<XAnnot1, YAnnot1>() + .registerConstructor<YAnnot1()>() + ''') +INTERFACE_BINDING_ANNOT2=( + ''' + struct Z : public X {}; + ''', + ''' + .bind<XAnnot2, ZAnnot2>() + .registerConstructor<ZAnnot2()>() + ''') +INSTALL_ANNOT1=( + ''' + fruit::Component<XAnnot1> getParentComponent1() { + return fruit::createComponent() + .registerConstructor<XAnnot1()>(); + } + ''', + '.install(getParentComponent1)') +INSTALL_ANNOT2=( + ''' + fruit::Component<XAnnot2> getParentComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot2()>(); + } + ''', + '.install(getParentComponent2)') +CONST_BINDING_FROM_INSTALL_ANNOT1=( + ''' + fruit::Component<ConstXAnnot1> getParentComponent1() { + return fruit::createComponent() + .registerConstructor<XAnnot1()>(); + } + ''', + '.install(getParentComponent1)') +CONST_BINDING_FROM_INSTALL_ANNOT2=( + ''' + fruit::Component<ConstXAnnot2> getParentComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot2()>(); + } + ''', + '.install(getParentComponent2)') +CONST_BINDING_ANNOT1=( + ''' + const X x1{}; + ''', + '.bindInstance<XAnnot1, X>(x1)') +CONST_BINDING_ANNOT2=( + ''' + const X x2{}; + ''', + '.bindInstance<XAnnot2, X>(x2)') + +@pytest.mark.parametrize( + 'binding1_preparation,binding1,binding2_preparation,binding2', + [ + CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, + CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, + INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, + INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, + INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, + INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2, + CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, + CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2, + CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2, + CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2, + CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2, + INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2, + CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, + INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, + INSTALL_ANNOT1 + INSTALL_ANNOT2, + CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2, + CONST_BINDING_ANNOT1 + INSTALL_ANNOT2, + INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + INSTALL_ANNOT1 + CONST_BINDING_ANNOT2, + CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2, + CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2, + ], + ids=[ + 'CONSTRUCTOR_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', + 'CONSTRUCTOR_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', + 'INTERFACE_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', + 'INTERFACE_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', + 'INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', + 'INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2', + 'CONST_BINDING_FROM_INSTALL_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', + 'CONST_BINDING_ANNOT1 + CONSTRUCTOR_BINDING_ANNOT2', + 'CONST_BINDING_FROM_INSTALL_ANNOT1 + INTERFACE_BINDING_ANNOT2', + 'CONST_BINDING_ANNOT1 + INTERFACE_BINDING_ANNOT2', + 'CONSTRUCTOR_BINDING_ANNOT1 + INSTALL_ANNOT2', + 'INTERFACE_BINDING_ANNOT1 + INSTALL_ANNOT2', + 'CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + 'CONSTRUCTOR_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', + 'INTERFACE_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + 'INTERFACE_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', + 'INSTALL_ANNOT1 + INSTALL_ANNOT2', + 'CONST_BINDING_FROM_INSTALL_ANNOT1 + INSTALL_ANNOT2', + 'CONST_BINDING_ANNOT1 + INSTALL_ANNOT2', + 'INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + 'INSTALL_ANNOT1 + CONST_BINDING_ANNOT2', + 'CONST_BINDING_FROM_INSTALL_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + 'CONST_BINDING_ANNOT1 + CONST_BINDING_ANNOT2', + 'CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + 'CONST_BINDING_ANNOT1 + CONST_BINDING_FROM_INSTALL_ANNOT2', + ]) +def test_no_clash_with_different_annotations(binding1_preparation, binding1, binding2_preparation, binding2): + source = ''' + struct X {}; + + %s + %s + + fruit::Component<const XAnnot1, const XAnnot2> getComponent() { + return fruit::createComponent() + %s + %s; + } + + int main() { + fruit::Injector<const XAnnot1, const XAnnot2> injector(getComponent); + injector.get<XAnnot1>(); + injector.get<XAnnot2>(); + } + ''' % (binding1_preparation, binding2_preparation, binding1, binding2) + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('NormalizedComponentXAnnot,ComponentXAnnot,XAnnot', [ + ('X', 'X', 'X'), + ('const X', 'X', 'X'), + ('X', 'const X', 'X'), + ('const X', 'const X', 'X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), +]) +def test_during_component_merge(NormalizedComponentXAnnot, ComponentXAnnot, XAnnot): + source = ''' + struct X {}; + + fruit::Component<NormalizedComponentXAnnot> getComponent1() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + + fruit::Component<ComponentXAnnot> getComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot()>(); + } + + void f() { + fruit::NormalizedComponent<NormalizedComponentXAnnot> nc(getComponent1); + fruit::Injector<> injector(nc, getComponent2); + (void) injector; + } + ''' + expect_compile_error( + 'DuplicateTypesInComponentError<XAnnot>', + 'The installed component provides some types that are already provided', + COMMON_DEFINITIONS, + source, + locals()) + +def test_during_component_merge_with_different_annotation_ok(): + source = ''' + struct X {}; + + fruit::Component<XAnnot1> getComponent1() { + return fruit::createComponent() + .registerConstructor<XAnnot1()>(); + } + + fruit::Component<XAnnot2> getComponent2() { + return fruit::createComponent() + .registerConstructor<XAnnot2()>(); + } + + int main() { + fruit::NormalizedComponent<XAnnot1> nc(getComponent1); + fruit::Injector<XAnnot1, XAnnot2> injector(nc, getComponent2); + injector.get<XAnnot1>(); + injector.get<XAnnot2>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot,XAnnotRegex', [ + ('X', '(struct )?X'), + ('fruit::Annotated<Annotation1, X>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), +]) +def test_bind_instance_and_bind_instance_runtime(XAnnot, XAnnotRegex): + source = ''' + struct X {}; + + fruit::Component<> getComponentForInstanceHelper() { + // Note: don't do this in real code, leaks memory. + return fruit::createComponent() + .bindInstance<XAnnot, X>(*(new X())); + } + + fruit::Component<XAnnot> getComponentForInstance() { + // Note: don't do this in real code, leaks memory. + return fruit::createComponent() + .install(getComponentForInstanceHelper) + .bindInstance<XAnnot, X>(*(new X())); + } + + int main() { + fruit::Injector<XAnnot> injector(getComponentForInstance); + injector.get<XAnnot>(); + } + ''' + expect_runtime_error( + 'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XAnnotRegex', [ + ('X', '(struct )?X'), + ('fruit::Annotated<Annotation1, X>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), +]) +def test_bind_instance_and_binding_runtime(XAnnot, XAnnotRegex): + source = ''' + struct X {}; + + fruit::Component<> getComponentForInstanceHelper(X* x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + fruit::Component<XAnnot> getComponentForInstance(X* x) { + return fruit::createComponent() + .install(getComponentForInstanceHelper, x) + .registerConstructor<XAnnot()>(); + } + + int main() { + X x; + fruit::Injector<XAnnot> injector(getComponentForInstance, &x); + injector.get<XAnnot>(); + } + ''' + expect_runtime_error( + 'Fatal injection error: the type XAnnotRegex was provided more than once, with different bindings.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_during_component_merge_consistent_ok(XAnnot): + source = ''' + struct X : public ConstructionTracker<X> { + using Inject = X(); + }; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .install(getComponent); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent); + fruit::Injector<XAnnot> injector(normalizedComponent, getComponent); + + Assert(X::num_objects_constructed == 0); + injector.get<XAnnot>(); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_binding_compression.py b/tests/test_binding_compression.py new file mode 100755 index 0000000..680449d --- /dev/null +++ b/tests/test_binding_compression.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + struct Annotation2 {}; + + template <typename T> + using WithNoAnnot = T; + + template <typename T> + using WithAnnot1 = fruit::Annotated<Annotation1, T>; + + template <typename T> + using WithAnnot2 = fruit::Annotated<Annotation2, T>; + ''' + +@pytest.mark.parametrize('IAnnot,XAnnot,WithAnnot', [ + ('I', 'X', 'WithNoAnnot'), + ('fruit::Annotated<Annotation1, I>', 'fruit::Annotated<Annotation2, X>', 'WithAnnot1'), +]) +def test_provider_returning_value_success_with_annotation(IAnnot, XAnnot, WithAnnot): + source = ''' + struct I { + int value = 5; + }; + + struct X : public I, ConstructionTracker<X> { + }; + + fruit::Component<IAnnot> getComponent() { + return fruit::createComponent() + .registerProvider<XAnnot()>([](){return X();}) + .bind<IAnnot, XAnnot>(); + } + + int main() { + fruit::Injector<IAnnot> injector(getComponent); + Assert((injector.get<WithAnnot<I >>() .value == 5)); + Assert((injector.get<WithAnnot<I* >>()->value == 5)); + Assert((injector.get<WithAnnot<I& >>() .value == 5)); + Assert((injector.get<WithAnnot<const I >>() .value == 5)); + Assert((injector.get<WithAnnot<const I* >>()->value == 5)); + Assert((injector.get<WithAnnot<const I& >>() .value == 5)); + Assert((injector.get<WithAnnot<std::shared_ptr<I>>>()->value == 5)); + Assert(fruit::impl::InjectorAccessorForTests::unsafeGet<WithAnnot<X>>(injector) == nullptr); + + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('IAnnot,XAnnot,XPtrAnnot,WithAnnot', [ + ('I', 'X', 'X*', 'WithNoAnnot'), + ('fruit::Annotated<Annotation1, I>', 'fruit::Annotated<Annotation2, X>', 'fruit::Annotated<Annotation2, X*>', 'WithAnnot1'), +]) +def test_provider_returning_pointer_success_with_annotation(IAnnot, XAnnot, XPtrAnnot, WithAnnot): + source = ''' + struct I { + int value = 5; + }; + + struct X : public I, ConstructionTracker<X> { + }; + + fruit::Component<IAnnot> getComponent() { + return fruit::createComponent() + .registerProvider<XPtrAnnot()>([](){return new X();}) + .bind<IAnnot, XAnnot>(); + } + + int main() { + fruit::Injector<IAnnot> injector(getComponent); + Assert((injector.get<WithAnnot<I >>() .value == 5)); + Assert((injector.get<WithAnnot<I* >>()->value == 5)); + Assert((injector.get<WithAnnot<I& >>() .value == 5)); + Assert((injector.get<WithAnnot<const I >>() .value == 5)); + Assert((injector.get<WithAnnot<const I* >>()->value == 5)); + Assert((injector.get<WithAnnot<const I& >>() .value == 5)); + Assert((injector.get<WithAnnot<std::shared_ptr<I>>>()->value == 5)); + Assert(fruit::impl::InjectorAccessorForTests::unsafeGet<WithAnnot<X>>(injector) == nullptr); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_compression_undone(): + source = ''' + struct I1 {}; + struct C1 : public I1, ConstructionTracker<C1> { + INJECT(C1()) = default; + }; + + struct I2 {}; + struct C2 : public I2 { + INJECT(C2(I1*)) {} + }; + + fruit::Component<I1> getI1Component() { + return fruit::createComponent() + .bind<I1, C1>(); + } + + fruit::Component<I2> getI2Component() { + return fruit::createComponent() + .install(getI1Component) + .bind<I2, C2>(); + } + + struct X { + // Intentionally C1 and not I1. This prevents binding compression for the I1->C1 edge. + INJECT(X(C1*)) {} + }; + + fruit::Component<X> getXComponent() { + return fruit::createComponent(); + } + + int main() { + // Here the binding C2->I1->C1 is compressed into C2->C1. + fruit::NormalizedComponent<I2> normalizedComponent(getI2Component); + + // However the binding X->C1 prevents binding compression on I1->C1, the binding compression must be undone. + fruit::Injector<I2, X> injector(normalizedComponent, getXComponent); + + Assert(C1::num_objects_constructed == 0); + injector.get<I2*>(); + injector.get<X*>(); + Assert(fruit::impl::InjectorAccessorForTests::unsafeGet<C1>(injector) != nullptr); + Assert(C1::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_class_destruction.py b/tests/test_class_destruction.py new file mode 100644 index 0000000..f36c83d --- /dev/null +++ b/tests/test_class_destruction.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + // The shared_ptr objects below ensure (since these tests are run under Valgrind) that deletion occurs, and only once. + + struct I1 { + std::shared_ptr<int> x = std::make_shared<int>(3); + virtual ~I1() {} + }; + + struct I2 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct I3 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct I4 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X1 : I1 { + INJECT(X1()) = default; + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X2 : I2 { + // Taking an X1 here prevents binding compression. + INJECT(X2(X1)) {} + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X3 : public I3 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X4 : public I4 { + // Taking an X3 here prevents binding compression. + X4(X3) {}; + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X5 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X6 : public I1 { + INJECT(X6()) = default; + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X7 : public I1 { + std::shared_ptr<int> x = std::make_shared<int>(3); + }; + + struct X8 : public I1 { + std::shared_ptr<int> x = std::make_shared<int>(3); + virtual ~X8() {} + }; + + struct Annotation {}; + + using I1Annot = fruit::Annotated<Annotation, I1>; + using I2Annot = fruit::Annotated<Annotation, I2>; + using I3Annot = fruit::Annotated<Annotation, I3>; + using I4Annot = fruit::Annotated<Annotation, I4>; + + using X1Annot = fruit::Annotated<Annotation, X1>; + using X2Annot = fruit::Annotated<Annotation, X2>; + using X3Annot = fruit::Annotated<Annotation, X3>; + using X4Annot = fruit::Annotated<Annotation, X4>; + using X5Annot = fruit::Annotated<Annotation, X5>; + using X6Annot = fruit::Annotated<Annotation, X6>; + using X7Annot = fruit::Annotated<Annotation, X7>; + using X8Annot = fruit::Annotated<Annotation, X8>; + + using X1PtrAnnot = fruit::Annotated<Annotation, X1*>; + ''' + +@pytest.mark.parametrize( + 'I1Annot,I2Annot,I3Annot,I4Annot,X1Annot,X2Annot,X3Annot,X4Annot,X5Annot,X6Annot,X7Annot,X8Annot,X1PtrAnnot,bindX5Instance,addX7InstanceMultibinding', [ + ('I1', 'I2', 'I3', 'I4', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X1*', 'bindInstance(x5)', 'addInstanceMultibinding(*x7)'), + ('I1Annot', 'I2Annot', 'I3Annot', 'I4Annot', 'X1Annot', 'X2Annot', 'X3Annot', 'X4Annot', 'X5Annot', 'X6Annot', 'X7Annot', 'X8Annot', 'X1PtrAnnot', 'bindInstance<X5Annot>(x5)', 'addInstanceMultibinding<X7Annot>(*x7)'), + ]) +def test_injector_creation_no_injection( + I1Annot, I2Annot, I3Annot, I4Annot, X1Annot, X2Annot, X3Annot, X4Annot, X5Annot, X6Annot, X7Annot, X8Annot, X1PtrAnnot, bindX5Instance, addX7InstanceMultibinding): + source = ''' + fruit::Component<I1Annot, I2Annot, I3Annot, I4Annot, X5Annot> getComponent() { + static X5 x5; + static std::unique_ptr<X7> x7(new X7()); + return fruit::createComponent() + .bind<I1Annot, X1Annot>() + .bind<I2Annot, X2Annot>() + .bind<I3Annot, X3Annot>() + .bind<I4Annot, X4Annot>() + .registerProvider<X3Annot()>([]() { return X3(); }) + .registerProvider<X4Annot(X3Annot)>([](X3 x3) { return X4(x3); }) + .bindX5Instance + .addMultibinding<I1Annot, X6Annot>() + .addX7InstanceMultibinding + .addMultibindingProvider<X1PtrAnnot()>([]() { return (X1*) new X8(); }); + } + + int main() { + fruit::Injector<I1Annot, I2Annot, I3Annot, I4Annot, X5Annot> injector(getComponent); + (void)injector; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('I1Annot,I2Annot,I3Annot,I4Annot,X1Annot,X2Annot,X3Annot,X4Annot,X5Annot,X6Annot,X7Annot,X8Annot,X1PtrAnnot,bindX5Instance,addX7InstanceMultibinding', [ + ('I1', 'I2', 'I3', 'I4', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X1*', 'bindInstance(x5)', 'addInstanceMultibinding(*x7)'), + ('I1Annot', 'I2Annot', 'I3Annot', 'I4Annot', 'X1Annot', 'X2Annot', 'X3Annot', 'X4Annot', 'X5Annot', 'X6Annot', 'X7Annot', 'X8Annot', 'X1PtrAnnot', 'bindInstance<X5Annot>(x5)', 'addInstanceMultibinding<X7Annot>(*x7)'), +]) +def test_injector_creation_and_injection( + I1Annot, I2Annot, I3Annot, I4Annot, X1Annot, X2Annot, X3Annot, X4Annot, X5Annot, X6Annot, X7Annot, X8Annot, X1PtrAnnot, bindX5Instance, addX7InstanceMultibinding): + source = ''' + fruit::Component<I1Annot, I2Annot, I3Annot, I4Annot, X5Annot> getComponent() { + static X5 x5; + static std::unique_ptr<X7> x7(new X7()); + return fruit::createComponent() + .bind<I1Annot, X1Annot>() + .bind<I2Annot, X2Annot>() + .bind<I3Annot, X3Annot>() + .bind<I4Annot, X4Annot>() + .registerProvider<X3Annot()>([]() { return X3(); }) + .registerProvider<X4Annot(X3Annot)>([](X3 x3) { return X4(x3); }) + .bindX5Instance + .addMultibinding<I1Annot, X6Annot>() + .addX7InstanceMultibinding + .addMultibindingProvider<X1PtrAnnot()>([]() { return (X1*) new X8(); }); + } + + int main() { + fruit::Injector<I1Annot, I2Annot, I3Annot, I4Annot, X5Annot> injector(getComponent); + + injector.get<I1Annot>(); + injector.get<I2Annot>(); + injector.get<I3Annot>(); + injector.get<I4Annot>(); + injector.get<X5Annot>(); + + injector.getMultibindings<I1Annot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_common.h b/tests/test_common.h new file mode 100644 index 0000000..d48f110 --- /dev/null +++ b/tests/test_common.h @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_TEST_COMMON_H +#define FRUIT_TEST_COMMON_H + +// This file includes headers used in various tests. +// This allows to improve compilation speed (and therefore test time) by pre-compiling this header. + +#include "class_construction_tracker.h" +#include "test_macros.h" +#include <fruit/fruit.h> +#include <fruit/impl/injector/injector_accessor_for_tests.h> +#include <map> +#include <vector> + +#endif // FRUIT_TEST_COMMON_H diff --git a/tests/test_component.py b/tests/test_component.py new file mode 100755 index 0000000..5bb6017 --- /dev/null +++ b/tests/test_component.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + ''' + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_move(XAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XAnnot> getComponent() { + fruit::Component<XAnnot> c = fruit::createComponent(); + fruit::Component<XAnnot> c2 = std::move(c); + return fruit::Component<XAnnot>(std::move(c2)); + } + + int main() { + fruit::Injector<XAnnot> injector(getComponent); + injector.get<XAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_move_partial_component(XAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XAnnot> getComponent() { + auto c = fruit::createComponent(); + auto c1 = std::move(c); + return std::move(c1); + } + + int main() { + fruit::Injector<XAnnot> injector(getComponent); + injector.get<XAnnot>(); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .fruit::PartialComponent<Bindings>::PartialComponent\(fruit::PartialComponent<Bindings>&&\).' + + '|error: call to deleted constructor of .fruit::PartialComponent<>.' + # MSVC 2017 + + '|error C2280: .fruit::PartialComponent<>::PartialComponent\(fruit::PartialComponent<> &&\).: attempting to reference a deleted function' + # MSVC 2015 + + '|error C2248: .fruit::PartialComponent<>::PartialComponent.: cannot access private member declared in class .fruit::PartialComponent<>.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_error_no_binding_found(XAnnot, ConstXAnnot): + source = ''' + struct X {}; + + fruit::Component<ConstXAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'NoBindingFoundError<XAnnot>', + 'No explicit binding nor C::Inject definition was found for T.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_error_no_binding_found_abstract_class(XAnnot, ConstXAnnot): + source = ''' + struct X { + virtual void f() = 0; + }; + + fruit::Component<ConstXAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'NoBindingFoundForAbstractClassError<XAnnot,X>', + 'No explicit binding was found for T, and note that C is an abstract class', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +def test_error_no_factory_binding_found(MaybeConst): + source = ''' + struct X {}; + + fruit::Component<MaybeConst std::function<std::unique_ptr<X>()>> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'NoBindingFoundError<std::function<std::unique_ptr<X(,std::default_delete<X>)?>\((void)?\)>', + 'No explicit binding nor C::Inject definition was found for T.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +def test_error_no_factory_binding_found_with_annotation(MaybeConst): + source = ''' + struct X {}; + + fruit::Component<fruit::Annotated<Annotation1, MaybeConst std::function<std::unique_ptr<X>()>>> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'NoBindingFoundError<fruit::Annotated<Annotation1,std::function<std::unique_ptr<X(,std::default_delete<X>)?>\((void)?\)>>', + 'No explicit binding nor C::Inject definition was found for T.', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_component_and_injector_params.py b/tests/test_component_and_injector_params.py new file mode 100755 index 0000000..00ccdb5 --- /dev/null +++ b/tests/test_component_and_injector_params.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X {}; + struct Y {}; + + struct Annotation1 {}; + using IntAnnot1 = fruit::Annotated<Annotation1, int>; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + using IntAnnot2 = fruit::Annotated<Annotation2, int>; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + ''' + +@pytest.mark.parametrize('XAnnot,MaybeConstXAnnot', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', + 'Injector', +]) +def test_duplicate_type(XAnnot, MaybeConstXAnnot, Class): + source = ''' + InstantiateType(fruit::Class<MaybeConstXAnnot, MaybeConstXAnnot>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', + 'Injector', +]) +def test_duplicate_type_different_constness(XAnnot, ConstXAnnot, Class): + source = ''' + InstantiateType(fruit::Class<XAnnot, ConstXAnnot>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +def test_duplicate_type_with_different_annotation_ok(): + source = ''' + fruit::Component<XAnnot1, XAnnot2> getComponent() { + return fruit::createComponent() + .registerConstructor<XAnnot1()>() + .registerConstructor<XAnnot2()>(); + } + + int main() { + fruit::Injector<XAnnot1, XAnnot2> injector1(getComponent); + injector1.get<XAnnot1>(); + injector1.get<XAnnot2>(); + + fruit::NormalizedComponent<XAnnot1, XAnnot2> normalizedComponent(getComponent); + fruit::Injector<XAnnot1, XAnnot2> injector2(getComponent); + injector2.get<XAnnot1>(); + injector2.get<XAnnot2>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot,MaybeConstXAnnot', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_duplicate_type_in_required(XAnnot, MaybeConstXAnnot, Class): + source = ''' + InstantiateType(fruit::Class<fruit::Required<MaybeConstXAnnot, MaybeConstXAnnot>>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_component_duplicate_type_in_required_different_constness(Class, XAnnot, ConstXAnnot): + source = ''' + InstantiateType(fruit::Class<fruit::Required<XAnnot, ConstXAnnot>>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,MaybeConstXAnnot', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_same_type_in_required_and_provided(XAnnot, MaybeConstXAnnot, Class): + source = ''' + InstantiateType(fruit::Class<fruit::Required<MaybeConstXAnnot>, MaybeConstXAnnot>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XAnnotInRequirements,XAnnotInProvides', [ + ('X', 'X', 'const X'), + ('X', 'const X', 'X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_same_type_in_required_and_provided_different_constness(XAnnot, XAnnotInRequirements, XAnnotInProvides, Class): + source = ''' + InstantiateType(fruit::Class<fruit::Required<XAnnotInRequirements>, XAnnotInProvides>) + ''' + expect_compile_error( + 'RepeatedTypesError<XAnnot,XAnnot>', + 'A type was specified more than once.', + COMMON_DEFINITIONS, + source, + locals()) + +def test_same_type_in_required_and_provided_different_annotation_ok(): + source = ''' + fruit::Component<fruit::Required<XAnnot1>, XAnnot2> getComponent() { + return fruit::createComponent() + .registerConstructor<XAnnot2()>(); + } + + fruit::Component<XAnnot1, XAnnot2> getRootComponent() { + return fruit::createComponent() + .install(getComponent) + .registerConstructor<XAnnot1()>(); + } + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XAnnot1, XAnnot2> injector1(getRootComponent); + injector1.get<XAnnot1>(); + injector1.get<XAnnot2>(); + + fruit::NormalizedComponent<XAnnot1, XAnnot2> normalizedComponent(getRootComponent); + fruit::Injector<XAnnot1, XAnnot2> injector2(normalizedComponent, getEmptyComponent); + injector2.get<XAnnot1>(); + injector2.get<XAnnot2>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XVariantAnnot,XVariantRegexp', [ + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('X&', 'X&'), + ('const X&', 'const X&'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), + ('fruit::Annotated<Annotation1, X*>', 'X\*'), + ('fruit::Annotated<Annotation1, const X*>', 'const X\*'), + ('fruit::Annotated<Annotation1, X&>', 'X&'), + ('fruit::Annotated<Annotation1, const X&>', 'const X&'), + ('fruit::Annotated<Annotation1, std::shared_ptr<X>>', 'std::shared_ptr<X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', + 'Injector', +]) +def test_error_non_class_type(XVariantAnnot, XVariantRegexp, Class): + source = ''' + InstantiateType(fruit::Class<XVariantAnnot>) + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariantAnnot,XVariantRegexp', [ + ('const X', 'const X'), + ('fruit::Annotated<Annotation1, const X>', 'const X'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', + 'Injector', +]) +def test_const_provided_type_ok(XVariantAnnot, XVariantRegexp, Class): + source = ''' + InstantiateType(fruit::Class<XVariantAnnot>) + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariantAnnot,XVariantRegexp', [ + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('X&', 'X&'), + ('const X&', 'const X&'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), + ('fruit::Annotated<Annotation1, X*>', 'X\*'), + ('fruit::Annotated<Annotation1, const X*>', 'const X\*'), + ('fruit::Annotated<Annotation1, X&>', 'X&'), + ('fruit::Annotated<Annotation1, const X&>', 'const X&'), + ('fruit::Annotated<Annotation1, std::shared_ptr<X>>', 'std::shared_ptr<X>'), +]) +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_error_non_class_type_in_requirements(XVariantAnnot, XVariantRegexp, Class): + source = ''' + InstantiateType(fruit::Class<fruit::Required<XVariantAnnot>>) + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstZAnnot,ZAnnot', [ + ('const Z', 'Z'), + ('fruit::Annotated<Annotation1, const Z>', 'fruit::Annotated<Annotation1, Z>'), +]) +def test_const_class_type_ok(ConstZAnnot, ZAnnot): + source = ''' + struct Z {}; + + const Z z{}; + + fruit::Component<ConstZAnnot> getComponent() { + return fruit::createComponent() + .bindInstance<ZAnnot, Z>(z); + } + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::NormalizedComponent<ConstZAnnot> normalizedComponent(getComponent); + fruit::Injector<ConstZAnnot> injector(normalizedComponent, getEmptyComponent); + injector.get<ZAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstZAnnot,ZAnnot', [ + ('const Z', 'Z'), + ('fruit::Annotated<Annotation1, const Z>', 'fruit::Annotated<Annotation1, Z>'), +]) +def test_const_class_type_in_requirements_ok(ConstZAnnot, ZAnnot): + source = ''' + struct Z {}; + + fruit::Component<fruit::Required<ConstZAnnot>> getComponent() { + return fruit::createComponent(); + } + + const Z z{}; + + fruit::Component<ConstZAnnot> getEmptyComponent() { + return fruit::createComponent() + .bindInstance<ZAnnot, Z>(z); + } + + int main() { + fruit::NormalizedComponent<fruit::Required<ConstZAnnot>> normalizedComponent(getComponent); + fruit::Injector<ConstZAnnot> injector(normalizedComponent, getEmptyComponent); + injector.get<ZAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_two_required_lists_error(Class): + source = ''' + InstantiateType(fruit::Class<fruit::Required<X>, fruit::Required<Y>>) + ''' + expect_compile_error( + 'RequiredTypesInComponentArgumentsError<fruit::Required<Y>>', + 'A Required<...> type was passed as a non-first template parameter to fruit::Component or fruit::NormalizedComponent', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('Class', [ + 'Component', + 'NormalizedComponent', +]) +def test_required_list_not_first_argument_error(Class): + source = ''' + InstantiateType(fruit::Class<X, fruit::Required<Y>>) + ''' + expect_compile_error( + 'RequiredTypesInComponentArgumentsError<fruit::Required<Y>>', + 'A Required<...> type was passed as a non-first template parameter to fruit::Component or fruit::NormalizedComponent', + COMMON_DEFINITIONS, + source, + locals()) + +def test_multiple_required_types_ok(): + source = ''' + fruit::Component<fruit::Required<X, Y>> getEmptyComponent() { + return fruit::createComponent(); + } + + fruit::Component<X, Y> getComponent() { + return fruit::createComponent() + .install(getEmptyComponent) + .registerConstructor<X()>() + .registerConstructor<Y()>(); + } + + int main() { + fruit::NormalizedComponent<fruit::Required<X, Y>> normalizedComponent(getEmptyComponent); + fruit::Injector<X> injector(normalizedComponent, getComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot,YAnnot', [ + ('X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_error_requirements_in_injector(XAnnot, YAnnot): + source = ''' + InstantiateType(fruit::Injector<fruit::Required<YAnnot>, XAnnot>) + ''' + expect_compile_error( + 'InjectorWithRequirementsError<YAnnot>', + 'Injectors can.t have requirements.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,YAnnot', [ + ('X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_error_requirements_in_injector_second_argument(XAnnot, YAnnot): + source = ''' + InstantiateType(fruit::Injector<XAnnot, fruit::Required<YAnnot>>) + ''' + expect_compile_error( + 'InjectorWithRequirementsError<YAnnot>', + 'Injectors can.t have requirements.', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_component_replacement.py b/tests/test_component_replacement.py new file mode 100755 index 0000000..f2f3e57 --- /dev/null +++ b/tests/test_component_replacement.py @@ -0,0 +1,920 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + ''' + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_success( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + int n = injector.get<int>(); + Assert(n == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_success_across_normalized_component( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<> getRootComponent1() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + fruit::Component<int> getRootComponent2() { + return fruit::createComponent() + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent1); + fruit::Injector<int> injector(normalizedComponent, getRootComponent2); + int n = injector.get<int>(); + Assert(n == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_replace_component_success_with_conversion(): + source = ''' + fruit::Component<int> getReplacedComponent(std::string) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(double, std::string, int) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .replace(getReplacedComponent, "Hi").with(getReplacementComponent, 2.0, "Hello", 12) + .install(getReplacedComponent, "Hi"); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + int n = injector.get<int>(); + Assert(n == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ComponentParamTypes,ReplacedComponentInstallation,ReplacementComponentInstallation,ReplacementReplacementComponentInstallation', [ + ('', 'getReplacedComponent', 'getReplacementComponent', 'getReplacementReplacementComponent'), + ('double', 'getReplacedComponent, 1.0', 'getReplacementComponent, 1.0', 'getReplacementReplacementComponent, 1.0'), +]) +def test_replace_component_chain_success( + ComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentInstallation, ReplacementReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementReplacementComponent(ComponentParamTypes) { + static int n = 30; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .replace(ReplacementComponentInstallation).with(ReplacementReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + int n = injector.get<int>(); + Assert(n == 30); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ComponentParamTypes,ReplacedComponentInstallation,ReplacementComponentInstallation,ReplacementReplacementComponentInstallation', [ + ('', 'getReplacedComponent', 'getReplacementComponent', 'getReplacementReplacementComponent'), + ('double', 'getReplacedComponent, 1.0', 'getReplacementComponent, 1.0', 'getReplacementReplacementComponent, 1.0'), +]) +def test_replace_component_chain_other_order_success( + ComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentInstallation, ReplacementReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementReplacementComponent(ComponentParamTypes) { + static int n = 30; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .replace(ReplacementComponentInstallation).with(ReplacementReplacementComponentInstallation) + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + int n = injector.get<int>(); + Assert(n == 30); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_replace_component_different_type_error(): + source = ''' + fruit::Component<int> getReplacedComponent(); + fruit::Component<double> getReplacementComponent(); + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(getReplacedComponent).with(getReplacementComponent); + } + ''' + expect_generic_compile_error( + # Clang + 'candidate template ignored: could not match .Component<int>. against .Component<double>.' + # GCC + '|mismatched types .int. and .double.' + # MSVC + '|could not deduce template argument for .fruit::Component<int> \(__cdecl \*\)\(FormalArgs...\). from .fruit::Component<double> \(void\).', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_already_replaced_consistent_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + int n = injector.get<int>(); + Assert(n == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_already_replaced_across_normalized_component_consistent_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<> getRootComponent1() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + fruit::Component<int> getRootComponent2() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent1); + fruit::Injector<int> injector(normalizedComponent, getRootComponent2); + int n = injector.get<int>(); + Assert(n == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation,OtherReplacementComponentInstallation', [ + ('', 'getReplacementComponent', 'getOtherReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")', 'getOtherReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_already_replaced_inconsistent_error( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation, OtherReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getOtherReplacementComponent(ReplacementComponentParamTypes) { + static int n = 30; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .replace(ReplacedComponentInstallation).with(OtherReplacementComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + (void) injector; + } + ''' + expect_runtime_error( + 'Fatal injection error: the component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\((void)?ReplacedComponentParamTypes\) was replaced ' + + '\(using .replace\(...\).with\(...\)\) with both the component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) and the component function at ' + + '(0x)?[0-9a-fA-F]* with signature (class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) .', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation,OtherReplacementComponentInstallation', [ + ('', 'getReplacementComponent', 'getOtherReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")', 'getOtherReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_already_replaced_across_normalized_component_inconsistent_error( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation, OtherReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getOtherReplacementComponent(ReplacementComponentParamTypes) { + static int n = 30; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<> getRootComponent1() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + fruit::Component<> getRootComponent2() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(OtherReplacementComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent1); + fruit::Injector<> injector(normalizedComponent, getRootComponent2); + (void) injector; + } + ''' + expect_runtime_error( + 'Fatal injection error: the component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\((void)?ReplacedComponentParamTypes\) was replaced ' + + '\(using .replace\(...\).with\(...\)\) with both the component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) and the component function at ' + + '(0x)?[0-9a-fA-F]* with signature (class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) .', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_after_install_error( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent() { + return fruit::createComponent() + .install(ReplacedComponentInstallation) + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + int main() { + fruit::Injector<int> injector(getRootComponent); + (void) injector; + } + ''' + expect_runtime_error( + 'Fatal injection error: unable to replace \(using .replace\(...\).with\(...\)\) the component function at ' + + '(0x)?[0-9a-fA-F]* with signature (class )?fruit::Component<int> \((__cdecl)?\*\)\((void)?ReplacedComponentParamTypes\) with the ' + + 'component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) because the former component function ' + + 'was installed before the .replace\(...\).with\(...\).', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_after_install_across_normalized_component_error( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<int> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .bindInstance(n); + } + + fruit::Component<int> getRootComponent1() { + return fruit::createComponent() + .install(ReplacedComponentInstallation); + } + + fruit::Component<> getRootComponent2() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<int> normalizedComponent(getRootComponent1); + fruit::Injector<int> injector(normalizedComponent, getRootComponent2); + (void) injector; + } + ''' + expect_runtime_error( + 'Fatal injection error: unable to replace \(using .replace\(...\).with\(...\)\) the component function at ' + + '(0x)?[0-9a-fA-F]* with signature (class )?fruit::Component<int> \((__cdecl)?\*\)\((void)?ReplacedComponentParamTypes\) with the ' + + 'component function at (0x)?[0-9a-fA-F]* with signature ' + + '(class )?fruit::Component<int> \((__cdecl)?\*\)\(.*\) because the former component function ' + + 'was installed before the .replace\(...\).with\(...\).', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation,ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacedComponent', '', 'getReplacementComponent'), + ('double', 'getReplacedComponent, 1.0', 'std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_unused_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 0); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_used_multiple_times_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_also_installed_directly_before_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .install(ReplacementComponentInstallation) + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_also_installed_directly_after_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation) + .install(ReplacementComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_also_installed_directly_before_across_normalized_component_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent1() { + return fruit::createComponent() + .install(ReplacementComponentInstallation); + } + + fruit::Component<> getRootComponent2() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent1); + fruit::Injector<> injector(normalizedComponent, getRootComponent2); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation', [ + ('', 'getReplacedComponent'), + ('double', 'getReplacedComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_component_also_installed_directly_after_across_normalized_component_ok( + ReplacedComponentParamTypes, ReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent1() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation); + } + + fruit::Component<> getRootComponent2() { + return fruit::createComponent() + .install(ReplacementComponentInstallation); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent1); + fruit::Injector<> injector(normalizedComponent, getRootComponent2); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 20); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ReplacedComponentParamTypes,ReplacedComponentInstallation,OtherReplacedComponentInstallation', [ + ('', 'getReplacedComponent', 'getOtherReplacementComponent'), + ('double', 'getReplacedComponent, 1.0', 'getOtherReplacementComponent, 1.0'), +]) +@pytest.mark.parametrize('ReplacementComponentParamTypes,ReplacementComponentInstallation', [ + ('', 'getReplacementComponent'), + ('std::string', 'getReplacementComponent, std::string("Hello, world")'), +]) +def test_replace_multiple_components_with_same( + ReplacedComponentParamTypes, ReplacedComponentInstallation, OtherReplacedComponentInstallation, ReplacementComponentParamTypes, ReplacementComponentInstallation): + source = ''' + fruit::Component<> getReplacedComponent(ReplacedComponentParamTypes) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getOtherReplacementComponent(ReplacedComponentParamTypes) { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent(ReplacementComponentParamTypes) { + static int n = 30; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(ReplacedComponentInstallation).with(ReplacementComponentInstallation) + .replace(OtherReplacedComponentInstallation).with(ReplacementComponentInstallation) + .install(ReplacedComponentInstallation) + .install(OtherReplacedComponentInstallation); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 1); + Assert(*(multibindings[0]) == 30); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_replace_component_one_set_of_args_only(): + source = ''' + fruit::Component<> getReplacedComponent(double) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent() { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(getReplacedComponent, 1.0).with(getReplacementComponent) + .install(getReplacedComponent, 1.0) + .install(getReplacedComponent, 5.0); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 2); + Assert(*(multibindings[0]) == 20); + Assert(*(multibindings[1]) == 10); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_replace_component_already_replaced_with_different_args(): + source = ''' + fruit::Component<> getReplacedComponent(double) { + static int n = 10; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getReplacementComponent() { + static int n = 20; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getOtherReplacementComponent() { + static int n = 30; + return fruit::createComponent() + .addInstanceMultibinding(n); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .replace(getReplacedComponent, 1.0).with(getReplacementComponent) + .replace(getReplacedComponent, 5.0).with(getOtherReplacementComponent) + .install(getReplacedComponent, 1.0) + .install(getReplacedComponent, 5.0); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + + std::vector<int*> multibindings = injector.getMultibindings<int>(); + Assert(multibindings.size() == 2); + Assert(*(multibindings[0]) == 20); + Assert(*(multibindings[1]) == 30); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_defn_h_includes.py b/tests/test_defn_h_includes.py new file mode 100755 index 0000000..5b1d54f --- /dev/null +++ b/tests/test_defn_h_includes.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import pytest +import re +import sys +from fruit_test_common import * + +@pytest.mark.skipif( + os.sep != '/', + reason = 'This only works in platforms where paths are /-separated.') +def test_defn_file_inclusion(): + include_pattern = re.compile(' *#include *<(.*)> *') + + fruit_headers_root_absolute_path = os.path.abspath(PATH_TO_FRUIT_STATIC_HEADERS) + + includes = {} + for root, _, files in os.walk(fruit_headers_root_absolute_path): + for file in files: + if file.endswith('.h'): + path = os.path.join(root, file) + with open(path, 'r') as f: + current_includes = set() + for line in f.readlines(): + # Remove the newline + line = line[:-1] + matches = re.match(include_pattern, line) + if matches: + current_includes.add(matches.groups()[0]) + root_relative_path = root.replace(fruit_headers_root_absolute_path, '') + relative_path = os.path.join(root_relative_path, file) + if relative_path.startswith(os.sep): + relative_path = relative_path[1:] + includes[relative_path] = current_includes + + for defn_file, defn_file_includes in includes.items(): + if defn_file.endswith('.defn.h'): + main_header_file = defn_file.replace('.defn.h', '.h') + # This is a special case where we don't follow the convention, so we need to specify the corresponding main + # header file explicitly. + if defn_file == 'fruit/impl/component_functors.defn.h': + main_header_file = 'fruit/component.h' + + # The .defn.h files for headers in fruit/ are in fruit/impl/ + alternative_main_header_file = main_header_file.replace('fruit/impl/', 'fruit/') + + if main_header_file not in includes and alternative_main_header_file not in includes: + raise Exception('Can\'t find the .h header corresponding to: %s. Considered: %s' % (defn_file, (main_header_file, alternative_main_header_file))) + if main_header_file not in defn_file_includes and alternative_main_header_file not in defn_file_includes: + raise Exception('%s should have included %s, but it includes only: %s' % (defn_file, main_header_file, defn_file_includes)) + if main_header_file in includes and defn_file not in includes[main_header_file]: + raise Exception('%s should have included %s, but it includes only: %s' % (main_header_file, defn_file, includes[main_header_file])) + if alternative_main_header_file in includes and defn_file not in includes[alternative_main_header_file]: + raise Exception('%s should have included %s, but it includes only: %s' % (main_header_file, defn_file, includes[alternative_main_header_file])) + for other_header, other_header_includes in includes.items(): + if other_header not in (main_header_file, alternative_main_header_file) and defn_file in other_header_includes: + raise Exception('Unexpected direct include: %s includes %s' % (other_header, defn_file)) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_dependency_loop.py b/tests/test_dependency_loop.py new file mode 100755 index 0000000..b9698bb --- /dev/null +++ b/tests/test_dependency_loop.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + + struct Annotation3 {}; + using XAnnot3 = fruit::Annotated<Annotation3, X>; + ''' + +@pytest.mark.parametrize('XAnnot,XConstRefAnnot,YAnnot,YConstRefAnnot', [ + ('X', 'const X&', 'Y', 'const Y&'), + ('fruit::Annotated<Annotation1, X>', 'ANNOTATED(Annotation1, const X&)', + 'fruit::Annotated<Annotation2, Y>', 'ANNOTATED(Annotation2, const Y&)') +]) +def test_loop_in_autoinject(XAnnot, XConstRefAnnot, YAnnot, YConstRefAnnot): + source = ''' + struct Y; + + struct X { + INJECT(X(YConstRefAnnot)) {}; + }; + + struct Y { + INJECT(Y(XConstRefAnnot)) {}; + }; + + fruit::Component<XAnnot> mutuallyConstructibleComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'SelfLoopError<XAnnot,YAnnot>', + 'Found a loop in the dependencies', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot,XConstRefAnnot,YAnnot,YConstRefAnnot', [ + ('X', 'const X', 'const X&', 'Y', 'const Y&'), + ('fruit::Annotated<Annotation1, X>', 'ANNOTATED(Annotation1, const X)', 'ANNOTATED(Annotation1, const X&)', + 'fruit::Annotated<Annotation2, Y>', 'ANNOTATED(Annotation2, const Y&)') +]) +def test_loop_in_autoinject_const(XAnnot, ConstXAnnot, XConstRefAnnot, YAnnot, YConstRefAnnot): + source = ''' + struct Y; + + struct X { + INJECT(X(YConstRefAnnot)) {}; + }; + + struct Y { + INJECT(Y(XConstRefAnnot)) {}; + }; + + fruit::Component<ConstXAnnot> mutuallyConstructibleComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'SelfLoopError<XAnnot,YAnnot>', + 'Found a loop in the dependencies', + COMMON_DEFINITIONS, + source, + locals()) + +def test_loop_in_register_provider(): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<X> mutuallyConstructibleComponent() { + return fruit::createComponent() + .registerProvider<X(Y)>([](Y) {return X();}) + .registerProvider<Y(X)>([](X) {return Y();}); + } + ''' + expect_compile_error( + 'SelfLoopError<X,Y>', + 'Found a loop in the dependencies', + COMMON_DEFINITIONS, + source, + locals()) + +def test_loop_in_register_provider_with_annotations(): + source = ''' + struct X {}; + + fruit::Component<fruit::Annotated<Annotation1, X>> mutuallyConstructibleComponent() { + return fruit::createComponent() + .registerProvider<fruit::Annotated<Annotation1, X>(fruit::Annotated<Annotation2, X>)>([](X x) {return x;}) + .registerProvider<fruit::Annotated<Annotation2, X>(fruit::Annotated<Annotation1, X>)>([](X x) {return x;}); + } + ''' + expect_compile_error( + 'SelfLoopError<fruit::Annotated<Annotation1, X>, fruit::Annotated<Annotation2, X>>', + 'Found a loop in the dependencies', + COMMON_DEFINITIONS, + source, + locals()) + +def test_with_different_annotations_ok(): + source = ''' + struct X {}; + + fruit::Component<XAnnot3> getComponent() { + return fruit::createComponent() + .registerProvider<XAnnot1()>([](){return X();}) + .registerProvider<XAnnot2(XAnnot1)>([](X x){return x;}) + .registerProvider<XAnnot3(XAnnot2)>([](X x){return x;}); + } + + int main() { + fruit::Injector<XAnnot3> injector(getComponent); + injector.get<XAnnot3>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_eager_injection.py b/tests/test_eager_injection.py new file mode 100644 index 0000000..60a914b --- /dev/null +++ b/tests/test_eager_injection.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X { + INJECT(X()) { + Assert(!constructed); + constructed = true; + } + + static bool constructed; + }; + + bool X::constructed = false; + + struct Y { + Y() { + Assert(!constructed); + constructed = true; + } + + static bool constructed; + }; + + bool Y::constructed = false; + + struct Z { + Z() { + Assert(!constructed); + constructed = true; + } + + static bool constructed; + }; + + bool Z::constructed = false; + ''' + +def test_eager_injection_deprecated(): + source = ''' + fruit::Component<X> getComponent() { + return fruit::createComponent() + .addMultibindingProvider([](){return new Y();}) + .registerConstructor<Z()>(); + } + + int main() { + + fruit::Injector<X> injector(getComponent); + + Assert(!X::constructed); + Assert(!Y::constructed); + Assert(!Z::constructed); + + injector.eagerlyInjectAll(); + + Assert(X::constructed); + Assert(Y::constructed); + // Z still not constructed, it's not reachable from Injector<X>. + Assert(!Z::constructed); + + return 0; + } + ''' + expect_generic_compile_error( + 'deprecation|deprecated', + COMMON_DEFINITIONS, + source) + +def test_eager_injection(): + source = ''' + fruit::Component<X> getComponent() { + return fruit::createComponent() + .addMultibindingProvider([](){return new Y();}) + .registerConstructor<Z()>(); + } + + int main() { + + fruit::Injector<X> injector(getComponent); + + Assert(!X::constructed); + Assert(!Y::constructed); + Assert(!Z::constructed); + + injector.eagerlyInjectAll(); + + Assert(X::constructed); + Assert(Y::constructed); + // Z still not constructed, it's not reachable from Injector<X>. + Assert(!Z::constructed); + + return 0; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals(), + ignore_deprecation_warnings=True) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_include_test.py b/tests/test_include_test.py new file mode 100644 index 0000000..b9fc3fb --- /dev/null +++ b/tests/test_include_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + ''' + +FRUIT_PUBLIC_HEADERS = [ + "component.h", + "fruit.h", + "fruit_forward_decls.h", + "injector.h", + "macro.h", + "normalized_component.h", + "provider.h", +] + +@pytest.mark.parametrize('HeaderFile', FRUIT_PUBLIC_HEADERS) +def test_header_self_contained(HeaderFile): + source = ''' + #include <fruit/HeaderFile> + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_injected_provider.py b/tests/test_injected_provider.py new file mode 100755 index 0000000..8754610 --- /dev/null +++ b/tests/test_injected_provider.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + ''' + +@pytest.mark.parametrize('XVariant,XVariantRegexp', [ + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('X&', 'X&'), + ('const X&', 'const X&'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), +]) +def test_error_non_class_type_parameter(XVariant, XVariantRegexp): + source = ''' + struct X {}; + + fruit::Provider<XVariant> provider; + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead', + COMMON_DEFINITIONS, + source, + locals()) + +def test_error_annotated_type_parameter(): + source = ''' + struct X {}; + + fruit::Provider<XAnnot> provider; + ''' + expect_compile_error( + 'AnnotatedTypeError<fruit::Annotated<Annotation1,X>,X>', + 'An annotated type was specified where a non-annotated type was expected.', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XBindingInInjector,XProviderAnnot,XParamInProvider,XProviderGetParam', [ + ('X', 'fruit::Provider<X>', 'X', 'X'), + ('X', 'fruit::Provider<X>', 'X', 'const X&'), + ('X', 'fruit::Provider<X>', 'X', 'const X*'), + ('X', 'fruit::Provider<X>', 'X', 'X&'), + ('X', 'fruit::Provider<X>', 'X', 'X*'), + ('X', 'fruit::Provider<X>', 'X', 'std::shared_ptr<X>'), + ('X', 'fruit::Provider<X>', 'X', 'fruit::Provider<X>'), + ('X', 'fruit::Provider<X>', 'X', 'fruit::Provider<const X>'), + ('X', 'fruit::Provider<const X>', 'const X', 'const X&'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, fruit::Provider<X>>', 'X', 'const X&'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, fruit::Provider<const X>>', 'const X', 'const X&'), +]) +def test_provider_get_ok(XBindingInInjector, XProviderAnnot, XParamInProvider, XProviderGetParam): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XBindingInInjector> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XBindingInInjector> injector(getComponent); + fruit::Provider<XParamInProvider> provider = injector.get<XProviderAnnot>(); + + XProviderGetParam x = provider.get<XProviderGetParam>(); + (void)x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XBindingInInjector,XProviderAnnot,XParamInProvider,XProviderGetParam', [ + ('const X', 'fruit::Provider<const X>', 'const X', 'X'), + ('const X', 'fruit::Provider<const X>', 'const X', 'const X&'), + ('const X', 'fruit::Provider<const X>', 'const X', 'const X*'), + ('const X', 'fruit::Provider<const X>', 'const X', 'fruit::Provider<const X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, fruit::Provider<const X>>', 'const X', 'const X&'), +]) +def test_provider_get_const_binding_ok(XBindingInInjector, XProviderAnnot, XParamInProvider, XProviderGetParam): + XBindingInInjectorWithoutConst = XBindingInInjector.replace('const ', '') + source = ''' + struct X {}; + + const X x{}; + + fruit::Component<XBindingInInjector> getComponent() { + return fruit::createComponent() + .bindInstance<XBindingInInjectorWithoutConst, X>(x); + } + + int main() { + fruit::Injector<XBindingInInjector> injector(getComponent); + fruit::Provider<XParamInProvider> provider = injector.get<XProviderAnnot>(); + + XProviderGetParam x = provider.get<XProviderGetParam>(); + (void)x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_provider_get_during_injection_ok(): + source = ''' + struct X { + INJECT(X()) = default; + void foo() { + } + }; + + struct Y { + X x; + INJECT(Y(fruit::Provider<X> xProvider)) + : x(xProvider.get<X>()) { + } + + void foo() { + x.foo(); + } + }; + + struct Z { + Y y; + INJECT(Z(fruit::Provider<Y> yProvider)) + : y(yProvider.get<Y>()) { + } + + void foo() { + y.foo(); + } + }; + + fruit::Component<Z> getZComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<Z> injector(getZComponent); + fruit::Provider<Z> provider(injector); + // During provider.get<Z>(), yProvider.get() is called, and during that xProvider.get() + // is called. + Z z = provider.get<Z>(); + z.foo(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_provider_get_error_type_not_provided(): + source = ''' + struct X {}; + struct Y {}; + + void f(fruit::Provider<X> provider) { + provider.get<Y>(); + } + ''' + expect_compile_error( + 'TypeNotProvidedError<Y>', + 'Trying to get an instance of T, but it is not provided by this Provider/Injector.', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X**', r'X\*\*'), + ('std::shared_ptr<X>*', r'std::shared_ptr<X>\*'), + ('const std::shared_ptr<X>', r'const std::shared_ptr<X>'), + ('X* const', r'X\* const'), + ('const X* const', r'const X\* const'), + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('X*&', r'X\*&'), + ('X(*)()', r'X(\((__cdecl)?\*\))?\((void)?\)'), + ('void', r'void'), + ('fruit::Annotated<Annotation1, fruit::Annotated<Annotation1, X>>', r'fruit::Annotated<Annotation1, X>'), +]) +def test_provider_get_error_type_not_injectable(XVariant,XVariantRegex): + source = ''' + struct X {}; + + void f(fruit::Provider<X> provider) { + provider.get<XVariant>(); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<XVariantRegex>', + 'The type T is not injectable', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XProviderGetParam,XProviderGetParamRegex', [ + ('X&', 'X&'), + ('X*', 'X\*'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), + ('fruit::Provider<X>', 'fruit::Provider<X>'), +]) +def test_const_provider_get_does_not_allow_injecting_nonconst_variants(XProviderGetParam, XProviderGetParamRegex): + source = ''' + void f(fruit::Provider<const X> provider) { + provider.get<XProviderGetParam>(); + } + ''' + expect_compile_error( + 'TypeProvidedAsConstOnlyError<XProviderGetParamRegex>', + 'Trying to get an instance of T, but it is only provided as a constant by this Provider/Injector', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('Y_PROVIDER_ANNOT', [ + ('fruit::Provider<Y>'), + ('ANNOTATED(Annotation1, fruit::Provider<Y>)'), +]) +def test_lazy_injection_with_annotations(Y_PROVIDER_ANNOT): + source = ''' + struct Y : public ConstructionTracker<Y> { + using Inject = Y(); + }; + + struct X : public ConstructionTracker<X> { + INJECT(X(Y_PROVIDER_ANNOT provider)) : provider(provider) { + } + + void run() { + Y* y(provider); + (void) y; + } + + fruit::Provider<Y> provider; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent); + fruit::Injector<X> injector(normalizedComponent, getComponent); + + Assert(X::num_objects_constructed == 0); + Assert(Y::num_objects_constructed == 0); + + X* x(injector); + + Assert(X::num_objects_constructed == 1); + Assert(Y::num_objects_constructed == 0); + + x->run(); + + Assert(X::num_objects_constructed == 1); + Assert(Y::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_injector.py b/tests/test_injector.py new file mode 100755 index 0000000..ed79b99 --- /dev/null +++ b/tests/test_injector.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + ''' + +def test_empty_injector(): + source = ''' + fruit::Component<> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<> injector(getComponent); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_error_component_with_requirements(XAnnot): + source = ''' + struct X {}; + + fruit::Component<fruit::Required<XAnnot>> getComponent(); + + void f(fruit::NormalizedComponent<XAnnot> normalizedComponent) { + fruit::Injector<XAnnot> injector(normalizedComponent, getComponent); + } + ''' + expect_compile_error( + 'ComponentWithRequirementsInInjectorError<XAnnot>', + 'When using the two-argument constructor of Injector, the component used as second parameter must not have requirements', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_error_declared_types_not_provided(XAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent); + fruit::Injector<XAnnot> injector(normalizedComponent, getEmptyComponent); + } + ''' + expect_compile_error( + 'TypesInInjectorNotProvidedError<XAnnot>', + 'The types in TypesNotProvided are declared as provided by the injector, but none of the two components passed to the Injector constructor provides them.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_error_declared_nonconst_types_provided_as_const(XAnnot, ConstXAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<ConstXAnnot> getComponent(); + + int main() { + fruit::Injector<XAnnot> injector(getComponent); + } + ''' + expect_generic_compile_error( + 'no matching constructor for initialization of .fruit::Injector<XAnnot>.' + '|no matching function for call to .fruit::Injector<XAnnot>::Injector\(fruit::Component<ConstXAnnot> \(&\)\(\)\).' + # MSVC + '|.fruit::Injector<XAnnot>::Injector.: none of the 2 overloads could convert all the argument types', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_error_declared_nonconst_types_provided_as_const_with_normalized_component(XAnnot, ConstXAnnot): + source = ''' + struct X {}; + + fruit::Component<> getEmptyComponent(); + + void f(fruit::NormalizedComponent<ConstXAnnot> normalizedComponent) { + fruit::Injector<XAnnot> injector(normalizedComponent, getEmptyComponent); + } + ''' + expect_compile_error( + 'TypesInInjectorProvidedAsConstOnlyError<XAnnot>', + 'The types in TypesProvidedAsConstOnly are declared as non-const provided types by the injector', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,YAnnot', [ + ('X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_injector_get_error_type_not_provided(XAnnot, YAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + struct Y {}; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XAnnot> injector(getComponent); + injector.get<YAnnot>(); + } + ''' + expect_compile_error( + 'TypeNotProvidedError<YAnnot>', + 'Trying to get an instance of T, but it is not provided by this Provider/Injector.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstXAnnot,XInjectorGetParam,XInjectorGetParamRegex', [ + ('const X', 'X&', 'X&'), + ('const X', 'X*', 'X\*'), + ('const X', 'std::shared_ptr<X>', 'std::shared_ptr<X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X&>', 'fruit::Annotated<Annotation1, X&>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X*>', 'fruit::Annotated<Annotation1, X\*>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, std::shared_ptr<X>>', 'fruit::Annotated<Annotation1, std::shared_ptr<X>>'), +]) +def test_injector_const_provided_type_does_not_allow_injecting_nonconst_variants(ConstXAnnot, XInjectorGetParam, XInjectorGetParamRegex): + source = ''' + void f(fruit::Injector<ConstXAnnot> injector) { + injector.get<XInjectorGetParam>(); + } + ''' + expect_compile_error( + 'TypeProvidedAsConstOnlyError<XInjectorGetParamRegex>', + 'Trying to get an instance of T, but it is only provided as a constant by this Provider/Injector', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XBindingInInjector,XInjectorGetParam', [ + ('X', 'X'), + ('X', 'const X&'), + ('X', 'const X*'), + ('X', 'X&'), + ('X', 'X*'), + ('X', 'std::shared_ptr<X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X&>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X*>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X&>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, std::shared_ptr<X>>'), +]) +def test_injector_get_ok(XBindingInInjector, XInjectorGetParam): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XBindingInInjector> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XBindingInInjector> injector(getComponent); + + auto x = injector.get<XInjectorGetParam>(); + (void)x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XBindingInInjector,XInjectorGetParam', [ + ('const X', 'X'), + ('const X', 'const X&'), + ('const X', 'const X*'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X&>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X*>'), +]) +def test_injector_get_const_binding_ok(XBindingInInjector, XInjectorGetParam): + XBindingInInjectorWithoutConst = XBindingInInjector.replace('const ', '') + source = ''' + struct X {}; + + const X x{}; + + fruit::Component<XBindingInInjector> getComponent() { + return fruit::createComponent() + .bindInstance<XBindingInInjectorWithoutConst, X>(x); + } + + int main() { + fruit::Injector<XBindingInInjector> injector(getComponent); + + auto x = injector.get<XInjectorGetParam>(); + (void)x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X**', r'X\*\*'), + ('std::shared_ptr<X>*', r'std::shared_ptr<X>\*'), + ('const std::shared_ptr<X>', r'const std::shared_ptr<X>'), + ('X* const', r'X\* const'), + ('const X* const', r'const X\* const'), + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('X*&', r'X\*&'), + ('X(*)()', r'X(\((__cdecl)?\*\))?\((void)?\)'), + ('void', r'void'), + ('fruit::Annotated<Annotation1, X**>', r'X\*\*'), +]) +def test_injector_get_error_type_not_injectable(XVariant,XVariantRegex): + source = ''' + struct X {}; + + void f(fruit::Injector<X> injector) { + injector.get<XVariant>(); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<XVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X[]', r'X\[\]'), +]) +def test_injector_get_error_array_type(XVariant,XVariantRegex): + source = ''' + struct X {}; + + void f(fruit::Injector<X> injector) { + injector.get<XVariant>(); + } + ''' + expect_generic_compile_error( + 'function cannot return array type' + '|function returning an array' + # MSVC + '|.fruit::Injector<X>::get.: no matching overloaded function found', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_injector_unsafe_get.py b/tests/test_injector_unsafe_get.py new file mode 100755 index 0000000..8768c63 --- /dev/null +++ b/tests/test_injector_unsafe_get.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + struct Annotation2 {}; + struct Annotation3 {}; + ''' + +@pytest.mark.parametrize('XAnnot,YAnnot,ZAnnot', [ + ('X', 'Y', 'Z'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation3, Z>'), +]) +def test_success(XAnnot, YAnnot, ZAnnot): + source = ''' + struct Y { + using Inject = Y(); + Y() = default; + }; + + struct X { + using Inject = X(YAnnot); + X(Y) { + } + }; + + struct Z {}; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .install(getComponent); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + const X* x = fruit::impl::InjectorAccessorForTests::unsafeGet<XAnnot>(injector); + const Y* y = fruit::impl::InjectorAccessorForTests::unsafeGet<YAnnot>(injector); + const Z* z = fruit::impl::InjectorAccessorForTests::unsafeGet<ZAnnot>(injector); + + (void) x; + (void) y; + (void) z; + Assert(x != nullptr); + Assert(y != nullptr); + Assert(z == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_install.py b/tests/test_install.py new file mode 100755 index 0000000..2901da1 --- /dev/null +++ b/tests/test_install.py @@ -0,0 +1,1067 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + ''' + +@pytest.mark.parametrize('XParamInChildComponent,XParamInRootComponent', [ + ('X', 'X'), + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_success(XParamInChildComponent, XParamInRootComponent): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + fruit::Component<XParamInChildComponent> getChildComponent() { + return fruit::createComponent() + .registerProvider<XParamInChildComponent()>([]() { return X(5); }); + } + + fruit::Component<XParamInRootComponent> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent); + } + + int main() { + fruit::Injector<XParamInRootComponent> injector(getRootComponent); + X x = injector.get<XParamInRootComponent>(); + Assert(x.n == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XParamInChildComponent,XParamInRootComponent', [ + ('const X', 'X'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), +]) +def test_install_error_child_component_provides_const(XParamInChildComponent, XParamInRootComponent): + source = ''' + struct X {}; + + fruit::Component<XParamInChildComponent> getChildComponent(); + + fruit::Component<XParamInRootComponent> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<XParamInRootComponent>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ProvidedXParam,RequiredXParam', [ + ('X', 'X'), + ('X', 'const X'), + ('const X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_with_requirements_success(ProvidedXParam, RequiredXParam): + ProvidedXParamWithoutConst = ProvidedXParam.replace('const ', '') + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Y { + X x; + Y(X x): x(x) {} + }; + + fruit::Component<fruit::Required<RequiredXParam>, Y> getChildComponent1() { + return fruit::createComponent() + .registerProvider<Y(RequiredXParam)>([](X x) { return Y(x); }); + } + + fruit::Component<ProvidedXParam> getChildComponent2() { + return fruit::createComponent() + .registerProvider<ProvidedXParamWithoutConst()>([]() { return X(5); }); + } + + fruit::Component<Y> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2); + } + + int main() { + fruit::Injector<Y> injector(getRootComponent); + Y y = injector.get<Y>(); + Assert(y.x.n == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ProvidedXParam,RequiredXParam', [ + ('const X', 'X'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), +]) +def test_with_requirements_error_only_nonconst_provided(ProvidedXParam, RequiredXParam): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<fruit::Required<RequiredXParam>, Y> getChildComponent1(); + + fruit::Component<ProvidedXParam> getChildComponent2(); + + fruit::Component<Y> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<RequiredXParam>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ProvidedXParam,RequiredXParam', [ + ('const X', 'X'), + ('fruit::Annotated<Annotation1, const X>', 'fruit::Annotated<Annotation1, X>'), +]) +def test_with_requirements_error_only_nonconst_provided_reversed_install_order(ProvidedXParam, RequiredXParam): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<fruit::Required<RequiredXParam>, Y> getChildComponent1(); + + fruit::Component<ProvidedXParam> getChildComponent2(); + + fruit::Component<Y> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent2) + .install(getChildComponent1); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<RequiredXParam>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +def test_with_requirements_not_specified_in_child_component_error(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Y { + X x; + Y(X x): x(x) {} + }; + + fruit::Component<fruit::Required<X>, Y> getParentYComponent() { + return fruit::createComponent() + .registerProvider([](X x) { return Y(x); }); + } + + // We intentionally don't have fruit::Required<X> here, we want to test that this results in an error. + fruit::Component<Y> getYComponent() { + return fruit::createComponent() + .install(getParentYComponent); + } + ''' + expect_compile_error( + 'NoBindingFoundError<X>', + 'No explicit binding nor C::Inject definition was found for T', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_install_requiring_nonconst_then_install_requiring_const_ok(XAnnot, ConstXAnnot): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<XAnnot>, Y> getChildComponent1() { + return fruit::createComponent() + .registerConstructor<Y()>(); + } + + fruit::Component<fruit::Required<ConstXAnnot>, Z> getChildComponent2() { + return fruit::createComponent() + .registerConstructor<Z()>(); + } + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2) + .registerConstructor<XAnnot()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_requiring_nonconst_then_install_requiring_const_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<X>, Y> getChildComponent1(); + fruit::Component<fruit::Required<const X>, Z> getChildComponent2(); + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_requiring_const_then_install_requiring_nonconst_ok(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, Y> getChildComponent1() { + return fruit::createComponent() + .registerConstructor<Y()>(); + } + + fruit::Component<fruit::Required<X>, Z> getChildComponent2() { + return fruit::createComponent() + .registerConstructor<Z()>(); + } + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_requiring_const_then_install_requiring_nonconst_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, Y> getChildComponent1(); + fruit::Component<fruit::Required<X>, Z> getChildComponent2(); + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent1) + .install(getChildComponent2); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_with_args_success(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&) { + return true; + } + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&) { + return 0; + } + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg, Arg) { + return fruit::createComponent() + .registerProvider([]() { return X(5); }); + } + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}, 15); + } + + int main() { + fruit::Injector<X> injector(getComponent); + X x = injector.get<X>(); + Assert(x.n == 5); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_install_with_args_error_not_move_constructible(): + source = ''' + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = delete; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg::Arg\(Arg&&\).' + + '|error: call to deleted constructor of .Arg.' + + '|.Arg::Arg\(Arg &&\).: cannot convert argument 1 from .std::_Tuple_val<_This>. to .const Arg &.', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_move_constructible_with_conversion(): + source = ''' + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = delete; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg::Arg\(Arg&&\).' + + '|error: call to deleted constructor of .Arg.' + + '|.Arg::Arg\(Arg &&\).: cannot convert argument 1 from .std::_Tuple_val<_This>. to .int.', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_copy_constructible(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg() = default; + Arg(const Arg&) = delete; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg::Arg\(const Arg&\).' + + '|error: call to deleted constructor of .Arg.' + + '|error C2280: .Arg::Arg\(const Arg &\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_copy_constructible_with_conversion(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = delete; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg::Arg\(const Arg&\).' + + '|error: call to deleted constructor of .Arg.' + + '|error C2280: .Arg::Arg\(const Arg &\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_move_assignable(): + source = ''' + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = delete; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg& Arg::operator=\(Arg&&\).' + + '|error: overload resolution selected deleted operator .=.' + + '|error C2280: .Arg &Arg::operator =\(Arg &&\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_move_assignable_with_conversion(): + source = ''' + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = delete; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg& Arg::operator=\(Arg&&\).' + + '|error: overload resolution selected deleted operator .=.' + + '|error C2280: .Arg &Arg::operator =\(Arg &&\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_copy_assignable(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = delete; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg& Arg::operator=\(const Arg&\).' + + '|error: overload resolution selected deleted operator .=.' + + '|error C2280: .Arg &Arg::operator =\(const Arg &\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_copy_assignable_with_conversion(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = delete; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .Arg& Arg::operator=\(const Arg&\).' + + '|error: overload resolution selected deleted operator .=.' + + '|error C2280: .Arg &Arg::operator =\(const Arg &\).: attempting to reference a deleted function', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_equality_comparable(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: no match for .operator==. \(operand types are .const Arg. and .const Arg.\)' + + '|error: invalid operands to binary expression \(.const Arg. and .const Arg.\)' + + '|error C2676: binary .==.: .const Arg. does not define this operator', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_equality_comparable_with_conversion(): + source = ''' + struct X { + int n; + X(int n) : n(n) {} + }; + + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + namespace std { + template <> + struct hash<Arg> { + size_t operator()(const Arg&); + }; + } + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: no match for .operator==. \(operand types are .const Arg. and .const Arg.\)' + + '|error: invalid operands to binary expression \(.const Arg. and .const Arg.\)' + + '|error C2676: binary .==.: .const Arg. does not define this operator', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_hashable(): + source = ''' + struct Arg { + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), Arg{}); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .std::hash<Arg>::hash\(\).' + + '|error: call to implicitly-deleted default constructor of .std::hash<Arg>.' + + '|error: invalid use of incomplete type .struct std::hash<Arg>.' + + '|error: implicit instantiation of undefined template .std::(__1::)?hash<Arg>.' + + '|error C2338: The C\+\+ Standard doesn.t provide a hash for this type.' + + '|error C2064: term does not evaluate to a function taking 1 arguments', + COMMON_DEFINITIONS, + source) + +def test_install_with_args_error_not_hashable_with_conversion(): + source = ''' + struct Arg { + Arg(int) {} + Arg() = default; + Arg(const Arg&) = default; + Arg(Arg&&) = default; + Arg& operator=(const Arg&) = default; + Arg& operator=(Arg&&) = default; + }; + + bool operator==(const Arg&, const Arg&); + + fruit::Component<X> getParentComponent(int, std::string, Arg); + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getParentComponent, 5, std::string("Hello"), 15); + } + ''' + expect_generic_compile_error( + 'error: use of deleted function .std::hash<Arg>::hash\(\).' + + '|error: call to implicitly-deleted default constructor of .std::hash<Arg>.' + + '|error: invalid use of incomplete type .struct std::hash<Arg>.' + + '|error: implicit instantiation of undefined template .std::(__1::)?hash<Arg>.' + + '|error C2338: The C\+\+ Standard doesn.t provide a hash for this type.' + + '|error C2064: term does not evaluate to a function taking 1 arguments', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_install_component_functions_deduped(XAnnot): + source = ''' + struct X {}; + + X x; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + + fruit::Component<> getComponent2() { + return fruit::createComponent() + .install(getComponent); + } + + fruit::Component<> getComponent3() { + return fruit::createComponent() + .install(getComponent); + } + + fruit::Component<> getComponent4() { + return fruit::createComponent() + .install(getComponent2) + .install(getComponent3); + } + + int main() { + fruit::Injector<> injector(getComponent4); + + // We test multibindings because the effect on other bindings is not user-visible (that only affects + // performance). + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 1); + Assert(multibindings[0] == &x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_install_component_functions_deduped_across_normalized_component(XAnnot): + source = ''' + struct X {}; + + X x; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + + fruit::Component<> getComponent2() { + return fruit::createComponent() + .install(getComponent); + } + + fruit::Component<> getComponent3() { + return fruit::createComponent() + .install(getComponent); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getComponent2); + fruit::Injector<> injector(normalizedComponent, getComponent3); + + // We test multibindings because the effect on other bindings is not user-visible (that only affects + // performance). + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 1); + Assert(multibindings[0] == &x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_install_component_functions_with_args_deduped(XAnnot): + source = ''' + struct X {}; + + X x; + + fruit::Component<> getComponent(int) { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + + fruit::Component<> getComponent2() { + return fruit::createComponent() + .install(getComponent, 1); + } + + fruit::Component<> getComponent3() { + return fruit::createComponent() + .install(getComponent, 1); + } + + fruit::Component<> getComponent4() { + return fruit::createComponent() + .install(getComponent2) + .install(getComponent3); + } + + int main() { + fruit::Injector<> injector(getComponent4); + + // We test multibindings because the effect on other bindings is not user-visible (that only affects + // performance). + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 1); + Assert(multibindings[0] == &x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_install_component_functions_different_args_not_deduped(XAnnot): + source = ''' + struct X {}; + + X x; + + fruit::Component<> getComponent(int) { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + + fruit::Component<> getComponent2() { + return fruit::createComponent() + .install(getComponent, 1); + } + + fruit::Component<> getComponent3() { + return fruit::createComponent() + .install(getComponent, 2); + } + + fruit::Component<> getComponent4() { + return fruit::createComponent() + .install(getComponent2) + .install(getComponent3); + } + + int main() { + fruit::Injector<> injector(getComponent4); + + // We test multibindings because the effect on other bindings is not user-visible (it only affects + // performance). + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 2); + Assert(multibindings[0] == &x); + Assert(multibindings[1] == &x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_component_functions_loop(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + // X -> Y -> Z -> Y + + fruit::Component<X> getXComponent(); + fruit::Component<Y> getYComponent(); + fruit::Component<Z> getZComponent(); + + fruit::Component<X> getXComponent() { + return fruit::createComponent() + .registerConstructor<X()>() + .install(getYComponent); + } + + fruit::Component<Y> getYComponent() { + return fruit::createComponent() + .registerConstructor<Y()>() + .install(getZComponent); + } + + fruit::Component<Z> getZComponent() { + return fruit::createComponent() + .registerConstructor<Z()>() + .install(getYComponent); + } + + int main() { + fruit::Injector<X> injector(getXComponent); + (void)injector; + } + ''' + expect_runtime_error( + 'Component installation trace \(from top-level to the most deeply-nested\):\n' + + '(class )?fruit::Component<(struct )?X> ?\((__cdecl)?\*\)\((void)?\)\n' + + '<-- The loop starts here\n' + + '(class )?fruit::Component<(struct )?Y> ?\((__cdecl)?\*\)\((void)?\)\n' + + '(class )?fruit::Component<(struct )?Z> ?\((__cdecl)?\*\)\((void)?\)\n' + + '(class )?fruit::Component<(struct )?Y> ?\((__cdecl)?\*\)\((void)?\)\n', + COMMON_DEFINITIONS, + source, + locals()) + +def test_install_component_functions_different_arguments_loop_not_reported(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + // X -> Y(1) -> Z -> Y(2) + + fruit::Component<X> getXComponent(); + fruit::Component<Y> getYComponent(int); + fruit::Component<Z> getZComponent(); + + fruit::Component<X> getXComponent() { + return fruit::createComponent() + .registerConstructor<X()>() + .install(getYComponent, 1); + } + + fruit::Component<Y> getYComponent(int n) { + if (n == 1) { + return fruit::createComponent() + .registerConstructor<Y()>() + .install(getZComponent); + } else { + return fruit::createComponent() + .registerConstructor<Y()>(); + } + } + + fruit::Component<Z> getZComponent() { + return fruit::createComponent() + .registerConstructor<Z()>() + .install(getYComponent, 2); + } + + int main() { + fruit::Injector<X> injector(getXComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_macros.h b/tests/test_macros.h new file mode 100644 index 0000000..8d850e1 --- /dev/null +++ b/tests/test_macros.h @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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 FRUIT_TEST_MACROS +#define FRUIT_TEST_MACROS + +#include <iostream> + +#define Assert(...) \ + do { \ + if (!(__VA_ARGS__)) { \ + std::cerr << __FILE__ << ":" << __LINE__ << ": " << __func__ << ": Assertion \"" << #__VA_ARGS__ << "\" failed." \ + << std::endl; \ + abort(); \ + } \ + } while (false) + +#define InstantiateType(...) \ + void f() { \ + (void)sizeof(__VA_ARGS__); \ + } + +#endif // FRUIT_TEST_MACROS diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 0000000..0cd6bec --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + template <typename T> + class V {}; + + template <typename T> + class X { + private: + X() {} + + public: + INJECT(X(ASSISTED(int))) { + } + }; + + using XFactory = std::function<X<V<float>>(int)>; + ''' + +def test_misc(): + source = ''' + fruit::Component<X<V<float>>> getXProvider2() { + return fruit::createComponent() + .registerProvider([](){return X<V<float>>(1);}); + } + + struct AssistedMultiparamExample { + INJECT(AssistedMultiparamExample(ASSISTED(std::map<int, float>))) {} + }; + + struct Implementation1 { + bool constructed = true; + + Implementation1(V<int>&&, XFactory) { + std::cout << "Called Implementation1() for object " << this << std::endl; + } + + Implementation1() = delete; + Implementation1(const Implementation1&) = delete; + + Implementation1& operator=(const Implementation1&) = delete; + Implementation1& operator=(Implementation1&&) = delete; + + Implementation1(Implementation1&&) { + std::cout << "Moving an Implementation1 into object" << this << std::endl; + } + + ~Implementation1() { + std::cout << "Called ~Implementation1() for object " << this << std::endl; + constructed = 0; + } + + int x; + }; + + struct Interface2 { + virtual void f() = 0; + }; + + struct Implementation2 : public Interface2 { + INJECT(Implementation2(std::function<Implementation1(int)>)) { + std::cout << "Called Implementation2()" << std::endl; + } + + virtual ~Implementation2() {} + + virtual void f() {}; + }; + + fruit::Component<Interface2, XFactory, std::function<Implementation1(int)>> getParentComponent() { + return fruit::createComponent() + .registerFactory<Implementation1(fruit::Assisted<int>, XFactory)>( + [](int, XFactory xFactory) { + return Implementation1(V<int>(), xFactory); + }) + .bind<Interface2, Implementation2>(); + } + + //************************************* + + struct Interface3 { + virtual void f() = 0; + }; + + struct Implementation3 : public Interface3 { + INJECT(Implementation3(Implementation2*, fruit::Provider<Implementation2> provider)) { + (void) provider.get(); + std::cout << "Called Implementation2()" << std::endl; + } + + virtual ~Implementation3() {} + + virtual void f() {}; + }; + + fruit::Component<Interface3, std::function<Implementation1(int)>> getMyComponent() { + return fruit::createComponent() + // Must fail at runtime. + // .install(getXProvider2) + .bind<Interface3, Implementation3>() + .install(getParentComponent); + } + + fruit::Component<std::function<AssistedMultiparamExample(std::map<int, float>)>> getAssistedMultiparamExampleComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector< + Interface3, + // XFactory, + std::function<Implementation1(int)> + > oldInjector(getMyComponent); + + // The move is completely unnecessary, it's just to check that it works. + fruit::Injector< + Interface3, + // XFactory, + std::function<Implementation1(int)> + > injector(std::move(oldInjector)); + + std::cout << "Constructing an Interface3" << std::endl; + Interface3* interface3(injector); + std::cout << std::endl; + (void) interface3; + + std::cout << "Constructing another Interface3" << std::endl; + Interface3* interface3_obj2 = injector.get<Interface3*>(); + std::cout << std::endl; + (void) interface3_obj2; + + std::function<Implementation1(int)> implementation1Factory(injector); + { + std::cout << "Constructing another Implementation1" << std::endl; + Implementation1 implementation1 = implementation1Factory(12); + (void) implementation1; + } + std::cout << "Destroying injector" << std::endl; + + fruit::Injector<std::function<AssistedMultiparamExample(std::map<int, float>)>> assistedMultiparamExampleInjector( + getAssistedMultiparamExampleComponent); + + return 0; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_multibindings_bind_instance.py b/tests/test_multibindings_bind_instance.py new file mode 100755 index 0000000..634a627 --- /dev/null +++ b/tests/test_multibindings_bind_instance.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + ''' + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_multibindings_bind_instance_ok(XAnnot): + source = ''' + struct X {}; + + X x; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + + int main() { + fruit::Injector<> injector(getComponent); + + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 1); + Assert(multibindings[0] == &x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_multibindings_bind_const_instance_error(XAnnot): + source = ''' + struct X {}; + + const X x{}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibinding<XAnnot, X>(x); + } + ''' + expect_generic_compile_error( + 'candidate function not viable: 1st argument \(.const X.\) would lose const qualifier' + '|no matching function for call to .fruit::PartialComponent<.*>::addInstanceMultibinding(<XAnnot,X>)?\(const X&\).' + '|error: no matching member function for call to .addInstanceMultibinding.' + '|cannot convert argument 1 from .const X. to .X &.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_multibindings_bind_instance_vector(XAnnot): + source = ''' + struct X {}; + + std::vector<X> values = {X(), X()}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibindings<XAnnot, X>(values); + } + + int main() { + fruit::Injector<> injector(getComponent); + + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 2); + Assert(multibindings[0] == &(values[0])); + Assert(multibindings[1] == &(values[1])); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_multibindings_bind_const_instance_vector_error(XAnnot): + source = ''' + struct X {}; + + const std::vector<X> values{}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibindings<XAnnot, X>(values); + } + ''' + expect_generic_compile_error( + 'candidate function not viable: 1st argument \(.const std::vector<X>.\) would lose const qualifier' + '|cannot convert .values. \(type .const std::(__debug::)?vector<X>.\) to type .std::(__debug::)?vector<X>&.' + '|no matching member function for call to .addInstanceMultibindings.' + '|cannot convert argument 1 from .const std::vector<X,std::allocator<.*>>. to .std::vector<X,std::allocator<.*>> &.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_multibindings_bind_instance_vector_of_consts_error(XAnnot): + source = ''' + struct X {}; + + std::vector<const X> values; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addInstanceMultibindings<XAnnot, X>(values); + } + ''' + expect_generic_compile_error( + '.*', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X**', r'X\*\*'), + ('std::shared_ptr<X>*', r'std::shared_ptr<X>\*'), + ('const std::shared_ptr<X>', r'const std::shared_ptr<X>'), + ('X* const', r'X\* const'), + ('const X* const', r'const X\* const'), + ('X*&', r'X\*&'), + ('fruit::Annotated<Annotation1, X**>', r'X\*\*'), +]) +def test_multibindings_bind_instance_non_class_type_error(XVariant, XVariantRegex): + source = ''' + struct X {}; + + using XVariantT = XVariant; + fruit::Component<> getComponent(XVariantT x) { + return fruit::createComponent() + .addInstanceMultibinding<XVariant, XVariant>(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegex,X>', + 'A non-class type T was specified.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('X(*)()', r'X(\((__cdecl)?\*\))?\((void)?\)'), +]) +def test_multibindings_bind_instance_non_injectable_type_error(XVariant, XVariantRegex): + source = ''' + struct X {}; + + using XVariantT = XVariant; + fruit::Component<> getComponent(XVariantT x) { + return fruit::createComponent() + .addInstanceMultibinding<XVariant, XVariant>(x); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<XVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_multibindings_bind_interface.py b/tests/test_multibindings_bind_interface.py new file mode 100755 index 0000000..90c4678 --- /dev/null +++ b/tests/test_multibindings_bind_interface.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * +from fruit_test_config import CXX_COMPILER_NAME +import re + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation {}; + struct Annotation1 {}; + struct Annotation2 {}; + ''' + +@pytest.mark.parametrize('XAnnot,XImplAnnot', [ + ('X', 'XImpl'), + ('X', 'fruit::Annotated<Annotation2, XImpl>'), + ('fruit::Annotated<Annotation1, X>', 'XImpl'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, XImpl>'), +]) +def test_add_interface_multibinding_success(XAnnot, XImplAnnot): + source = ''' + struct X { + virtual int foo() = 0; + }; + + struct XImpl : public X { + INJECT(XImpl()) = default; + + int foo() override { + return 5; + } + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibinding<XAnnot, XImplAnnot>(); + } + + int main() { + fruit::Injector<> injector(getComponent); + + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 1); + Assert(multibindings[0]->foo() == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XImplAnnot,ConstXImplAnnot', [ + ('X', 'XImpl', 'const XImpl'), + ('X', 'fruit::Annotated<Annotation2, XImpl>', 'fruit::Annotated<Annotation2, const XImpl>'), +]) +def test_add_interface_multibinding_const_target_error_install_first(XAnnot, XImplAnnot, ConstXImplAnnot): + source = ''' + struct X { + virtual int foo() = 0; + }; + + struct XImpl : public X { + int foo() override { + return 5; + } + }; + + fruit::Component<ConstXImplAnnot> getXImplComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getXImplComponent) + .addMultibinding<XAnnot, XImplAnnot>(); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<XImplAnnot>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XImplAnnot,ConstXImplAnnot', [ + ('X', 'XImpl', 'const XImpl'), + ('X', 'fruit::Annotated<Annotation2, XImpl>', 'fruit::Annotated<Annotation2, const XImpl>'), +]) +def test_add_interface_multibinding_const_target_error_binding_first(XAnnot, XImplAnnot, ConstXImplAnnot): + source = ''' + struct X { + virtual int foo() = 0; + }; + + struct XImpl : public X { + int foo() override { + return 5; + } + }; + + fruit::Component<ConstXImplAnnot> getXImplComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibinding<XAnnot, XImplAnnot>() + .install(getXImplComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<XImplAnnot>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,intAnnot', [ + ('X', 'int'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, int>'), +]) +def test_error_not_base(XAnnot, intAnnot): + source = ''' + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibinding<XAnnot, intAnnot>(); + } + ''' + expect_compile_error( + 'NotABaseClassOfError<X,int>', + 'I is not a base class of C.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot', [ + ('Scaler', 'ScalerImpl'), + ('fruit::Annotated<Annotation1, Scaler>', 'fruit::Annotated<Annotation2, ScalerImpl>'), +]) +def test_error_abstract_class(ScalerAnnot, ScalerImplAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + // Note: here we "forgot" to implement scale() (on purpose, for this test) so ScalerImpl is an abstract class. + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibinding<ScalerAnnot, ScalerImplAnnot>(); + } + ''' + expect_compile_error( + 'NoBindingFoundForAbstractClassError<ScalerImplAnnot,ScalerImpl>', + 'No explicit binding was found for T, and note that C is an abstract class', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot', [ + ('Scaler', 'ScalerImpl'), + ('fruit::Annotated<Annotation1, Scaler>', 'fruit::Annotated<Annotation2, ScalerImpl>'), +]) +@pytest.mark.skipif( + re.search('Clang', CXX_COMPILER_NAME) is None, + reason = 'This is Clang-only because GCC >=4.9 refuses to even mention the type C() when C is an abstract class, ' + 'while Clang allows to mention the type (but of course there can be no functions with this type)') +def test_error_abstract_class_clang(ScalerAnnot, ScalerImplAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + INJECT(ScalerImpl()) = default; + + // Note: here we "forgot" to implement scale() (on purpose, for this test) so ScalerImpl is an abstract class. + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibinding<ScalerAnnot, ScalerImplAnnot>(); + } + ''' + expect_compile_error( + 'CannotConstructAbstractClassError<ScalerImpl>', + 'The specified class can.t be constructed because it.s an abstract class.', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_multibindings_bind_provider.py b/tests/test_multibindings_bind_provider.py new file mode 100755 index 0000000..9008c2f --- /dev/null +++ b/tests/test_multibindings_bind_provider.py @@ -0,0 +1,569 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + struct Annotation2 {}; + + template <typename T> + using WithNoAnnot = T; + + template <typename T> + using WithAnnot1 = fruit::Annotated<Annotation1, T>; + ''' + +@pytest.mark.parametrize('ConstructX', [ + 'X()', + 'new X()', +]) +def test_bind_multibinding_provider_success(ConstructX): + source = ''' + struct X : public ConstructionTracker<X> { + INJECT(X()) = default; + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider([](){return ConstructX;}); + } + + int main() { + fruit::Injector<> injector(getComponent); + + Assert(X::num_objects_constructed == 0); + Assert(injector.getMultibindings<X>().size() == 1); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +def test_bind_multibinding_provider_abstract_class_success(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + virtual ~I() = default; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<WithAnnot<I*>()>([](){return static_cast<I*>(new X());}); + } + + int main() { + fruit::Injector<> injector(getComponent); + + Assert(injector.getMultibindings<WithAnnot<I>>().size() == 1); + Assert(injector.getMultibindings<WithAnnot<I>>()[0]->foo() == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +def test_bind_multibinding_provider_abstract_class_with_no_virtual_destructor_error(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<WithAnnot<I*>()>([](){return static_cast<I*>(new X());}); + } + ''' + expect_compile_error( + 'MultibindingProviderReturningPointerToAbstractClassWithNoVirtualDestructorError<I>', + 'registerMultibindingProvider\(\) was called with a lambda that returns a pointer to T, but T is an abstract class with no virtual destructor', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'Y*', + 'const Y*', + 'Y&', + 'const Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', + 'fruit::Provider<const Y>', +]) +def test_bind_multibinding_provider_with_param_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + + struct X : public ConstructionTracker<X> {}; + + fruit::Component<WithAnnot<Y>> getYComponent() { + return fruit::createComponent() + .registerConstructor<WithAnnot<Y>()>(); + } + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .addMultibindingProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<> injector(getComponent); + + Assert(X::num_objects_constructed == 0); + Assert(injector.getMultibindings<X>().size() == 1); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'const Y*', + 'const Y&', + 'fruit::Provider<const Y>', +]) +def test_bind_multibinding_provider_with_param_const_binding_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + + struct X : public ConstructionTracker<X> {}; + + const Y y{}; + + fruit::Component<WithAnnot<const Y>> getYComponent() { + return fruit::createComponent() + .bindInstance<WithAnnot<Y>, Y>(y); + } + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .addMultibindingProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<> injector(getComponent); + + Assert(X::num_objects_constructed == 0); + Assert(injector.getMultibindings<X>().size() == 1); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnot', 'Y'), + ('WithAnnot1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_bind_multibinding_provider_with_param_error_nonconst_param_required(ConstructX, XPtr, WithAnnot, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .addMultibindingProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnot', 'Y'), + ('WithAnnot1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_bind_multibinding_provider_with_param_error_nonconst_param_required_install_after(ConstructX, XPtr, WithAnnot, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }) + .install(getYComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +def test_bind_multibinding_provider_requiring_nonconst_then_requiring_const_ok(): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .addMultibindingProvider([](X&) { return Y(); }) + .addMultibindingProvider([](const X&) { return Y(); }) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + injector.getMultibindings<Y>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_bind_multibinding_provider_requiring_nonconst_then_requiring_const_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<fruit::Required<const X>> getRootComponent() { + return fruit::createComponent() + .addMultibindingProvider([](X&) { return Y(); }) + .addMultibindingProvider([](const X&) { return Y(); }); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_bind_multibinding_provider_requiring_const_then_requiring_nonconst_ok(): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .addMultibindingProvider([](const X&) { return Y(); }) + .addMultibindingProvider([](X&) { return Y(); }) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + injector.getMultibindings<Y>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_bind_multibinding_provider_requiring_const_then_requiring_nonconst_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<fruit::Required<const X>> getRootComponent() { + return fruit::createComponent() + .addMultibindingProvider([](const X&) { return Y(); }) + .addMultibindingProvider([](X&) { return Y(); }); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('YAnnot,ConstYAnnot,YVariant,YVariantRegex', [ + ('Y', 'Y', 'Y**', 'Y\*\*'), + ('Y', 'Y', 'std::shared_ptr<Y>*', 'std::shared_ptr<Y>\*'), + ('Y', 'const Y', 'Y**', 'Y\*\*'), + ('Y', 'const Y', 'std::shared_ptr<Y>*', 'std::shared_ptr<Y>\*'), + ('fruit::Annotated<Annotation1, Y>', 'fruit::Annotated<Annotation1, Y>', 'Y**', 'Y\*\*'), + ('fruit::Annotated<Annotation1, Y>', 'fruit::Annotated<Annotation1, const Y>', 'Y**', 'Y\*\*'), +]) +def test_bind_multibinding_provider_with_param_error_type_not_injectable(ConstructX, XPtr, YAnnot, ConstYAnnot, YVariant, YVariantRegex): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XPtr(YVariant)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<YVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XAnnot,XPtrAnnot', [ + ('X()', 'X', 'X'), + ('X()', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('new X()', 'X', 'X*'), + ('new X()', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>'), +]) +def test_bind_multibinding_provider_explicit_signature_success(ConstructX, XAnnot, XPtrAnnot): + source = ''' + struct X : public ConstructionTracker<X> { + INJECT(X()) = default; + + static bool constructed; + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XPtrAnnot()>([](){return ConstructX;}); + } + + int main() { + fruit::Injector<> injector(getComponent); + + Assert(X::num_objects_constructed == 0); + Assert(injector.getMultibindings<XAnnot>().size() == 1); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XAnnot,XPtrAnnot', [ + ('X()', 'X', 'X'), + ('X()', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>'), + ('new X()', 'X', 'X*'), + ('new X()', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>'), +]) +def test_bind_multibinding_provider_explicit_signature_with_normalized_component_success(ConstructX, XAnnot, XPtrAnnot): + source = ''' + struct X : public ConstructionTracker<X> { + INJECT(X()) = default; + + static bool constructed; + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XPtrAnnot()>([](){return ConstructX;}); + } + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getComponent); + fruit::Injector<> injector(normalizedComponent, getEmptyComponent); + + Assert(X::num_objects_constructed == 0); + Assert(injector.getMultibindings<XAnnot>().size() == 1); + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XPtrAnnot,intAnnot', [ + ('X', 'X*', 'int'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>', 'fruit::Annotated<Annotation2, int>'), +]) +def test_multiple_providers(XAnnot, XPtrAnnot, intAnnot): + source = ''' + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerProvider<intAnnot()>([](){return 42;}) + .addMultibindingProvider<XAnnot(intAnnot)>([](int){return X();}) + .addMultibindingProvider<XPtrAnnot(intAnnot)>([](int){return new X();}); + } + + int main() { + fruit::Injector<> injector(getComponent); + + std::vector<X*> multibindings = injector.getMultibindings<XAnnot>(); + Assert(multibindings.size() == 2); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX', [ + 'X()', + 'new X()', +]) +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_bind_multibinding_provider_malformed_signature(ConstructX, XAnnot): + source = ''' + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XAnnot>([](){return ConstructX;}); + } + ''' + expect_compile_error( + 'NotASignatureError<XAnnot>', + 'CandidateSignature was specified as parameter, but it.s not a signature.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX', [ + 'X(n)', + 'new X(n)', +]) +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_bind_multibinding_provider_lambda_with_captures_error(ConstructX, XAnnot): + source = ''' + struct X { + X(int) {} + }; + + fruit::Component<> getComponent() { + int n = 3; + return fruit::createComponent() + .addMultibindingProvider<XAnnot()>([=]{return ConstructX;}); + } + ''' + expect_compile_error( + 'FunctorUsedAsProviderError<.*>', + 'A stateful lambda or a non-lambda functor was used as provider', + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: should XPtrAnnot be just XAnnot in the signature? +# Make sure the behavior here is consistent with registerProvider() and registerFactory(). +@pytest.mark.parametrize('XAnnot,XPtrAnnot,XAnnotRegex', [ + ('X', 'X*', '(struct )?X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), +]) +def test_provider_returns_nullptr_error(XAnnot, XPtrAnnot, XAnnotRegex): + source = ''' + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .addMultibindingProvider<XPtrAnnot()>([](){return (X*)nullptr;}); + } + + int main() { + fruit::Injector<> injector(getComponent); + injector.getMultibindings<XAnnot>(); + } + ''' + expect_runtime_error( + 'Fatal injection error: attempting to get an instance for the type XAnnotRegex but the provider returned nullptr', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_multibindings_misc.py b/tests/test_multibindings_misc.py new file mode 100755 index 0000000..5b85700 --- /dev/null +++ b/tests/test_multibindings_misc.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Listener; + + struct X {}; + + struct Annotation {}; + struct Annotation1 {}; + using ListenerAnnot = fruit::Annotated<Annotation, Listener>; + ''' + +def test_get_none(): + source = ''' + fruit::Component<> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<> injector(getComponent); + + std::vector<X*> multibindings = injector.getMultibindings<X>(); + (void) multibindings; + Assert(multibindings.empty()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_multiple_various_kinds(): + source = ''' + static int numNotificationsToListener1 = 0; + static int numNotificationsToListener2 = 0; + static int numNotificationsToListener3 = 0; + + struct Listener { + public: + virtual ~Listener() = default; + + virtual void notify() = 0; + }; + + struct Listener1 : public Listener { + public: + INJECT(Listener1()) = default; + + virtual ~Listener1() = default; + + void notify() override { + ++numNotificationsToListener1; + } + }; + + struct Writer { + public: + virtual void write(std::string s) = 0; + }; + + struct StdoutWriter : public Writer { + public: + INJECT(StdoutWriter()) = default; + + void write(std::string s) override { + std::cout << s << std::endl; + } + }; + + struct Listener2 : public Listener { + private: + Writer* writer; + + public: + INJECT(Listener2(Writer* writer)) + : writer(writer) { + } + + virtual ~Listener2() = default; + + void notify() override { + (void) writer; + ++numNotificationsToListener2; + } + }; + + struct Listener3 : public Listener { + private: + Writer* writer; + + public: + INJECT(Listener3(Writer* writer)) + : writer(writer) { + } + + virtual ~Listener3() = default; + + void notify() override { + (void) writer; + ++numNotificationsToListener3; + } + }; + + fruit::Component<> getListenersComponent() { + return fruit::createComponent() + .bind<Writer, StdoutWriter>() + // Note: this is just to exercise the other method, but in real code you should split this in + // an addMultibinding<Listener, Listener1> and a registerProvider with the lambda. + .addMultibindingProvider([]() { + Listener1* listener1 = new Listener1(); + return static_cast<Listener*>(listener1); + }) + .addMultibinding<Listener, Listener2>() + .addMultibinding<ListenerAnnot, Listener3>(); + } + + int main() { + fruit::Injector<> injector(getListenersComponent); + std::vector<Listener*> listeners = injector.getMultibindings<Listener>(); + for (Listener* listener : listeners) { + listener->notify(); + } + + std::vector<Listener*> listeners2 = injector.getMultibindings<Listener>(); + Assert(listeners == listeners2); + + if (numNotificationsToListener1 != 1 || numNotificationsToListener2 != 1 + || numNotificationsToListener3 != 0) { + abort(); + } + + std::vector<Listener*> listenersWithAnnotation = injector.getMultibindings<ListenerAnnot>(); + for (Listener* listener : listenersWithAnnotation) { + listener->notify(); + } + + if (numNotificationsToListener1 != 1 || numNotificationsToListener2 != 1 + || numNotificationsToListener3 != 1) { + abort(); + } + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_order(): + source = ''' + std::vector<int> numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + // * + // |-- 0 + // |-- A + // | |-- 1 + // | |-- B + // | | |-- 2 + // | | `-- 3 + // | |-- 4 + // | |-- C + // | | |-- 5 + // | | |-- 6 + // | | |-- D + // | | | |-- 7 + // | | | |-- E + // | | | | |-- 8 + // | | | | `-- 9 + // | | | `-- 10 + // | | |-- 11 + // | | |-- F + // | | | |-- 12 + // | | | `-- 13 + // | | `-- 14 + // | |-- 15 + // | |-- C (won't be expanded) + // | `-- 16 + // |-- 17 + // |-- C (won't be expanded) + // `-- 18 + + fruit::Component<> getRootComponent(); + fruit::Component<> getComponentA(); + fruit::Component<> getComponentB(); + fruit::Component<> getComponentC(); + fruit::Component<> getComponentD(); + fruit::Component<> getComponentE(); + fruit::Component<> getComponentF(); + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[0]) + .install(getComponentA) + .addInstanceMultibinding(numbers[17]) + .install(getComponentC) + .addInstanceMultibinding(numbers[18]); + } + + fruit::Component<> getComponentA() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[1]) + .install(getComponentB) + .addInstanceMultibinding(numbers[4]) + .install(getComponentC) + .addInstanceMultibinding(numbers[15]) + .install(getComponentC) + .addInstanceMultibinding(numbers[16]); + } + + fruit::Component<> getComponentB() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[2]) + .addInstanceMultibinding(numbers[3]); + } + + fruit::Component<> getComponentC() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[5]) + .addInstanceMultibinding(numbers[6]) + .install(getComponentD) + .addInstanceMultibinding(numbers[11]) + .install(getComponentF) + .addInstanceMultibinding(numbers[14]); + } + + fruit::Component<> getComponentD() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[7]) + .install(getComponentE) + .addInstanceMultibinding(numbers[10]); + } + + fruit::Component<> getComponentE() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[8]) + .addInstanceMultibinding(numbers[9]); + } + + fruit::Component<> getComponentF() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[12]) + .addInstanceMultibinding(numbers[13]); + } + + int main() { + fruit::Injector<> injector(getRootComponent); + std::vector<int*> result_ptrs = injector.getMultibindings<int>(); + std::vector<int> results; + std::cout << "Results: "; + for (int* result : result_ptrs) { + std::cout << *result << ", "; + results.push_back(*result); + } + std::cout << std::endl; + Assert(results == numbers); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + + +def test_order_with_normalized_component(): + source = ''' + std::vector<int> numbers = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37}; + // root1 + // |-- 0 + // |-- A + // | |-- 1 + // | |-- B + // | | |-- 2 + // | | `-- 3 + // | |-- 4 + // | |-- C + // | | |-- 5 + // | | |-- 6 + // | | |-- D + // | | | |-- 7 + // | | | |-- E + // | | | | |-- 8 + // | | | | `-- 9 + // | | | `-- 10 + // | | |-- 11 + // | | |-- F + // | | | |-- 12 + // | | | `-- 13 + // | | `-- 14 + // | |-- 15 + // | |-- C (won't be expanded) + // | `-- 16 + // |-- 17 + // |-- C (won't be expanded) + // `-- 18 + + // root2 + // |-- 19 + // |-- A2 + // | |-- 20 + // | |-- B2 + // | | |-- 21 + // | | `-- 22 + // | |-- 23 + // | |-- C2 + // | | |-- 24 + // | | |-- 25 + // | | |-- D2 + // | | | |-- 26 + // | | | |-- E2 + // | | | | |-- 27 + // | | | | `-- 28 + // | | | `-- 29 + // | | |-- 30 + // | | |-- F2 + // | | | |-- 31 + // | | | `-- 32 + // | | `-- 33 + // | |-- 34 + // | |-- C2 (won't be expanded) + // | `-- 35 + // |-- 36 + // |-- C2 (won't be expanded) + // `-- 37 + + fruit::Component<> getRootComponent(); + fruit::Component<> getComponentA(); + fruit::Component<> getComponentB(); + fruit::Component<> getComponentC(); + fruit::Component<> getComponentD(); + fruit::Component<> getComponentE(); + fruit::Component<> getComponentF(); + + fruit::Component<> getRootComponent2(); + fruit::Component<> getComponentA2(); + fruit::Component<> getComponentB2(); + fruit::Component<> getComponentC2(); + fruit::Component<> getComponentD2(); + fruit::Component<> getComponentE2(); + fruit::Component<> getComponentF2(); + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[0]) + .install(getComponentA) + .addInstanceMultibinding(numbers[17]) + .install(getComponentC) + .addInstanceMultibinding(numbers[18]); + } + + fruit::Component<> getComponentA() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[1]) + .install(getComponentB) + .addInstanceMultibinding(numbers[4]) + .install(getComponentC) + .addInstanceMultibinding(numbers[15]) + .install(getComponentC) + .addInstanceMultibinding(numbers[16]); + } + + fruit::Component<> getComponentB() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[2]) + .addInstanceMultibinding(numbers[3]); + } + + fruit::Component<> getComponentC() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[5]) + .addInstanceMultibinding(numbers[6]) + .install(getComponentD) + .addInstanceMultibinding(numbers[11]) + .install(getComponentF) + .addInstanceMultibinding(numbers[14]); + } + + fruit::Component<> getComponentD() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[7]) + .install(getComponentE) + .addInstanceMultibinding(numbers[10]); + } + + fruit::Component<> getComponentE() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[8]) + .addInstanceMultibinding(numbers[9]); + } + + fruit::Component<> getComponentF() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[12]) + .addInstanceMultibinding(numbers[13]); + } + + fruit::Component<> getRootComponent2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[19]) + .install(getComponentA2) + .addInstanceMultibinding(numbers[36]) + .install(getComponentC2) + .addInstanceMultibinding(numbers[37]); + } + + fruit::Component<> getComponentA2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[20]) + .install(getComponentB2) + .addInstanceMultibinding(numbers[23]) + .install(getComponentC2) + .addInstanceMultibinding(numbers[34]) + .install(getComponentC2) + .addInstanceMultibinding(numbers[35]); + } + + fruit::Component<> getComponentB2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[21]) + .addInstanceMultibinding(numbers[22]); + } + + fruit::Component<> getComponentC2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[24]) + .addInstanceMultibinding(numbers[25]) + .install(getComponentD2) + .addInstanceMultibinding(numbers[30]) + .install(getComponentF2) + .addInstanceMultibinding(numbers[33]); + } + + fruit::Component<> getComponentD2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[26]) + .install(getComponentE2) + .addInstanceMultibinding(numbers[29]); + } + + fruit::Component<> getComponentE2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[27]) + .addInstanceMultibinding(numbers[28]); + } + + fruit::Component<> getComponentF2() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[31]) + .addInstanceMultibinding(numbers[32]); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent); + fruit::Injector<> injector(normalizedComponent, getRootComponent2); + std::vector<int*> result_ptrs = injector.getMultibindings<int>(); + std::vector<int> results; + std::cout << "Results: "; + for (int* result : result_ptrs) { + std::cout << *result << ", "; + results.push_back(*result); + } + std::cout << std::endl; + Assert(results == numbers); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_with_normalized_component_lazy_components_not_deduped_across(): + source = ''' + std::vector<int> numbers = {0, 1, 2, 3, 4}; + + // * + // |-- 0 + // |-- A (lazy) + // | |-- 1 + // | `-- 2 + // |-- 3 + // |-- A (lazy, won't be expanded) + // `-- 4 + + fruit::Component<> getRootComponent(); + fruit::Component<> getComponentA(); + + fruit::Component<> getRootComponent() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[0]) + .install(getComponentA) + .addInstanceMultibinding(numbers[3]) + .install(getComponentA) + .addInstanceMultibinding(numbers[4]); + } + + fruit::Component<> getComponentA() { + return fruit::createComponent() + .addInstanceMultibinding(numbers[1]) + .addInstanceMultibinding(numbers[2]); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getRootComponent); + fruit::Injector<> injector(normalizedComponent, getRootComponent); + std::vector<int*> result_ptrs = injector.getMultibindings<int>(); + std::vector<int> results; + std::cout << "Results: "; + for (int* result : result_ptrs) { + std::cout << *result << ", "; + results.push_back(*result); + } + std::cout << std::endl; + std::vector<int> expected_numbers = {0, 1, 2, 3, 4}; + Assert(results == expected_numbers); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XVariantAnnot,XVariantRegexp', [ + ('const X', 'const X'), + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), + ('fruit::Annotated<Annotation1, const X>', 'const X'), + ('fruit::Annotated<Annotation1, X*>', 'X\*'), + ('fruit::Annotated<Annotation1, const X*>', 'const X\*'), + ('fruit::Annotated<Annotation1, std::shared_ptr<X>>', 'std::shared_ptr<X>'), +]) +def test_multibindings_get_error_non_class_type(XVariantAnnot, XVariantRegexp): + source = ''' + void f(fruit::Injector<> injector) { + injector.getMultibindings<XVariantAnnot>(); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariantAnnot,XVariantRegexp', [ + ('X&', 'X&'), + ('const X&', 'const X&'), + ('fruit::Annotated<Annotation1, X&>', 'X&'), + ('fruit::Annotated<Annotation1, const X&>', 'const X&'), +]) +def test_multibindings_get_error_reference_type(XVariantAnnot, XVariantRegexp): + source = ''' + void f(fruit::Injector<> injector) { + injector.getMultibindings<XVariantAnnot>(); + } + ''' + expect_generic_compile_error( + 'declared as a pointer to a reference of type' + '|forming pointer to reference type' + '|fruit::Injector<.*>::getMultibindings.: no matching overloaded function found', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_normalized_component.py b/tests/test_normalized_component.py new file mode 100755 index 0000000..4d0025d --- /dev/null +++ b/tests/test_normalized_component.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + using XAnnot2 = fruit::Annotated<Annotation2, X>; + ''' + +@pytest.mark.parametrize('XAnnot,X_ANNOT,YAnnot', [ + ('X', 'X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'ANNOTATED(Annotation1, X)', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_success_normalized_component_provides_unused(XAnnot, X_ANNOT, YAnnot): + source = ''' + struct X {}; + + struct Y { + INJECT(Y(X_ANNOT)) {}; + }; + + fruit::Component<fruit::Required<XAnnot>, YAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<XAnnot> getXComponent(X* x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + int main() { + fruit::NormalizedComponent<fruit::Required<XAnnot>, YAnnot> normalizedComponent(getComponent); + + X x{}; + + fruit::Injector<XAnnot> injector(normalizedComponent, getXComponent, &x); + injector.get<XAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,X_ANNOT,YAnnot', [ + ('X', 'X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'ANNOTATED(Annotation1, X)', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_success(XAnnot, X_ANNOT, YAnnot): + source = ''' + struct X {}; + + struct Y { + INJECT(Y(X_ANNOT)) {}; + }; + + fruit::Component<fruit::Required<XAnnot>, YAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<XAnnot> getXComponent(X* x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + int main() { + fruit::NormalizedComponent<fruit::Required<XAnnot>, YAnnot> normalizedComponent(getComponent); + + X x{}; + + fruit::Injector<YAnnot> injector(normalizedComponent, getXComponent, &x); + injector.get<YAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,X_ANNOT,YAnnot', [ + ('X', 'X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'ANNOTATED(Annotation1, X)', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_success_inline_component(XAnnot, X_ANNOT, YAnnot): + source = ''' + struct X {}; + + struct Y { + INJECT(Y(X_ANNOT)) {}; + }; + + fruit::Component<fruit::Required<XAnnot>, YAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<XAnnot> getAdditionalComponent(X* x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + int main() { + fruit::NormalizedComponent<fruit::Required<XAnnot>, YAnnot> normalizedComponent(getComponent); + + X x{}; + + fruit::Injector<YAnnot> injector(normalizedComponent, getAdditionalComponent, &x); + injector.get<YAnnot>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_injector_from_normalized_component_unsatisfied_requirements(XAnnot): + source = ''' + struct X {}; + + fruit::Component<fruit::Required<XAnnot>> getComponent(); + fruit::Component<> getEmptyComponent(); + + int main() { + fruit::NormalizedComponent<fruit::Required<XAnnot>> normalizedComponent(getComponent); + fruit::Injector<> injector(normalizedComponent, getEmptyComponent); + } + ''' + expect_compile_error( + 'UnsatisfiedRequirementsInNormalizedComponentError<XAnnot>', + 'The requirements in UnsatisfiedRequirements are required by the NormalizedComponent but are not provided by the Component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,ConstXAnnot', [ + ('X', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>'), +]) +def test_normalized_component_providing_nonconst_from_component_providing_const_error(XAnnot, ConstXAnnot): + source = ''' + struct X {}; + + fruit::Component<XAnnot> getComponent(); + + int main() { + fruit::NormalizedComponent<ConstXAnnot> normalizedComponent(getComponent); + (void) normalizedComponent; + } + ''' + expect_generic_compile_error( + 'no matching function for call to .fruit::NormalizedComponent<ConstXAnnot>::NormalizedComponent\(fruit::Component<XAnnot> \(&\)\(\)\).' + '|no matching constructor for initialization of .fruit::NormalizedComponent<ConstXAnnot>.' + '|.fruit::NormalizedComponent<ConstXAnnot>::NormalizedComponent.: none of the 2 overloads could convert all the argument types', + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: we should probably return a more specific error here. +@pytest.mark.parametrize('XAnnot,YAnnot', [ + ('X', 'Y'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>'), +]) +def test_injector_from_normalized_component_nonconst_requirements_provided_as_const_error(XAnnot, YAnnot): + source = ''' + struct X {}; + struct Y {}; + + fruit::Component<const XAnnot> getXComponent(); + + void f(fruit::NormalizedComponent<fruit::Required<XAnnot>, YAnnot> normalizedComponent) { + fruit::Injector<YAnnot> injector(normalizedComponent, getXComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<XAnnot>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_register_constructor.py b/tests/test_register_constructor.py new file mode 100755 index 0000000..3333075 --- /dev/null +++ b/tests/test_register_constructor.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot = fruit::Annotated<Annotation1, X>; + + struct Annotation2 {}; + + struct Annotation3 {}; + + template <typename T> + using WithNoAnnotation = T; + + template <typename T> + using WithAnnotation1 = fruit::Annotated<Annotation1, T>; + ''' + +def test_register_constructor_success_copyable_and_movable(): + source = ''' + struct X { + INJECT(X()) = default; + X(X&&) = default; + X(const X&) = default; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_register_constructor_success_movable_only(): + source = ''' + struct X { + INJECT(X()) = default; + X(X&&) = default; + X(const X&) = delete; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_register_constructor_success_not_movable(): + source = ''' + struct X { + INJECT(X()) = default; + X(X&&) = delete; + X(const X&) = delete; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +# TODO: consider moving to test_normalized_component.py +@pytest.mark.parametrize('XAnnot,YAnnot,MaybeConstYAnnot,ZAnnot', [ + ('X', 'Y', 'Y', 'Z'), + ('X', 'Y', 'const Y', 'Z'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation2, Y>', 'fruit::Annotated<Annotation2, const Y>', 'fruit::Annotated<Annotation3, Z>'), +]) +def test_autoinject_with_annotation_success(XAnnot, YAnnot, MaybeConstYAnnot, ZAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + struct Y : public ConstructionTracker<Y> { + using Inject = Y(); + }; + + struct Z { + using Inject = Z(); + }; + + fruit::Component<ZAnnot, MaybeConstYAnnot, XAnnot> getComponent() { + return fruit::createComponent(); + } + + fruit::Component<> getEmptyComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::NormalizedComponent<> normalizedComponent(getEmptyComponent); + fruit::Injector<MaybeConstYAnnot> injector(normalizedComponent, getComponent); + + Assert(Y::num_objects_constructed == 0); + injector.get<YAnnot>(); + Assert(Y::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_autoinject_annotation_in_signature_return_type(): + source = ''' + struct X { + using Inject = XAnnot(); + }; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'InjectTypedefWithAnnotationError<X>', + 'C::Inject is a signature that returns an annotated type', + COMMON_DEFINITIONS, + source) + +def test_autoinject_wrong_class_in_typedef(): + source = ''' + struct X { + using Inject = X(); + }; + + struct Y : public X { + }; + + fruit::Component<Y> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'InjectTypedefForWrongClassError<Y,X>', + 'C::Inject is a signature, but does not return a C. Maybe the class C has no Inject typedef and', + COMMON_DEFINITIONS, + source) + +def test_register_constructor_error_abstract_class(): + source = ''' + struct X { + X(int*) {} + + virtual void foo() = 0; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .registerConstructor<fruit::Annotated<Annotation1, X>(int*)>(); + } + ''' + if re.search('GNU|MSVC', CXX_COMPILER_NAME) is not None: + expect_generic_compile_error( + 'invalid abstract return type' + '|.X.: cannot instantiate abstract class', + COMMON_DEFINITIONS, + source) + else: + expect_compile_error( + 'CannotConstructAbstractClassError<X>', + 'The specified class can.t be constructed because it.s an abstract class', + COMMON_DEFINITIONS, + source) + +def test_register_constructor_error_malformed_signature(): + source = ''' + struct X { + X(int) {} + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .registerConstructor<X[]>(); + } + ''' + expect_compile_error( + 'NotASignatureError<X\[\]>', + 'CandidateSignature was specified as parameter, but it.s not a signature. Signatures are of the form', + COMMON_DEFINITIONS, + source) + +def test_register_constructor_error_malformed_signature_autoinject(): + source = ''' + struct X { + using Inject = X[]; + X(int) {} + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'InjectTypedefNotASignatureError<X,X\[\]>', + 'C::Inject should be a typedef to a signature', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('charPtrAnnot', [ + 'char*', + 'fruit::Annotated<Annotation1, char*>', +]) +def test_register_constructor_does_not_exist_error(charPtrAnnot): + source = ''' + struct X { + X(int*) {} + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .registerConstructor<X(charPtrAnnot)>(); + } + ''' + expect_compile_error( + 'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>', + 'contains an Inject typedef but it.s not constructible with the specified types', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('charPtrAnnot', [ + 'char*', + 'fruit::Annotated<Annotation1, char*>', +]) +def test_autoinject_constructor_does_not_exist_error(charPtrAnnot): + source = ''' + struct X { + using Inject = X(charPtrAnnot); + X(int*) {} + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'NoConstructorMatchingInjectSignatureError<X,X\(char\*\)>', + 'contains an Inject typedef but it.s not constructible with the specified types', + COMMON_DEFINITIONS, + source, + locals()) + +def test_autoinject_abstract_class_error(): + source = ''' + struct X { + using Inject = fruit::Annotated<Annotation1, X>(); + + virtual void scale() = 0; + // Note: here we "forgot" to implement scale() (on purpose, for this test) so X is an abstract class. + }; + + fruit::Component<fruit::Annotated<Annotation1, X>> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'CannotConstructAbstractClassError<X>', + 'The specified class can.t be constructed because it.s an abstract class.', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('WithAnnotation', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'Y*', + 'const Y*', + 'Y&', + 'const Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', + 'fruit::Provider<const Y>', +]) +def test_register_constructor_with_param_success(WithAnnotation, YVariant): + source = ''' + struct Y {}; + struct X { + X(YVariant) { + } + }; + + fruit::Component<WithAnnotation<Y>> getYComponent() { + return fruit::createComponent() + .registerConstructor<WithAnnotation<Y>()>(); + } + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerConstructor<X(WithAnnotation<YVariant>)>(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnotation', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'const Y*', + 'const Y&', + 'fruit::Provider<const Y>', +]) +def test_register_constructor_with_param_const_binding_success(WithAnnotation, YVariant): + source = ''' + struct Y {}; + struct X { + X(YVariant) { + } + }; + + const Y y{}; + + fruit::Component<WithAnnotation<const Y>> getYComponent() { + return fruit::createComponent() + .bindInstance<WithAnnotation<Y>, Y>(y); + } + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerConstructor<X(WithAnnotation<YVariant>)>(); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [ + ('WithNoAnnotation', 'Y'), + ('WithAnnotation1', 'fruit::Annotated<Annotation1,Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_constructor_with_param_error_nonconst_param_required(WithAnnotation, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X { + X(YVariant); + }; + + fruit::Component<WithAnnotation<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerConstructor<X(WithAnnotation<YVariant>)>(); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnotation,YAnnotRegex', [ + ('WithNoAnnotation', 'Y'), + ('WithAnnotation1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_constructor_with_param_error_nonconst_param_required_install_after(WithAnnotation, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X { + X(YVariant); + }; + + fruit::Component<WithAnnotation<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerConstructor<X(WithAnnotation<YVariant>)>() + .install(getYComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_constructor_requiring_nonconst_then_requiring_const_ok(): + source = ''' + struct X {}; + + struct Y { + Y(X&) {} + }; + + struct Z { + Z(const X&) {} + }; + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .registerConstructor<Y(X&)>() + .registerConstructor<Z(const X&)>() + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_constructor_requiring_nonconst_then_requiring_const_declaring_const_requirement_error(): + source = ''' + struct X {}; + + struct Y { + Y(X&) {} + }; + + struct Z { + Z(const X&) {} + }; + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .registerConstructor<Y(X&)>() + .registerConstructor<Z(const X&)>(); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_constructor_requiring_const_then_requiring_nonconst_ok(): + source = ''' + struct X {}; + + struct Y { + Y(const X&) {} + }; + + struct Z { + Z(X&) {} + }; + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .registerConstructor<Y(const X&)>() + .registerConstructor<Z(X&)>() + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_constructor_requiring_const_then_requiring_nonconst_declaring_const_requirement_error(): + source = ''' + struct X {}; + + struct Y { + Y(const X&) {} + }; + + struct Z { + Z(X&) {} + }; + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .registerConstructor<Y(const X&)>() + .registerConstructor<Z(X&)>(); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('YVariant,YVariantRegex', [ + ('Y**', r'Y\*\*'), + ('std::shared_ptr<Y>*', r'std::shared_ptr<Y>\*'), + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('Y*&', r'Y\*&'), + ('Y(*)()', r'Y(\((__cdecl)?\*\))?\((void)?\)'), + ('fruit::Annotated<Annotation1, Y**>', r'Y\*\*'), +]) +def test_register_constructor_with_param_error_type_not_injectable(YVariant, YVariantRegex): + source = ''' + struct Y {}; + struct X { + X(YVariant); + }; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerConstructor<X(YVariant)>(); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<YVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_register_factory.py b/tests/test_register_factory.py new file mode 100755 index 0000000..02d1169 --- /dev/null +++ b/tests/test_register_factory.py @@ -0,0 +1,2320 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * +from fruit_test_config import CXX_COMPILER_NAME + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Scaler; + struct ScalerImpl; + + struct Annotation1 {}; + using ScalerAnnot1 = fruit::Annotated<Annotation1, Scaler>; + using ScalerImplAnnot1 = fruit::Annotated<Annotation1, ScalerImpl>; + + struct Annotation2 {}; + using ScalerAnnot2 = fruit::Annotated<Annotation2, Scaler>; + using ScalerImplAnnot2 = fruit::Annotated<Annotation2, ScalerImpl>; + + template <typename T> + using WithNoAnnotation = T; + + template <typename T> + using WithAnnotation1 = fruit::Annotated<Annotation1, T>; + ''' + +@pytest.mark.parametrize('XFactoryAnnot', [ + 'std::function<X()>', + 'fruit::Annotated<Annotation1, std::function<X()>>', +]) +def test_register_factory_success_no_params_autoinject(XFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + }; + + fruit::Component<XFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XFactoryAnnot> injector(getComponent); + injector.get<XFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtrAnnot,XPtrFactoryAnnot', [ + ('X()', 'X', 'std::function<X()>'), + ('X()', 'fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, std::function<X()>>'), + ('std::unique_ptr<X>()', 'std::unique_ptr<X>', 'std::function<std::unique_ptr<X>()>'), + ('std::unique_ptr<X>()', 'fruit::Annotated<Annotation1, std::unique_ptr<X>>', 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>()>>'), +]) +def test_register_factory_success_no_params(ConstructX, XPtrAnnot, XPtrFactoryAnnot): + source = ''' + struct X {}; + + fruit::Component<XPtrFactoryAnnot> getComponent() { + return fruit::createComponent() + .registerFactory<XPtrAnnot()>([](){return ConstructX;}); + } + + int main() { + fruit::Injector<XPtrFactoryAnnot> injector(getComponent); + injector.get<XPtrFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +def test_register_factory_autoinject_success(MaybeConst): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + INJECT(ScalerImpl(ASSISTED(double) factor)) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<MaybeConst ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>(); + } + + int main() { + fruit::Injector<MaybeConst ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_autoinject_abstract_class_with_no_virtual_destructor_error(): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + public: + INJECT(ScalerImpl(ASSISTED(double))) { + } + + double scale(double x) override { + return x; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>(); + } + ''' + expect_compile_error( + 'FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorError<std::function<std::unique_ptr<Scaler(,std::default_delete<Scaler>)?>\(double\)>,std::function<std::unique_ptr<ScalerImpl(,std::default_delete<ScalerImpl>)?>\(double\)>>', + 'Fruit was trying to bind BaseFactory to DerivedFactory but the return type of BaseFactory is a std::unique_ptr of a class with no virtual destructor', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_autoinject_non_abstract_class_with_no_virtual_destructor_error(): + source = ''' + struct Scaler { + }; + + struct ScalerImpl : public Scaler { + public: + INJECT(ScalerImpl(ASSISTED(double))) { + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>(); + } + ''' + expect_compile_error( + 'FactoryBindingForUniquePtrOfClassWithNoVirtualDestructorError<std::function<std::unique_ptr<Scaler(,std::default_delete<Scaler>)?>\(double\)>,std::function<std::unique_ptr<ScalerImpl(,std::default_delete<ScalerImpl>)?>\(double\)>>', + 'Fruit was trying to bind BaseFactory to DerivedFactory but the return type of BaseFactory is a std::unique_ptr of a class with no virtual destructor', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerFactoryAnnot,MaybeConstScalerFactoryAnnot', [ + ('Scaler', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'std::function<std::unique_ptr<Scaler>(double)>'), + ('Scaler', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'const std::function<std::unique_ptr<Scaler>(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation1, const std::function<std::unique_ptr<Scaler>(double)>>'), +]) +def test_autoinject(ScalerAnnot, ScalerFactoryAnnot, MaybeConstScalerFactoryAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + INJECT(ScalerImpl(ASSISTED(double) factor)) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<MaybeConstScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImpl>(); + } + + int main() { + fruit::Injector<MaybeConstScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +def test_autoinject_returning_value(MaybeConst): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + private: + double factor; + + public: + INJECT(Scaler(ASSISTED(double) factor, X)) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<MaybeConst ScalerFactory> getScalerComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<MaybeConst ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerFactoryAnnot,ScalerImplFactoryAnnotRegex', [ + ('Scaler', + 'ScalerImpl', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'std::function<std::unique_ptr<ScalerImpl(,std::default_delete<ScalerImpl>)?>\(double\)>', + ), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation2,std::function<std::unique_ptr<ScalerImpl(,std::default_delete<ScalerImpl>)?>\(double\)>>', + ), +]) +def test_autoinject_error_abstract_class(ScalerAnnot, ScalerImplAnnot, ScalerFactoryAnnot, ScalerImplFactoryAnnotRegex): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + // Note: here we "forgot" to implement scale() (on purpose, for this test) so ScalerImpl is an abstract class. + }; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>(); + } + ''' + expect_compile_error( + 'NoBindingFoundForAbstractClassError<ScalerImplFactoryAnnotRegex,ScalerImpl>', + 'No explicit binding was found for T, and note that C is an abstract class', + COMMON_DEFINITIONS, + source, + locals()) + +def test_autoinject_nonmovable_ok(): + source = ''' + struct I { + virtual ~I() = default; + }; + + struct C : public I { + INJECT(C()) = default; + + C(const C&) = delete; + C(C&&) = delete; + C& operator=(const C&) = delete; + C& operator=(C&&) = delete; + }; + + using IFactory = std::function<std::unique_ptr<I>()>; + + fruit::Component<IFactory> getIFactory() { + return fruit::createComponent() + .bind<I, C>(); + } + + int main() { + fruit::Injector<IFactory> injector(getIFactory); + IFactory iFactory(injector); + std::unique_ptr<I> i = iFactory(); + (void)i; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_2_assisted_params(): + source = ''' + struct Foo { + Foo(int x, float y) { + (void)x; + (void)y; + } + }; + + using FooFactory = std::function<Foo(int, float)>; + + fruit::Component<FooFactory> getComponent() { + return fruit::createComponent() + .registerFactory<Foo(fruit::Assisted<int>, fruit::Assisted<float>)>( + [](int x, float y) { + return Foo(x, y); + }); + } + + int main() { + fruit::Injector<FooFactory> injector(getComponent); + FooFactory fooFactory(injector); + Foo foo = fooFactory(1, 2.3f); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_2_assisted_params_returning_value(): + source = ''' + struct Foo { + Foo(int x, float y) { + (void)x; + (void)y; + } + }; + + using FooFactory = std::function<Foo(int, float)>; + + fruit::Component<FooFactory> getComponent() { + return fruit::createComponent() + .registerFactory<Foo(fruit::Assisted<int>, fruit::Assisted<float>)>( + [](int x, float y) { + return Foo(x, y); + }); + } + + int main() { + fruit::Injector<FooFactory> injector(getComponent); + FooFactory fooFactory(injector); + Foo foo = fooFactory(1, 2.3f); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_instances_bound_to_assisted_params(): + source = ''' + struct X {}; + struct Y {}; + + struct Foo { + Foo(X x, Y y) { + (void)x; + (void)y; + } + }; + + using FooFactory = std::function<Foo()>; + + fruit::Component<FooFactory> getComponent() { + static X x = X(); + static Y y = Y(); + return fruit::createComponent() + .bindInstance(x) + .bindInstance(y) + .registerFactory<Foo(X, Y)>( + [](X x, Y y) { + return Foo(x, y); + }); + } + + + int main() { + fruit::Injector<FooFactory> injector(getComponent); + FooFactory fooFactory(injector); + Foo foo = fooFactory(); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_2_assisted_params_plus_nonassisted_params(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + struct Foo { + Foo(X, Y, int, float, Z) { + } + }; + + using FooPtrFactory = std::function<std::unique_ptr<Foo>(int, float)>; + + fruit::Component<FooPtrFactory> getComponent() { + static X x = X(); + static Y y = Y(); + static Z z = Z(); + return fruit::createComponent() + .bindInstance(x) + .bindInstance(y) + .bindInstance(z) + .registerFactory<std::unique_ptr<Foo>(X, Y, fruit::Assisted<int>, fruit::Assisted<float>, Z)>( + [](X x, Y y, int n, float a, Z z) { + return std::unique_ptr<Foo>(new Foo(x, y, n, a, z)); + }); + } + + int main() { + fruit::Injector<FooPtrFactory> injector(getComponent); + FooPtrFactory fooPtrFactory(injector); + std::unique_ptr<Foo> foo = fooPtrFactory(1, 3.4f); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_2_assisted_params_plus_nonassisted_params_returning_value(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + struct Foo { + Foo(X, Y, int, float, Z) { + } + }; + + using FooFactory = std::function<Foo(int, float)>; + + fruit::Component<FooFactory> getComponent() { + static X x = X(); + static Y y = Y(); + static Z z = Z(); + return fruit::createComponent() + .bindInstance(x) + .bindInstance(y) + .bindInstance(z) + .registerFactory<Foo(X, Y, fruit::Assisted<int>, fruit::Assisted<float>, Z)>( + [](X x, Y y, int n, float a, Z z) { + return Foo(x, y, n, a, z); + }); + } + + int main() { + fruit::Injector<FooFactory> injector(getComponent); + FooFactory fooFactory(injector); + Foo foo = fooFactory(1, 3.4f); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_mixed_assisted_and_injected_params(): + source = ''' + struct X {}; + struct Y {}; + + struct Foo { + Foo(int, float, X, Y, double) { + } + }; + + using FooFactory = std::function<Foo(int, float, double)>; + + fruit::Component<FooFactory> getComponent() { + static X x = X(); + static Y y = Y(); + return fruit::createComponent() + .bindInstance(x) + .bindInstance(y) + .registerFactory<Foo(fruit::Assisted<int>, fruit::Assisted<float>, X, Y, fruit::Assisted<double>)>( + [](int n, float a, X x, Y y, double d) { + return Foo(n, a, x, y, d); + }); + } + + int main() { + fruit::Injector<FooFactory> injector(getComponent); + FooFactory fooFactory(injector); + Foo foo = fooFactory(1, 3.4f, 3.456); + (void)foo; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_annotation_in_signature_return_type(): + source = ''' + struct X { + using Inject = fruit::Annotated<Annotation1, X>(); + }; + + fruit::Component<fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>()>>> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'InjectTypedefWithAnnotationError<X>', + 'C::Inject is a signature that returns an annotated type', + COMMON_DEFINITIONS, + source) + +def test_autoinject_annotation_in_signature_return_type_returning_value(): + source = ''' + struct X { + using Inject = fruit::Annotated<Annotation1, X>(); + }; + + fruit::Component<fruit::Annotated<Annotation1, std::function<X()>>> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'InjectTypedefWithAnnotationError<X>', + 'C::Inject is a signature that returns an annotated type', + COMMON_DEFINITIONS, + source) + +def test_autoinject_from_provider_simple(): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor, X) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .registerProvider([](X x) { + return std::function<std::unique_ptr<ScalerImpl>(double)>([x](double n){ + return std::unique_ptr<ScalerImpl>(new ScalerImpl(n, x)); + }); + }) + .bind<Scaler, ScalerImpl>(); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('ScalerAnnot,ScalerFactoryAnnot,MaybeConstScalerFactoryAnnot,ScalerImplAnnot,ScalerImplFactoryAnnot', [ + ('Scaler', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'ScalerImpl', + 'std::function<std::unique_ptr<ScalerImpl>(double)>'), + ('Scaler', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'const std::function<std::unique_ptr<Scaler>(double)>', + 'ScalerImpl', + 'std::function<std::unique_ptr<ScalerImpl>(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation2, std::function<std::unique_ptr<ScalerImpl>(double)>>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation1, const std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation2, std::function<std::unique_ptr<ScalerImpl>(double)>>'), +]) +def test_autoinject_from_provider(ScalerAnnot, ScalerFactoryAnnot, MaybeConstScalerFactoryAnnot, ScalerImplAnnot, ScalerImplFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor, X) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + using ScalerImplFactory = std::function<std::unique_ptr<ScalerImpl>(double)>; + + fruit::Component<MaybeConstScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .registerProvider<ScalerImplFactoryAnnot(X)>([](X x) { + return std::function<std::unique_ptr<ScalerImpl>(double)>([x](double n){ + return std::unique_ptr<ScalerImpl>(new ScalerImpl(n, x)); + }); + }) + .bind<ScalerAnnot, ScalerImplAnnot>(); + } + + int main() { + fruit::Injector<MaybeConstScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerFactoryAnnot', [ + 'ScalerFactory', + 'fruit::Annotated<Annotation1, ScalerFactory>', +]) +def test_autoinject_from_provider_returning_value(ScalerFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + private: + double factor; + + public: + Scaler(double factor, X) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .registerProvider<ScalerFactoryAnnot(X)>([](X x) { + return std::function<Scaler(double)>([x](double n){ + return Scaler(n, x); + }); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +@pytest.mark.parametrize('X_ANNOT', [ + 'X', + 'ANNOTATED(Annotation1, X)', +]) +def test_autoinject_with_binding(MaybeConst, X_ANNOT): + source = ''' + struct X { + using Inject = X(); + }; + + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + INJECT(ScalerImpl(ASSISTED(double) factor, X_ANNOT x)) + : factor(factor) { + (void)x; + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<MaybeConst ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>(); + } + + int main() { + fruit::Injector<MaybeConst ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('MaybeConst', [ + '', + 'const', +]) +@pytest.mark.parametrize('X_ANNOT', [ + 'X', + 'ANNOTATED(Annotation1, X)', +]) +def test_autoinject_with_binding_returning_value(MaybeConst, X_ANNOT): + source = ''' + struct X { + using Inject = X(); + }; + + struct Scaler { + private: + double factor; + + public: + INJECT(Scaler(ASSISTED(double) factor, X_ANNOT x)) + : factor(factor) { + (void)x; + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<MaybeConst ScalerFactory> getScalerComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<MaybeConst ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_autoinject_with_binding2(): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + INJECT(ScalerImpl(X, ASSISTED(double) factor)) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>(); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_autoinject_with_binding2_returning_value(): + source = ''' + struct X { + INJECT(X()) = default; + }; + + struct Scaler { + private: + double factor; + + public: + INJECT(Scaler(X, ASSISTED(double) factor)) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerFactoryAnnot', [ + ('Scaler', + 'ScalerImpl', + 'std::function<std::unique_ptr<Scaler>(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>'), +]) +def test_register_factory_success(ScalerAnnot, ScalerImplAnnot, ScalerFactoryAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>() + .registerFactory<ScalerImplAnnot(fruit::Assisted<double>)>([](double factor) { return ScalerImpl(factor); }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_with_annotation_returning_value(): + source = ''' + struct Scaler { + private: + double factor; + + public: + Scaler(double factor) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + using ScalerFactoryAnnot1 = fruit::Annotated<Annotation1, ScalerFactory>; + + fruit::Component<ScalerFactoryAnnot1> getScalerComponent() { + return fruit::createComponent() + .registerFactory<ScalerAnnot1(fruit::Assisted<double>)>( + [](double factor) { + return Scaler(factor); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot1> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot1>(); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_register_factory_with_different_annotation(): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + using ScalerFactoryAnnot1 = fruit::Annotated<Annotation1, ScalerFactory>; + + fruit::Component<ScalerFactoryAnnot1> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot1, ScalerImplAnnot2>() + .registerFactory<ScalerImplAnnot2(fruit::Assisted<double>)>( + [](double factor) { + return ScalerImpl(factor); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot1> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot1>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerFactoryAnnot', [ + ('Scaler', + 'ScalerImpl', + 'std::function<std::unique_ptr<Scaler>(double, double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double, double)>>'), +]) +def test_register_factory_2arg_success(ScalerAnnot, ScalerImplAnnot, ScalerFactoryAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double, double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>() + .registerFactory<ScalerImplAnnot(fruit::Assisted<double>, fruit::Assisted<double>)>( + [](double factor, double) { + return ScalerImpl(factor); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1, 34.2); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_with_different_annotation_error(): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + using ScalerFactoryAnnot1 = fruit::Annotated<Annotation1, ScalerFactory>; + + fruit::Component<ScalerFactoryAnnot1> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot1, ScalerImplAnnot1>() + .registerFactory<ScalerImplAnnot2(fruit::Assisted<double>)>([](double factor) { return ScalerImpl(factor); }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot1> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot1>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_compile_error( + 'NoBindingFoundError<fruit::Annotated<Annotation1,std::function<std::unique_ptr<ScalerImpl(,std::default_delete<ScalerImpl>)?>\(double\)>>>', + '', + COMMON_DEFINITIONS, + source) + + +def test_register_factory_dep_on_provider(): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>() + .registerProvider([](){return 23;}) + .registerFactory<ScalerImpl(fruit::Assisted<double>, fruit::Provider<int>)>( + [](double factor, fruit::Provider<int> provider) { + return ScalerImpl(factor * provider.get<int>()); + }); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_register_factory_dep_on_provider_returning_value(): + source = ''' + struct Scaler { + private: + double factor; + + public: + Scaler(double factor) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .registerProvider([](){return 23;}) + .registerFactory<Scaler(fruit::Assisted<double>, fruit::Provider<int>)>( + [](double factor, fruit::Provider<int> provider) { + return Scaler(factor * provider.get<int>()); + }); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +def test_register_factory_error_abstract_class(): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + // Note: here we "forgot" to implement scale() (on purpose, for this test) so ScalerImpl is an abstract class. + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImpl>() + .registerFactory<fruit::Annotated<Annotation1, ScalerImpl>(fruit::Assisted<double>)>([](double) { return (ScalerImpl*)nullptr; }); + } + ''' + expect_compile_error( + 'CannotConstructAbstractClassError<ScalerImpl>', + 'The specified class can.t be constructed because it.s an abstract class.', + COMMON_DEFINITIONS, + source) + +def test_register_factory_error_not_function(): + source = ''' + struct X { + X(int) {} + }; + + fruit::Component<std::function<X()>> getComponent() { + int n = 3; + return fruit::createComponent() + .registerFactory<X()>([=]{return X(n);}); + } + ''' + expect_compile_error( + 'LambdaWithCapturesError<.*>', + 'Only lambdas with no captures are supported', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerImplPtrAnnot,ScalerFactoryAnnot,ScalerImplFactorySignatureAnnotRegex', [ + ('Scaler', + 'ScalerImpl', + 'ScalerImpl*', + 'std::function<std::unique_ptr<Scaler>(double)>', + 'ScalerImpl\*\(fruit::Assisted<double>\)'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation2, ScalerImpl*>', + 'fruit::Annotated<Annotation2, std::function<std::unique_ptr<Scaler>(double)>>', + 'fruit::Annotated<Annotation2,ScalerImpl\*>\(fruit::Assisted<double>\)') +]) +def test_register_factory_for_pointer(ScalerAnnot, ScalerImplAnnot, ScalerImplPtrAnnot, ScalerFactoryAnnot, ScalerImplFactorySignatureAnnotRegex): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>() + .registerFactory<ScalerImplPtrAnnot(fruit::Assisted<double>)>([](double factor) { return new ScalerImpl(factor); }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_compile_error( + 'FactoryReturningPointerError<ScalerImplFactorySignatureAnnotRegex>', + 'The specified factory returns a pointer. This is not supported', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerPtrAnnot,ScalerFactoryAnnot,ScalerFactorySignatureAnnotRegex', [ + ('Scaler*', + 'std::function<Scaler(double)>', + 'Scaler\*\(fruit::Assisted<double>\)'), + ('fruit::Annotated<Annotation1, Scaler*>', + 'fruit::Annotated<Annotation1, std::function<Scaler(double)>>', + 'fruit::Annotated<Annotation1,Scaler\*>\(fruit::Assisted<double>\)'), +]) +def test_register_factory_for_pointer_returning_value(ScalerPtrAnnot, ScalerFactoryAnnot, ScalerFactorySignatureAnnotRegex): + source = ''' + struct Scaler { + private: + double factor; + + public: + Scaler(double factor) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .registerFactory<ScalerPtrAnnot(fruit::Assisted<double>)>([](double factor) { return new Scaler(factor); }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_compile_error( + 'FactoryReturningPointerError<ScalerFactorySignatureAnnotRegex>', + 'The specified factory returns a pointer. This is not supported', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerImplPtrAnnot,ScalerFactoryAnnot', [ + ('Scaler', + 'ScalerImpl', + 'std::unique_ptr<ScalerImpl>', + 'std::function<std::unique_ptr<Scaler>(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation2, std::unique_ptr<ScalerImpl>>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>'), +]) +def test_register_factory_for_unique_pointer(ScalerAnnot, ScalerImplAnnot, ScalerImplPtrAnnot, ScalerFactoryAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>() + .registerFactory<ScalerImplPtrAnnot(fruit::Assisted<double>)>( + [](double factor) { + return std::unique_ptr<ScalerImpl>(new ScalerImpl(factor)); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerImplAnnot,ScalerImplPtrAnnot,ScalerFactoryAnnot', [ + ('Scaler', + 'ScalerImpl', + 'std::unique_ptr<ScalerImpl>', + 'std::function<std::unique_ptr<Scaler>(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation2, ScalerImpl>', + 'fruit::Annotated<Annotation2, std::unique_ptr<ScalerImpl>>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<Scaler>(double)>>'), +]) +def test_register_factory_for_unique_pointer_returning_invalid_unique_ptr_ok(ScalerAnnot, ScalerImplAnnot, ScalerImplPtrAnnot, ScalerFactoryAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + virtual ~Scaler() = default; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .bind<ScalerAnnot, ScalerImplAnnot>() + .registerFactory<ScalerImplPtrAnnot(fruit::Assisted<double>)>( + [](double) { + return std::unique_ptr<ScalerImpl>(nullptr); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + Assert(scaler.get() == nullptr); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerAnnot,ScalerFactoryAnnot', [ + ('Scaler', + 'std::function<Scaler(double)>'), + ('fruit::Annotated<Annotation1, Scaler>', + 'fruit::Annotated<Annotation1, std::function<Scaler(double)>>'), +]) +def test_register_factory_for_unique_pointer_returning_value(ScalerAnnot, ScalerFactoryAnnot): + source = ''' + struct Scaler { + private: + double factor; + + public: + Scaler(double factor) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactoryAnnot> getScalerComponent() { + return fruit::createComponent() + .registerFactory<ScalerAnnot(fruit::Assisted<double>)>( + [](double factor) { + return Scaler(factor); + }); + } + + int main() { + fruit::Injector<ScalerFactoryAnnot> injector(getScalerComponent); + ScalerFactory scalerFactory = injector.get<ScalerFactoryAnnot>(); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ScalerImplAnnot', [ + 'ScalerImpl', + 'fruit::Annotated<Annotation1, ScalerImpl>', +]) +def test_register_factory_inconsistent_signature(ScalerImplAnnot): + source = ''' + struct Scaler { + virtual double scale(double x) = 0; + }; + + struct ScalerImpl : public Scaler { + private: + double factor; + + public: + ScalerImpl(double factor) + : factor(factor) { + } + + double scale(double x) override { + return x * factor; + } + }; + + using ScalerFactory = std::function<std::unique_ptr<Scaler>(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .bind<Scaler, ScalerImplAnnot>() + .registerFactory<ScalerImplAnnot(fruit::Assisted<double>)>([](float factor) { return ScalerImpl(factor); }); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + std::unique_ptr<Scaler> scaler = scalerFactory(12.1); + std::cout << scaler->scale(3) << std::endl; + } + ''' + expect_compile_error( + 'FunctorSignatureDoesNotMatchError<ScalerImpl\(double\),ScalerImpl\(float\)>', + 'Unexpected functor signature', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_inconsistent_signature_returning_value(): + source = ''' + struct Scaler { + private: + double factor; + + public: + Scaler(double factor) + : factor(factor) { + } + + double scale(double x) { + return x * factor; + } + }; + + using ScalerFactory = std::function<Scaler(double)>; + + fruit::Component<ScalerFactory> getScalerComponent() { + return fruit::createComponent() + .registerFactory<Scaler(fruit::Assisted<double>)>([](float factor) { return Scaler(factor); }); + } + + int main() { + fruit::Injector<ScalerFactory> injector(getScalerComponent); + ScalerFactory scalerFactory(injector); + Scaler scaler = scalerFactory(12.1); + std::cout << scaler.scale(3) << std::endl; + } + ''' + expect_compile_error( + 'FunctorSignatureDoesNotMatchError<Scaler\(double\),Scaler\(float\)>', + 'Unexpected functor signature', + COMMON_DEFINITIONS, + source) + +def test_register_factory_nonmovable_ok(): + source = ''' + struct C { + INJECT(C()) = default; + + C(const C&) = delete; + C(C&&) = delete; + C& operator=(const C&) = delete; + C& operator=(C&&) = delete; + }; + + using CFactory = std::function<std::unique_ptr<C>()>; + + fruit::Component<CFactory> getCFactory() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<CFactory> injector(getCFactory); + CFactory cFactory(injector); + std::unique_ptr<C> c = cFactory(); + (void)c; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +# TODO: this might not be the best error message, maybe we should ignore the constructor entirely in the message, +# or mention that there are other ways to satisfy that dependency. +@pytest.mark.parametrize('XAnnot,XFactoryAnnot', [ + ('X', + 'std::function<X(int)>'), + ('fruit::Annotated<Annotation1, X>', + 'fruit::Annotated<Annotation1, std::function<X(int)>>'), +]) +def test_register_factory_not_existing_constructor1(XAnnot, XFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + }; + + fruit::Component<XFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'FunctorSignatureDoesNotMatchError<XAnnot\(int\),XAnnot\((void)?\)>', + 'Unexpected functor signature', + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: this might not be the best error message, maybe we should ignore the constructor entirely in the message, +# or mention that there are other ways to satisfy that dependency. +@pytest.mark.parametrize('XIntFactoryAnnot,XIntFactoryAnnotRegex,XVoidFactoryAnnotRegex', [ + ('std::function<std::unique_ptr<X>(int)>', + 'std::unique_ptr<X(,std::default_delete<X>)?>\(int\)', + 'std::unique_ptr<X(,std::default_delete<X>)?>\((void)?\)'), + ('fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>(int)>>', + 'fruit::Annotated<Annotation1,std::unique_ptr<X(,std::default_delete<X>)?>>\(int\)', + 'fruit::Annotated<Annotation1,std::unique_ptr<X(,std::default_delete<X>)?>>\((void)?\)') +]) +def test_register_factory_not_existing_constructor2(XIntFactoryAnnot, XIntFactoryAnnotRegex, XVoidFactoryAnnotRegex): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XIntFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'FunctorSignatureDoesNotMatchError<XIntFactoryAnnotRegex,XVoidFactoryAnnotRegex>', + 'Unexpected functor signature', + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: this might not be the best error message, maybe we should ignore the constructor entirely in the message, +# or mention that there are other ways to satisfy that dependency. +@pytest.mark.parametrize('XAnnot,XFactoryAnnot', [ + ('X', + 'std::function<X(int)>'), + ('fruit::Annotated<Annotation1, X>', + 'fruit::Annotated<Annotation1, std::function<X(int)>>'), +]) +def test_register_factory_not_existing_constructor2_returning_value(XAnnot, XFactoryAnnot): + source = ''' + struct X { + using Inject = X(); + }; + + fruit::Component<XFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + ''' + expect_compile_error( + 'FunctorSignatureDoesNotMatchError<XAnnot\(int\), XAnnot\((void)?\)>', + 'Unexpected functor signature', + COMMON_DEFINITIONS, + source, + locals()) + + +@pytest.mark.parametrize('XFactoryAnnot', [ + 'std::function<X()>', + 'fruit::Annotated<Annotation1, std::function<X()>>', +]) +def test_register_factory_success_factory_movable_only_implicit(XFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + X(X&&) = default; + X(const X&) = delete; + }; + + fruit::Component<XFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XFactoryAnnot> injector(getComponent); + injector.get<XFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XPtrAnnot,ConstructX,XPtrFactoryAnnot', [ + ('X', 'X()', 'std::function<X()>'), + ('fruit::Annotated<Annotation1, X>', 'X()', 'fruit::Annotated<Annotation1, std::function<X()>>'), + ('std::unique_ptr<X>', 'std::unique_ptr<X>(new X())', 'std::function<std::unique_ptr<X>()>'), + ('fruit::Annotated<Annotation1, std::unique_ptr<X>>', 'std::unique_ptr<X>(new X())', 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>()>>'), +]) +def test_register_factory_success_factory_movable_only_explicit(XPtrAnnot, ConstructX, XPtrFactoryAnnot): + source = ''' + struct X { + X() = default; + X(X&&) = default; + X(const X&) = delete; + }; + + fruit::Component<XPtrFactoryAnnot> getComponent() { + return fruit::createComponent() + .registerFactory<XPtrAnnot()>([](){return ConstructX;}); + } + + int main() { + fruit::Injector<XPtrFactoryAnnot> injector(getComponent); + injector.get<XPtrFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XPtrFactoryAnnot', [ + 'std::function<std::unique_ptr<X>()>', + 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>()>>', +]) +def test_register_factory_success_factory_not_movable_implicit(XPtrFactoryAnnot): + source = ''' + struct X { + INJECT(X()) = default; + X(X&&) = delete; + X(const X&) = delete; + }; + + fruit::Component<XPtrFactoryAnnot> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XPtrFactoryAnnot> injector(getComponent); + injector.get<XPtrFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XPtrAnnot,XPtrFactoryAnnot', [ + ('std::unique_ptr<X>', 'std::function<std::unique_ptr<X>()>'), + ('fruit::Annotated<Annotation1, std::unique_ptr<X>>', 'fruit::Annotated<Annotation1, std::function<std::unique_ptr<X>()>>'), +]) +def test_register_factory_success_factory_not_movable_explicit_returning_pointer(XPtrAnnot, XPtrFactoryAnnot): + source = ''' + struct X { + X() = default; + X(X&&) = delete; + X(const X&) = delete; + }; + + fruit::Component<XPtrFactoryAnnot> getComponent() { + return fruit::createComponent() + .registerFactory<XPtrAnnot()>([](){return std::unique_ptr<X>(new X());}); + } + + int main() { + fruit::Injector<XPtrFactoryAnnot> injector(getComponent); + injector.get<XPtrFactoryAnnot>()(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('std::unique_ptr<X>(new X())', 'std::unique_ptr<X>'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'Y*', + 'const Y*', + 'Y&', + 'const Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', + 'fruit::Provider<const Y>', +]) +def test_register_factory_with_param_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<Y>> getYComponent() { + return fruit::createComponent() + .registerConstructor<WithAnnot<Y>()>(); + } + + fruit::Component<std::function<XPtr()>> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerFactory<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<std::function<XPtr()>> injector(getComponent); + XPtr x = injector.get<std::function<XPtr()>>()(); + (void) x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('std::unique_ptr<X>(new X())', 'std::unique_ptr<X>'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y*', + 'const Y&', + 'fruit::Provider<const Y>', +]) +def test_register_factory_with_param_const_binding_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + struct X {}; + + const Y y{}; + + fruit::Component<WithAnnot<const Y>> getYComponent() { + return fruit::createComponent() + .bindInstance<WithAnnot<Y>, Y>(y); + } + + fruit::Component<std::function<XPtr()>> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerFactory<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<std::function<XPtr()>> injector(getComponent); + XPtr x = injector.get<std::function<XPtr()>>()(); + (void) x; + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('std::unique_ptr<X>(new X())', 'std::unique_ptr<X>'), +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnotation', 'Y'), + ('WithAnnotation1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('XFactoryResult', [ + 'X', + 'std::unique_ptr<X>', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_factory_with_param_error_nonconst_param_required(ConstructX, XPtr, WithAnnot, YAnnotRegex, XFactoryResult, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<std::function<XFactoryResult()>> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerFactory<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('std::unique_ptr<X>(new X())', 'std::unique_ptr<X>'), +]) +@pytest.mark.parametrize('XFactoryResult', [ + 'X', + 'std::unique_ptr<X>', +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnotation', 'Y'), + ('WithAnnotation1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_factory_with_param_error_nonconst_param_required_install_after(ConstructX, XPtr, XFactoryResult, WithAnnot, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<std::function<XFactoryResult()>> getComponent() { + return fruit::createComponent() + .registerFactory<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }) + .install(getYComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_requiring_nonconst_then_requiring_const_ok(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<std::function<Y()>, std::function<Z()>> getRootComponent() { + return fruit::createComponent() + .registerFactory<Y(X&)>([](X&) { return Y();}) + .registerFactory<Z(const X&)>([](const X&) { return Z();}) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<std::function<Y()>, std::function<Z()>> injector(getRootComponent); + std::function<Y()> yFactory = injector.get<std::function<Y()>>(); + yFactory(); + std::function<Z()> zFactory = injector.get<std::function<Z()>>(); + zFactory(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_requiring_nonconst_then_requiring_const_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, std::function<Y()>, std::function<Z()>> getRootComponent() { + return fruit::createComponent() + .registerFactory<Y(X&)>([](X&) { return Y();}) + .registerFactory<Z(const X&)>([](const X&) { return Z();}); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_requiring_const_then_requiring_nonconst_ok(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<std::function<Y()>, std::function<Z()>> getRootComponent() { + return fruit::createComponent() + .registerFactory<Y(const X&)>([](const X&) { return Y();}) + .registerFactory<Z(X&)>([](X&) { return Z();}) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<std::function<Y()>, std::function<Z()>> injector(getRootComponent); + std::function<Y()> yFactory = injector.get<std::function<Y()>>(); + yFactory(); + std::function<Z()> zFactory = injector.get<std::function<Z()>>(); + zFactory(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_requiring_const_then_requiring_nonconst_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, std::function<Y()>, std::function<Z()>> getRootComponent() { + return fruit::createComponent() + .registerFactory<Y(const X&)>([](const X&) { return Y();}) + .registerFactory<Z(X&)>([](X&) { return Z();}); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_provider_get_error_type_unique_pointer_pointer_not_provided(): + source = ''' + struct X {}; + + void f(fruit::Provider<X> provider) { + provider.get<std::unique_ptr<X>*>(); + } + ''' + expect_compile_error( + 'TypeNotProvidedError<std::unique_ptr<X(,std::default_delete<X>)?>\*>', + 'Trying to get an instance of T, but it is not provided by this Provider/Injector.', + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('std::unique_ptr<X>(new X())', 'std::unique_ptr<X>'), +]) +@pytest.mark.parametrize('XFactoryResult', [ + 'X', + 'std::unique_ptr<X>', +]) +@pytest.mark.parametrize('YVariant,YVariantRegex', [ + ('Y**', r'Y\*\*'), + ('std::shared_ptr<Y>*', r'std::shared_ptr<Y>\*'), + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('Y*&', r'Y\*&'), + ('Y(*)()', r'Y(\((__cdecl)?\*\))?\((void)?\)'), + ('fruit::Annotated<Annotation1, Y**>', r'Y\*\*'), +]) +def test_register_factory_with_param_error_type_not_injectable( + ConstructX, XPtr, XFactoryResult, YVariant, YVariantRegex): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerFactory<XPtr(YVariant)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<YVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_bind_nonconst_unique_ptr_factory_to_const_value_factory(): + source = ''' + struct X { + INJECT(X()) = default; + }; + + fruit::Component<const std::function<X()>> getChildComponent() { + return fruit::createComponent(); + } + + fruit::Component<std::function<std::unique_ptr<X>()>> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent); + } + + int main() { + fruit::Injector<std::function<std::unique_ptr<X>()>> injector(getRootComponent); + std::function<std::unique_ptr<X>()> xFactory(injector); + xFactory(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_bind_const_interface_factory_to_nonconst_implementation_factory(): + source = ''' + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + + struct Y : public X { + INJECT(Y()) = default; + + void foo() override { + } + }; + + fruit::Component<std::function<std::unique_ptr<Y>()>> getChildComponent() { + return fruit::createComponent(); + } + + fruit::Component<const std::function<std::unique_ptr<X>()>> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent) + .bind<X, Y>(); + } + + int main() { + fruit::Injector<const std::function<std::unique_ptr<X>()>> injector(getRootComponent); + std::function<std::unique_ptr<X>()> xFactory(injector); + xFactory(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_factory_bind_nonconst_interface_factory_to_const_implementation_factory(): + source = ''' + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + + struct Y : public X { + INJECT(Y()) = default; + + void foo() override { + } + }; + + fruit::Component<const std::function<std::unique_ptr<Y>()>> getChildComponent() { + return fruit::createComponent(); + } + + fruit::Component<std::function<std::unique_ptr<X>()>> getRootComponent() { + return fruit::createComponent() + .install(getChildComponent) + .bind<X, Y>(); + } + + int main() { + fruit::Injector<std::function<std::unique_ptr<X>()>> injector(getRootComponent); + std::function<std::unique_ptr<X>()> xFactory(injector); + xFactory(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +def test_register_factory_abstract_class_ok(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + virtual ~I() = default; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<WithAnnot<std::function<std::unique_ptr<I>()>>> getComponent() { + return fruit::createComponent() + .registerFactory<WithAnnot<std::unique_ptr<I>>()>([](){return std::unique_ptr<I>(static_cast<I*>(new X()));}); + } + + int main() { + fruit::Injector<WithAnnot<std::function<std::unique_ptr<I>()>>> injector(getComponent); + + Assert(injector.get<WithAnnot<std::function<std::unique_ptr<I>()>>>()()->foo() == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +# TODO: investigate why this doesn't fail to compile with MSVC and then re-enable it for MSVC too. +@pytest.mark.skipif( + re.search('MSVC', CXX_COMPILER_NAME), + reason = 'This is disabled in MSVC because it compiles cleanly with the latest MSVC.') +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnotation', + 'WithAnnotation1', +]) +def test_register_factory_abstract_class_with_no_virtual_destructor_error(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<WithAnnot<std::function<std::unique_ptr<I>()>>> getComponent() { + return fruit::createComponent() + .registerFactory<WithAnnot<std::unique_ptr<I>>()>([](){return std::unique_ptr<I>(static_cast<I*>(new X()));}); + } + ''' + expect_compile_error( + 'RegisterFactoryForUniquePtrOfAbstractClassWithNoVirtualDestructorError<I>', + 'registerFactory\(\) was called with a lambda that returns a std::unique_ptr<T>, but T is an abstract class', + COMMON_DEFINITIONS, + source, + locals(), + ignore_warnings=True, + disable_error_line_number_check=True) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_register_instance.py b/tests/test_register_instance.py new file mode 100755 index 0000000..f449809 --- /dev/null +++ b/tests/test_register_instance.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + ''' + +def escape_regex(regex): + # We un-escape the space because we strip the spaces in fruit_test_common, and this would otherwise leave a + # stray backslash. + return re.escape(regex).replace('\\ ', ' ') + +def test_bind_instance_success(): + source = ''' + struct X { + int n; + + X(int n) + : n(n) { + } + }; + + fruit::Component<X> getComponent(X* x) { + return fruit::createComponent() + .bindInstance(*x); + } + + int main() { + X x(34); + fruit::Injector<X> injector(getComponent, &x); + X& x1 = injector.get<X&>(); + Assert(&x == &x1); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_bind_instance_annotated_success(): + source = ''' + struct X { + int n; + + X(int n) + : n(n) { + } + }; + + fruit::Component<fruit::Annotated<Annotation1, X>> getComponent(X* x) { + return fruit::createComponent() + .bindInstance<fruit::Annotated<Annotation1, X>>(*x); + } + + int main() { + X x(34); + fruit::Injector<fruit::Annotated<Annotation1, X>> injector(getComponent, &x); + X& x1 = injector.get<fruit::Annotated<Annotation1, X&>>(); + Assert(&x == &x1); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_bind_const_instance_success(): + source = ''' + struct X { + int n; + + X(int n) + : n(n) { + } + }; + + fruit::Component<const X> getComponent(const X* x) { + return fruit::createComponent() + .bindInstance(*x); + } + + const X x(34); + + int main() { + fruit::Injector<const X> injector(getComponent, &x); + const X& x1 = injector.get<const X&>(); + Assert(&x == &x1); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_bind_const_instance_annotated_success(): + source = ''' + struct X { + int n; + + X(int n) + : n(n) { + } + }; + + fruit::Component<fruit::Annotated<Annotation1, const X>> getComponent(const X* x) { + return fruit::createComponent() + .bindInstance<fruit::Annotated<Annotation1, X>>(*x); + } + + const X x(34); + + int main() { + fruit::Injector<fruit::Annotated<Annotation1, const X>> injector(getComponent, &x); + const X& x1 = injector.get<fruit::Annotated<Annotation1, const X&>>(); + Assert(&x == &x1); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +@pytest.mark.parametrize('XAnnot,MaybeConstXAnnot,XPtr,XPtrAnnot', [ + ('X', 'X', 'X*', 'X*'), + ('X', 'const X', 'const X*', 'const X*'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X>', 'X*', 'fruit::Annotated<Annotation1, X*>'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, const X>', 'const X*', 'fruit::Annotated<Annotation1, const X*>'), +]) +def test_bind_instance_two_explicit_type_arguments_success(XAnnot, MaybeConstXAnnot, XPtr, XPtrAnnot): + source = ''' + struct X { + int n; + + X(int n) + : n(n) { + } + }; + + fruit::Component<MaybeConstXAnnot> getComponent(XPtr x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + int main() { + X x(34); + fruit::Injector<MaybeConstXAnnot> injector(getComponent, &x); + XPtr x1 = injector.get<XPtrAnnot>(); + Assert(&x == x1); + } + ''' + expect_success(COMMON_DEFINITIONS, source, locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_bind_instance_abstract_class_ok(XAnnot): + source = ''' + struct X { + virtual void foo() = 0; + }; + + fruit::Component<> getComponentForInstanceHelper(X* x) { + return fruit::createComponent() + .bindInstance<XAnnot, X>(*x); + } + + fruit::Component<XAnnot> getComponentForInstance(X* x) { + return fruit::createComponent() + .install(getComponentForInstanceHelper, x) + .bindInstance<XAnnot, X>(*x); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('intAnnot,intPtrAnnot', [ + ('int', 'int*'), + ('fruit::Annotated<Annotation1, int>', 'fruit::Annotated<Annotation1, int*>'), +]) +def test_bind_instance_multiple_equivalent_bindings_success(intAnnot, intPtrAnnot): + source = ''' + fruit::Component<> getComponentForInstanceHelper(int* n) { + return fruit::createComponent() + .bindInstance<intAnnot, int>(*n); + } + + fruit::Component<intAnnot> getComponentForInstance(int* n) { + return fruit::createComponent() + .install(getComponentForInstanceHelper, n) + .bindInstance<intAnnot, int>(*n); + } + + int main() { + int n = 5; + fruit::Injector<intAnnot> injector(getComponentForInstance, &n); + if (injector.get<intPtrAnnot>() != &n) + abort(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('intAnnot,intPtrAnnot', [ + ('int', 'int*'), + ('fruit::Annotated<Annotation1, int>', 'fruit::Annotated<Annotation1, int*>'), +]) +def test_bind_instance_multiple_equivalent_bindings_different_constness_success(intAnnot, intPtrAnnot): + source = ''' + fruit::Component<> getComponentForInstanceHelper(const int* n) { + return fruit::createComponent() + .bindInstance<intAnnot, int>(*n); + } + + fruit::Component<intAnnot> getComponentForInstance(int* n) { + return fruit::createComponent() + .install(getComponentForInstanceHelper, n) + .bindInstance<intAnnot, int>(*n); + } + + int main() { + int n = 5; + fruit::Injector<intAnnot> injector(getComponentForInstance, &n); + if (injector.get<intPtrAnnot>() != &n) + abort(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('intAnnot,intPtrAnnot', [ + ('int', 'int*'), + ('fruit::Annotated<Annotation1, int>', 'fruit::Annotated<Annotation1, int*>'), +]) +def test_bind_instance_multiple_equivalent_bindings_different_constness_other_order_success(intAnnot, intPtrAnnot): + source = ''' + fruit::Component<> getComponentForInstanceHelper(const int* n) { + return fruit::createComponent() + .bindInstance<intAnnot, int>(*n); + } + + fruit::Component<intAnnot> getComponentForInstance(int* n) { + return fruit::createComponent() + .bindInstance<intAnnot, int>(*n) + .install(getComponentForInstanceHelper, n); + } + + int main() { + int n = 5; + fruit::Injector<intAnnot> injector(getComponentForInstance, &n); + if (injector.get<intPtrAnnot>() != &n) + abort(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant', [ + 'X*', + 'const X*', + 'std::shared_ptr<X>', +]) +def test_bind_instance_non_normalized_type_error(XVariant): + if XVariant.endswith('&'): + XVariantRegexp = escape_regex(XVariant[:-1]) + else: + XVariantRegexp = escape_regex(XVariant) + source = ''' + struct X {}; + + fruit::Component<> getComponent(XVariant x) { + return fruit::createComponent() + .bindInstance(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegexp', [ + ('const X', 'const X'), + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('X&', 'X&'), + ('const X&', 'const X&'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), +]) +def test_bind_instance_non_normalized_type_error_with_annotation(XVariant, XVariantRegexp): + source = ''' + struct X {}; + + fruit::Component<> getComponent(XVariant x) { + return fruit::createComponent() + .bindInstance<fruit::Annotated<Annotation1, XVariant>>(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnotVariant,XVariant', [ + ('const X', 'const X'), + ('X*', 'X*'), + ('const X*', 'const X*'), + ('X&', 'X&'), + ('const X&', 'const X&'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), + + ('fruit::Annotated<Annotation1, const X>', 'const X'), + ('fruit::Annotated<Annotation1, X*>', 'X*'), + ('fruit::Annotated<Annotation1, const X*>', 'const X*'), + ('fruit::Annotated<Annotation1, X&>', 'X&'), + ('fruit::Annotated<Annotation1, const X&>', 'const X&'), + ('fruit::Annotated<Annotation1, std::shared_ptr<X>>', 'std::shared_ptr<X>'), + + ('fruit::Annotated<Annotation1, X>', 'const X'), + ('fruit::Annotated<Annotation1, X>', 'X*'), + ('fruit::Annotated<Annotation1, X>', 'const X*'), + ('fruit::Annotated<Annotation1, X>', 'X&'), + ('fruit::Annotated<Annotation1, X>', 'const X&'), + ('fruit::Annotated<Annotation1, X>', 'std::shared_ptr<X>'), +]) +def test_bind_instance_non_normalized_type_error_two_explicit_type_arguments(XAnnotVariant, XVariant): + XVariantRegexp = escape_regex(XVariant) + source = ''' + struct X {}; + + fruit::Component<> getComponent(XVariant x) { + return fruit::createComponent() + .bindInstance<XAnnotVariant, XVariant>(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegexp,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), +]) +def test_register_instance_error_must_be_reference(XVariant, XVariantRegex): + source = ''' + struct X {}; + + fruit::Component<> getComponentForInstance(XVariant x) { + return fruit::createComponent() + .bindInstance(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegex,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X*', 'X\*'), + ('const X*', 'const X\*'), + ('std::shared_ptr<X>', 'std::shared_ptr<X>'), +]) +def test_register_instance_error_must_be_reference_with_annotation(XVariant, XVariantRegex): + source = ''' + struct X {}; + + fruit::Component<> getComponentForInstance(XVariant x) { + return fruit::createComponent() + .bindInstance<fruit::Annotated<Annotation1, X>>(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegex,X>', + 'A non-class type T was specified. Use C instead.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_bind_instance_mismatched_type_arguments(XAnnot): + source = ''' + struct X {}; + + fruit::Component<> getComponent(int* n) { + return fruit::createComponent() + .bindInstance<XAnnot, int>(*n); + } + ''' + expect_compile_error( + 'TypeMismatchInBindInstanceError<X,int>', + 'A type parameter was specified in bindInstance.. but it doesn.t match the value type', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('BaseAnnot,BasePtrAnnot', [ + ('Base', 'Base*'), + ('fruit::Annotated<Annotation1, Base>', 'fruit::Annotated<Annotation1, Base*>'), +]) +def test_bind_instance_to_subclass(BaseAnnot, BasePtrAnnot): + source = ''' + struct Base { + virtual void f() = 0; + virtual ~Base() { + } + }; + + struct Derived : public Base { + void f() override { + } + }; + + fruit::Component<BaseAnnot> getComponent(Derived* derived) { + return fruit::createComponent() + .bindInstance<BaseAnnot, Base>(*derived); + } + + int main() { + Derived derived; + fruit::Injector<BaseAnnot> injector(getComponent, &derived); + Base* base = injector.get<BasePtrAnnot>(); + base->f(); + } + ''' + expect_success(COMMON_DEFINITIONS, source, locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X**', r'X\*\*'), + ('std::shared_ptr<X>*', r'std::shared_ptr<X>\*'), + ('X*&', r'X\*&'), + ('fruit::Annotated<Annotation1, X**>', r'X\*\*'), +]) +def test_bind_instance_type_not_normalized(XVariant, XVariantRegex): + source = ''' + struct X {}; + + using XVariantT = XVariant; + fruit::Component<> getComponent(XVariantT x) { + return fruit::createComponent() + .bindInstance<XVariant, XVariant>(x); + } + ''' + expect_compile_error( + 'NonClassTypeError<XVariantRegex,X>', + 'A non-class type T was specified.', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XVariant,XVariantRegex', [ + ('X(*)()', r'X(\((__cdecl)?\*\))?\((void)?\)'), +]) +def test_bind_instance_type_not_injectable_error(XVariant, XVariantRegex): + source = ''' + struct X {}; + + using XVariantT = XVariant; + fruit::Component<> getComponent(XVariantT x) { + return fruit::createComponent() + .bindInstance<XVariant, XVariant>(x); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<XVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_register_provider.py b/tests/test_register_provider.py new file mode 100755 index 0000000..d34803d --- /dev/null +++ b/tests/test_register_provider.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +import pytest + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation1 {}; + + template <typename T> + using WithNoAnnot = T; + + template <typename T> + using WithAnnot1 = fruit::Annotated<Annotation1, T>; + ''' + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +def test_register_provider_success(WithAnnot,ConstructX, XPtr): + source = ''' + struct X : public ConstructionTracker<X> { + int value = 5; + }; + + fruit::Component<WithAnnot<X>> getComponent() { + return fruit::createComponent() + .registerProvider<WithAnnot<XPtr>()>([](){return ConstructX;}); + } + + int main() { + fruit::Injector<WithAnnot<X>> injector(getComponent); + + Assert((injector.get<WithAnnot<X >>(). value == 5)); + Assert((injector.get<WithAnnot<X* >>()->value == 5)); + Assert((injector.get<WithAnnot<X& >>(). value == 5)); + Assert((injector.get<WithAnnot<const X >>(). value == 5)); + Assert((injector.get<WithAnnot<const X* >>()->value == 5)); + Assert((injector.get<WithAnnot<const X& >>(). value == 5)); + Assert((injector.get<WithAnnot<std::shared_ptr<X>>>()->value == 5)); + + Assert(X::num_objects_constructed == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +def test_register_provider_abstract_class_ok(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + virtual ~I() = default; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<WithAnnot<I>> getComponent() { + return fruit::createComponent() + .registerProvider<WithAnnot<I*>()>([](){return static_cast<I*>(new X());}); + } + + int main() { + fruit::Injector<WithAnnot<I>> injector(getComponent); + + Assert(injector.get<WithAnnot<I*>>()->foo() == 5); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +def test_register_provider_abstract_class_with_no_virtual_destructor_error(WithAnnot): + source = ''' + struct I { + virtual int foo() = 0; + }; + + struct X : public I { + int foo() override { + return 5; + } + }; + + fruit::Component<WithAnnot<I>> getComponent() { + return fruit::createComponent() + .registerProvider<WithAnnot<I*>()>([](){return static_cast<I*>(new X());}); + } + ''' + expect_compile_error( + 'ProviderReturningPointerToAbstractClassWithNoVirtualDestructorError<I>', + 'registerProvider\(\) was called with a lambda that returns a pointer to T, but T is an abstract class', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX', [ + 'X()', + 'new X()', +]) +def test_register_provider_not_copyable_success(ConstructX): + source = ''' + struct X { + X() = default; + X(X&&) = default; + X(const X&) = delete; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .registerProvider([](){return ConstructX;}); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_provider_not_movable_returning_pointer_success(): + source = ''' + struct X { + X() = default; + X(X&&) = delete; + X(const X&) = delete; + }; + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .registerProvider([](){return new X();}); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source) + +@pytest.mark.parametrize('XAnnot', [ + 'X', + 'fruit::Annotated<Annotation1, X>', +]) +def test_register_provider_error_not_function(XAnnot): + source = ''' + struct X { + X(int) {} + }; + + fruit::Component<XAnnot> getComponent() { + int n = 3; + return fruit::createComponent() + .registerProvider<XAnnot()>([=]{return X(n);}); + } + ''' + expect_compile_error( + 'FunctorUsedAsProviderError<.*>', + 'A stateful lambda or a non-lambda functor was used as provider', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('intAnnot', [ + 'int', + 'fruit::Annotated<Annotation1, int>', +]) +def test_register_provider_error_malformed_signature(intAnnot): + source = ''' + fruit::Component<intAnnot> getComponent() { + return fruit::createComponent() + .registerProvider<intAnnot>([](){return 42;}); + } + ''' + expect_compile_error( + 'NotASignatureError<intAnnot>', + 'CandidateSignature was specified as parameter, but it.s not a signature. Signatures are of the form', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('XAnnot,XPtrAnnot,XAnnotRegex', [ + ('X', 'X*', '(struct )?X'), + ('fruit::Annotated<Annotation1, X>', 'fruit::Annotated<Annotation1, X*>', '(struct )?fruit::Annotated<(struct )?Annotation1, ?(struct )?X>'), +]) +def test_register_provider_error_returned_nullptr(XAnnot, XPtrAnnot, XAnnotRegex): + source = ''' + struct X {}; + + fruit::Component<XAnnot> getComponent() { + return fruit::createComponent() + .registerProvider<XPtrAnnot()>([](){return (X*)nullptr;}); + } + + int main() { + fruit::Injector<XAnnot> injector(getComponent); + injector.get<XAnnot>(); + } + ''' + expect_runtime_error( + 'Fatal injection error: attempting to get an instance for the type XAnnotRegex but the provider returned nullptr', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'Y*', + 'const Y*', + 'Y&', + 'const Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', + 'fruit::Provider<const Y>', +]) +def test_register_provider_with_param_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<Y>> getYComponent() { + return fruit::createComponent() + .registerConstructor<WithAnnot<Y>()>(); + } + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot', [ + 'WithNoAnnot', + 'WithAnnot1', +]) +@pytest.mark.parametrize('YVariant', [ + 'Y', + 'const Y', + 'const Y*', + 'const Y&', + 'fruit::Provider<const Y>', +]) +def test_register_provider_with_param_const_binding_success(ConstructX, XPtr, WithAnnot, YVariant): + source = ''' + struct Y {}; + struct X {}; + + const Y y{}; + + fruit::Component<WithAnnot<const Y>> getYComponent() { + return fruit::createComponent() + .bindInstance<WithAnnot<Y>, Y>(y); + } + + fruit::Component<X> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + + int main() { + fruit::Injector<X> injector(getComponent); + injector.get<X>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnot', 'Y'), + ('WithAnnot1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_provider_with_param_error_nonconst_param_required(ConstructX, XPtr, WithAnnot, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .registerProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('WithAnnot,YAnnotRegex', [ + ('WithNoAnnot', 'Y'), + ('WithAnnot1', 'fruit::Annotated<Annotation1, Y>'), +]) +@pytest.mark.parametrize('YVariant', [ + 'Y*', + 'Y&', + 'std::shared_ptr<Y>', + 'fruit::Provider<Y>', +]) +def test_register_provider_with_param_error_nonconst_param_required_install_after(ConstructX, XPtr, WithAnnot, YAnnotRegex, YVariant): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<WithAnnot<const Y>> getYComponent(); + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerProvider<XPtr(WithAnnot<YVariant>)>([](YVariant){ return ConstructX; }) + .install(getYComponent); + } + ''' + expect_compile_error( + 'NonConstBindingRequiredButConstBindingProvidedError<YAnnotRegex>', + 'The type T was provided as constant, however one of the constructors/providers/factories in this component', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_provider_requiring_nonconst_then_requiring_const_ok(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .registerProvider([](X&) { return Y();}) + .registerProvider([](const X&) { return Z();}) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_provider_requiring_nonconst_then_requiring_const_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .registerProvider([](X&) { return Y();}) + .registerProvider([](const X&) { return Z();}); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_provider_requiring_const_then_requiring_nonconst_ok(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<Y, Z> getRootComponent() { + return fruit::createComponent() + .registerProvider([](const X&) { return Y();}) + .registerProvider([](X&) { return Z();}) + .registerConstructor<X()>(); + } + + int main() { + fruit::Injector<Y, Z> injector(getRootComponent); + injector.get<Y>(); + injector.get<Z>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_register_provider_requiring_const_then_requiring_nonconst_declaring_const_requirement_error(): + source = ''' + struct X {}; + struct Y {}; + struct Z {}; + + fruit::Component<fruit::Required<const X>, Y, Z> getRootComponent() { + return fruit::createComponent() + .registerProvider([](const X&) { return Y();}) + .registerProvider([](X&) { return Z();}); + } + ''' + expect_compile_error( + 'ConstBindingDeclaredAsRequiredButNonConstBindingRequiredError<X>', + 'The type T was declared as a const Required type in the returned Component, however', + COMMON_DEFINITIONS, + source, + locals()) + +@pytest.mark.parametrize('ConstructX,XPtr', [ + ('X()', 'X'), + ('new X()', 'X*'), +]) +@pytest.mark.parametrize('YVariant,YVariantRegex', [ + ('Y**', r'Y\*\*'), + ('std::shared_ptr<Y>*', r'std::shared_ptr<Y>\*'), + ('std::nullptr_t', r'(std::)?nullptr(_t)?'), + ('Y*&', r'Y\*&'), + ('Y(*)()', r'Y(\((__cdecl)?\*\))?\((void)?\)'), +]) +def test_register_provider_with_param_error_type_not_injectable(ConstructX, XPtr, YVariant, YVariantRegex): + source = ''' + struct Y {}; + struct X {}; + + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerProvider<XPtr(YVariant)>([](YVariant){ return ConstructX; }); + } + ''' + expect_compile_error( + 'NonInjectableTypeError<YVariantRegex>', + 'The type T is not injectable.', + COMMON_DEFINITIONS, + source, + locals()) + + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_required_types.py b/tests/test_required_types.py new file mode 100755 index 0000000..ab375f6 --- /dev/null +++ b/tests/test_required_types.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X; + + struct Annotation1 {}; + using XAnnot1 = fruit::Annotated<Annotation1, X>; + ''' + +def test_required_success(): + source = ''' + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + using XFactory = std::function<std::unique_ptr<X>()>; + struct Y { + XFactory xFactory; + + INJECT(Y(XFactory xFactory)) + : xFactory(xFactory) { + } + + void doStuff() { + xFactory()->foo(); + } + }; + fruit::Component<fruit::Required<XFactory>, Y> getYComponent() { + return fruit::createComponent(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<XFactory> getXFactoryComponent() { + return fruit::createComponent() + .bind<X, XImpl>(); + } + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_required_annotated_success(): + source = ''' + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + using XFactory = std::function<std::unique_ptr<X>()>; + using XFactoryAnnot = fruit::Annotated<Annotation1, XFactory>; + struct Y { + XFactory xFactory; + + INJECT(Y(ANNOTATED(Annotation1, XFactory) xFactory)) + : xFactory(xFactory) { + } + + void doStuff() { + xFactory()->foo(); + } + }; + fruit::Component<fruit::Required<XFactoryAnnot>, Y> getYComponent() { + return fruit::createComponent(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<XFactoryAnnot> getXFactoryComponent() { + return fruit::createComponent() + .bind<fruit::Annotated<Annotation1, X>, fruit::Annotated<Annotation1, XImpl>>(); + } + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_required_forward_declared_success(): + source = ''' + struct X; + using XFactory = std::function<std::unique_ptr<X>()>; + struct Y { + XFactory xFactory; + + INJECT(Y(XFactory xFactory)) + : xFactory(xFactory) { + } + + void doStuff(); + }; + fruit::Component<fruit::Required<XFactory>, Y> getYComponent() { + return fruit::createComponent(); + } + fruit::Component<XFactory> getXFactoryComponent(); + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + + // We define X as late as possible, to make sure that all the above compiles even if X is only forward-declared. + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + void Y::doStuff() { + xFactory()->foo(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<XFactory> getXFactoryComponent() { + return fruit::createComponent() + .bind<X, XImpl>(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_required_annotated_forward_declared_success(): + source = ''' + struct X; + using XFactory = std::function<std::unique_ptr<X>()>; + using XFactoryAnnot = fruit::Annotated<Annotation1, XFactory>; + struct Y { + XFactory xFactory; + + INJECT(Y(ANNOTATED(Annotation1, XFactory) xFactory)) + : xFactory(xFactory) { + } + + void doStuff(); + }; + fruit::Component<fruit::Required<XFactoryAnnot>, Y> getYComponent() { + return fruit::createComponent(); + } + fruit::Component<XFactoryAnnot> getXFactoryComponent(); + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + + // We define X as late as possible, to make sure that all the above compiles even if X is only forward-declared. + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + void Y::doStuff() { + xFactory()->foo(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<XFactoryAnnot> getXFactoryComponent() { + return fruit::createComponent() + .bind<fruit::Annotated<Annotation1, X>, fruit::Annotated<Annotation1, XImpl>>(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_required_const_forward_declared_success(): + source = ''' + struct X; + using XFactory = std::function<std::unique_ptr<X>()>; + struct Y { + XFactory xFactory; + + INJECT(Y(XFactory xFactory)) + : xFactory(xFactory) { + } + + void doStuff(); + }; + fruit::Component<fruit::Required<const XFactory>, Y> getYComponent() { + return fruit::createComponent(); + } + fruit::Component<const XFactory> getXFactoryComponent(); + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + + // We define X as late as possible, to make sure that all the above compiles even if X is only forward-declared. + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + void Y::doStuff() { + xFactory()->foo(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<const XFactory> getXFactoryComponent() { + return fruit::createComponent() + .bind<X, XImpl>(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +def test_required_const_annotated_forward_declared_success(): + source = ''' + struct X; + using XFactory = std::function<std::unique_ptr<X>()>; + using ConstXFactoryAnnot = fruit::Annotated<Annotation1, const XFactory>; + struct Y { + XFactory xFactory; + + INJECT(Y(ANNOTATED(Annotation1, XFactory) xFactory)) + : xFactory(xFactory) { + } + + void doStuff(); + }; + fruit::Component<fruit::Required<ConstXFactoryAnnot>, Y> getYComponent() { + return fruit::createComponent(); + } + fruit::Component<ConstXFactoryAnnot> getXFactoryComponent(); + fruit::Component<Y> getComponent() { + return fruit::createComponent() + .install(getYComponent) + .install(getXFactoryComponent); + } + int main() { + fruit::Injector<Y> injector(getComponent); + Y* y(injector); + y->doStuff(); + } + + // We define X as late as possible, to make sure that all the above compiles even if X is only forward-declared. + struct X { + virtual void foo() = 0; + virtual ~X() = default; + }; + void Y::doStuff() { + xFactory()->foo(); + } + struct XImpl : public X { + INJECT(XImpl()) = default; + void foo() override {} + }; + fruit::Component<ConstXFactoryAnnot> getXFactoryComponent() { + return fruit::createComponent() + .bind<fruit::Annotated<Annotation1, X>, fruit::Annotated<Annotation1, XImpl>>(); + } + ''' + expect_success(COMMON_DEFINITIONS, source) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_semistatic_map_hash_selection.py b/tests/test_semistatic_map_hash_selection.py new file mode 100644 index 0000000..6bf557a --- /dev/null +++ b/tests/test_semistatic_map_hash_selection.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct X1 {}; + struct X2 {}; + struct X3 {}; + struct X4 {}; + struct X5 {}; + struct X6 {}; + struct X7 {}; + ''' + +def test_semistatic_map_hash_selection(): + source = ''' + fruit::Component<> getComponent() { + return fruit::createComponent() + .registerConstructor<X1()>() + .registerConstructor<X2()>() + .registerConstructor<X3()>() + .registerConstructor<X4()>() + .registerConstructor<X5()>() + .registerConstructor<X6()>() + .registerConstructor<X7()>(); + } + + int main() { + // The component normalization generates a random hash. By looping 50 times it's very likely that we'll get at + // least one hash with too many collisions (and we'll generate another). + for (int i = 0; i < 50; i++) { + fruit::NormalizedComponent<> normalizedComponent(getComponent); + (void) normalizedComponent; + } + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_type_alignment.py b/tests/test_type_alignment.py new file mode 100644 index 0000000..39d8193 --- /dev/null +++ b/tests/test_type_alignment.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct alignas(1) X { + INJECT(X()) { + Assert(reinterpret_cast<std::uintptr_t>(this) % 1 == 0); + } + }; + + struct alignas(4) Y { + INJECT(Y()) { + Assert(reinterpret_cast<std::uintptr_t>(this) % 4 == 0); + } + }; + + struct alignas(128) Z { + INJECT(Z()) { + Assert(reinterpret_cast<std::uintptr_t>(this) % 128 == 0); + } + }; + ''' + +def test_everything(): + source = ''' + fruit::Component<X, Y, Z> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<X, Y, Z> injector(getComponent); + + injector.get<X*>(); + injector.get<Y*>(); + injector.get<Z*>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/test_type_alignment_with_annotation.py b/tests/test_type_alignment_with_annotation.py new file mode 100644 index 0000000..c830c4d --- /dev/null +++ b/tests/test_type_alignment_with_annotation.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + struct Annotation {}; + + struct alignas(1) X { + using Inject = X(); + X() { + Assert(reinterpret_cast<std::uintptr_t>(this) % 1 == 0); + } + }; + + struct alignas(4) Y { + using Inject = Y(); + Y() { + Assert(reinterpret_cast<std::uintptr_t>(this) % 4 == 0); + } + }; + + struct alignas(128) Z { + using Inject = Z(); + Z() { + Assert(reinterpret_cast<std::uintptr_t>(this) % 128 == 0); + } + }; + + using XAnnot = fruit::Annotated<Annotation, X>; + using YAnnot = fruit::Annotated<Annotation, Y>; + using ZAnnot = fruit::Annotated<Annotation, Z>; + ''' + +def test_type_alignment_with_annotation(): + source = ''' + fruit::Component<XAnnot, YAnnot, ZAnnot> getComponent() { + return fruit::createComponent(); + } + + int main() { + fruit::Injector<XAnnot, YAnnot, ZAnnot> injector(getComponent); + + injector.get<fruit::Annotated<Annotation, X*>>(); + injector.get<fruit::Annotated<Annotation, Y*>>(); + injector.get<fruit::Annotated<Annotation, Z*>>(); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/tested_features.md b/tests/tested_features.md new file mode 100644 index 0000000..8f2fc4c --- /dev/null +++ b/tests/tested_features.md @@ -0,0 +1,182 @@ + +# Features tested in end-to-end tests + +#### INJECT macro +* **TODO** Typical use-case +* **TODO** With assisted params +* **TODO** Check what happens with non-normalized types (all kinds) + +#### Binding to an instance +* Using `bind(x)` or `bind<fruit::Annotated<A, T>>(x)`. +* Check that calling bindInstance with a non-normalized type (e.g. const pointer, nonconst ptr, etc.) causes an error +* Abstract class (ok) +* Mismatched type arguments +* Bind to subclass + +#### Interface bindings +* Check that bind<T, T> causes an easy-to-understand error +* bind<T, Annotated<A, T>> +* Check that bind<X, Y>, bind<Y, Z> is allowed if Z derives from Y and Y derives from X +* bind<X, Y> with X not a base class of Y +* Check that the types passed to bind<> are normalized +* Check that bind<I, C> also means bind<std::function<std::unique_ptr<I>(Args...)>, std::function<std::unique_ptr<C>(Args...)>> (with and without Args) + +##### Binding to a constructor +* Explicitly, with a non-signature (not ok) +* Implicitly, with a non-signature (not ok) +* Implicitly, with a signature "returning" another type (not ok) +* Implicitly, with a signature "returning" an annotated type (not ok) +* Explicitly, with a signature that doesn't match any of the type's constructors +* Implicitly, with a signature that doesn't match any of the type's constructors +* **TODO** Using the Inject typedef +* **TODO** Using the INJECT macro +* **TODO** Also with no params +* **TODO** Also for a templated class +* **TODO** Also for a templated constructor (only explicitly or using Inject) +* **TODO** With all kinds of non-normalized params (esp. with INJECT) +* **TODO** With a constructor mistakenly taking an Assisted<X> or Annotated<A,X> parameter (instead of just using Assisted/Annotated in the Inject typedef) +* For an abstract type (not ok), both implicit and explicit +* **TODO** Check that a default-constructible type without an Inject typedef can't be auto-injected + +##### Binding to a provider +* Returning a value +* **TODO: ownership check** Returning a pointer (also check that Fruit takes ownership) +* Check that lambdas with captures are forbidden +* **TODO** Check that non-lambda functors/functions are forbidden +* **TODO** Check that objects without operator() are forbidden +* Passing a non-signature type +* **TODO** Passing a signature type incompatible with the lambda's signature +* **TODO** With a lambda mistakenly taking an Assisted<X> or Annotated<A,X> parameter (instead of just using Assisted/Annotated in the Inject typedef) +* **TODO** For an abstract type (ok) +* With a provider that returns nullptr (runtime error) + +#### Factory bindings +* Explicit, using `registerFactory()` +* Implicitly, with a signature "returning" an annotated type (not ok) +* **TODO** Explicit, using `registerFactory()`, but passing a non-signature +* Explicit, using `registerFactory()`, but with a lambda that has a different signature compared to the one given explicitly +* Implicitly, with a signature that doesn't match any of the type's constructors +* Check that lambdas with captures are forbidden in `registerFactory()` +* **TODO** Check that non-lambda functors/functions are forbidden in `registerFactory()` +* **TODO** Check that objects without operator() are forbidden in `registerFactory()` +* Using the INJECT macro +* With some assisted params and some injected params +* **TODO** With no assisted params but some injected params +* With some assisted params but no injected params +* **TODO** With no assisted params and no injected params +* **TODO** Using the factory in another class' constructor instead of getting it from the injector directly +* **TODO** With a lambda mistakenly taking a Assisted<X>/Annotated<A,X> parameter (instead of just using Assisted/Annotated in the Inject typedef) +* Explicit, for an abstract type (ok) +* Implicit, for an abstract class (not ok) +* Explicit, with a lambda returning a pointer (not supported) +* Explicit, with a lambda returning a unique ptr (ok) +* **TODO** With assisted params of all kinds of non-normalized types (especially in ASSISTED) +* Implicitly, registering a `std::function<T(...)>` instead of a `std::function<std::unique_ptr<T>(...)>` +* Explicitly, registering a `std::function<T(...)>` instead of a `std::function<std::unique_ptr<T>(...)>` +* Implicitly, generating a binding for std::function<T()> when there is a binding for T +* Implicitly, generating a binding for std::function<std::unique_ptr<T>()> when there is a binding for T +* **TODO** Check that assisted params are passed in the right order when there are multiple +* **TODO** Try calling the factory multiple times +* Injecting a std::function<std::unique_ptr<T>(...)> with T not movable + +#### Annotated bindings +* **TODO** Using `fruit::Annotated<>` +* **TODO** Using the ANNOTATED macro (only in constructors using INJECT) +* **TODO** Check possibly-misleading behavior of binding Annotated<A1, I> and Annotated<A2, I> to C (only 1 C instance is created and shared) +* **TODO** With assisted params of all kinds of non-normalized types (especially in ANNOTATED) + +#### Multibindings +* Interface multibindings +* **TODO** Check that addMultibinding<I, I> causes an easy-to-understand error +* Instance multibindings +* **TODO** Check that calling addInstanceMultibinding with a non-normalized type (e.g. const pointer, nonconst ptr, etc.) causes an error +* **TODO** `addInstanceMultibindings(x)`, `addInstanceMultibindings<T>(x)` and `addInstanceMultibindings<Annotated<A, T>>(x)` +* **TODO** `addInstanceMultibindings()` with an empty vector +* **TODO** Check that calling `addInstanceMultibindings()` with a non-normalized type causes an error +* `addMultibindingProvider`: + * Returning a value + * **TODO: ownership check** Returning a pointer (also check that Fruit takes ownership) + * Check that lambdas with captures are forbidden + * **TODO** Check that non-lambda functors/functions are forbidden + * **TODO** Check that objects without operator() are forbidden + * Passing a non-signature type + * **TODO** Passing a signature type incompatible with the lambda's signature + * **TODO** With a lambda mistakenly taking an Assisted<X> or Annotated<A,X> parameter (instead of just using Assisted/Annotated in the Inject typedef) + * For an abstract type (not ok) + * With a provider that returns nullptr (runtime error) + +#### PartialComponent and Component +* copy a Component +* move a Component +* move a PartialComponent +* construction of a Component from another Component +* construction of a Component from a PartialComponent +* install() (old and new style) +* Type already bound (various combinations, incl. binding+install) +* No binding found for abstract class +* Dependency loops +* Run-time error for multiple inconsistent bindings in different components +* Class-level static_asserts in Component + * Check that there are no repeated types + * Check that no type is both in Required<> and outside + * Check that all types are normalized + * Check that Required only appears once + * Check that Required only appears as first parameter (if at all) + +#### Normalized components +* Constructing an injector from NC + C +* **TODO** Constructing an injector from NC + C with empty NC or empty C +* With requirements +* Class-level static_asserts + * Check that there are no repeated types + * Check that no type is both in Required<> and outside + * **TODO** Check that all types are normalized + * Check that Required only appears once + * Check that Required only appears as first parameter (if at all) + +#### Components with requirements +* Usual case (where the required type is only forward-declared, with no definition available) +* Usual case (where the required type is defined but it's an abstract class) +* **TODO** Check that requirements aren't allowed in injectors +* Check that multiple Required<...> params are not allowed +* Check that the Required<...> param is only allowed if it's the 1st +* **TODO** Check that an empty Required<...> param is allowed + +#### Injectors +* **TODO** `std::move()`-ing an injector +* Getting instances from an Injector: + * **TODO** Using `get<T>` (for all type variations) + * **TODO** Using `get()` or casting to try to get a value that the injector doesn't provide + * **TODO** Casting the injector to the desired type +* Getting multibindings from an Injector + * for a type that has no multibindings + * for a type that has 1 multibinding + * for a type that has >1 multibindings +* **TODO** Eager injection +* **TODO** Check that the component (in the constructor from C) has no requirements +* **TODO** Check that the resulting component (in the constructor from C+NC) has no requirements +* **TODO: partial** Empty injector (construct, get multibindings, eager injection, etc.) +* **TODO** Injector with a single instance type bound and nothing else +* **TODO** Injector with a single bindProvider and nothing else +* **TODO** Injector with a single multibinding and nothing else +* **TODO** Injector with a single factory and nothing else +* Injector<T> where the C doesn't provide T +* Injector<T> where the C+NC don't provide T +* Class-level static_asserts + * Check that there are no repeated types + * Check that all types are normalized + * Check that there are no Required types + +#### Injecting Provider<>s +* **TODO** In constructors +* Getting a Provider<> from an injector using get<> or casting the injector) +* **TODO** Getting a Provider<> from an injector by casting the injector +* In a constructor and calling get() before the constructor completes +* **TODO** casting a Provider to the desired value instead of calling `get()` +* **TODO** Calling either `get<T>()` or `get()` on the Provider +* **TODO** Check that a Provider's type argument is normalized and not annotated +* Copying a Provider and using the copy +* Using `get()` to try to get a value that the provider doesn't provide +* Class-level static_asserts + * Check that the type is normalized + * Check that the type is not annotated diff --git a/tests/util/BUILD b/tests/util/BUILD new file mode 100644 index 0000000..632aa14 --- /dev/null +++ b/tests/util/BUILD @@ -0,0 +1,9 @@ + +licenses(["notice"]) + +load("//third_party/fruit/tests:build_defs.bzl", "fruit_py_tests") + +fruit_py_tests( + srcs = glob(["test_*.py"]), +) + diff --git a/tests/util/test_lambda_invoker.py b/tests/util/test_lambda_invoker.py new file mode 100644 index 0000000..321962f --- /dev/null +++ b/tests/util/test_lambda_invoker.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + using namespace std; + using namespace fruit::impl; + ''' + +def test_invoke_no_args(): + source = ''' + int main() { + // This is static because the lambda must have no captures. + static int num_invocations = 0; + + auto l = []() { + ++num_invocations; + }; + using L = decltype(l); + LambdaInvoker::invoke<L>(); + Assert(num_invocations == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_invoke_some_args(): + source = ''' + int main() { + // This is static because the lambda must have no captures. + static int num_invocations = 0; + + auto l = [](int n, double x) { + Assert(n == 5); + Assert(x == 3.14); + ++num_invocations; + }; + using L = decltype(l); + LambdaInvoker::invoke<L>(5, 3.14); + Assert(num_invocations == 1); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/util/test_type_info.py b/tests/util/test_type_info.py new file mode 100644 index 0000000..c1225e0 --- /dev/null +++ b/tests/util/test_type_info.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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. + +from fruit_test_common import * + +COMMON_DEFINITIONS = ''' + #include "test_common.h" + + using namespace std; + using namespace fruit::impl; + ''' + +def test_size(): + source = ''' + int main() { + Assert(getTypeId<char[27]>().type_info->size() == 27); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_alignment(): + source = ''' + struct alignas(128) TypeAligned128 { + }; + + int main() { + Assert(getTypeId<TypeAligned128>().type_info->alignment() == 128); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_name(): + source = ''' + struct MyStruct { + }; + + int main() { + std::string result = getTypeId<MyStruct>().type_info->name(); + if (result != "MyStruct" && result != "struct MyStruct") { + std::cerr << "Demangling failed." << std::endl; + std::cerr << "typeid(MyStruct).name() == " << typeid(MyStruct).name() << std::endl; + std::cerr << "getTypeId<MyStruct>().type_info->name() == " << result << std::endl; + abort(); + } + Assert(std::string(getTypeId<MyStruct>()) == "MyStruct" || std::string(getTypeId<MyStruct>()) == "struct MyStruct"); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_isTriviallyDestructible_true(): + source = ''' + int main() { + Assert(getTypeId<int>().type_info->isTriviallyDestructible()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +def test_isTriviallyDestructible_false(): + source = ''' + int main() { + Assert(!getTypeId<std::vector<int>>().type_info->isTriviallyDestructible()); + } + ''' + expect_success( + COMMON_DEFINITIONS, + source, + locals()) + +if __name__== '__main__': + main(__file__) diff --git a/tests/valgrind_suppressions.supp b/tests/valgrind_suppressions.supp new file mode 100644 index 0000000..d56279e --- /dev/null +++ b/tests/valgrind_suppressions.supp @@ -0,0 +1,48 @@ +{ + malloc/__emutls_get_address + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:__emutls_get_address +} +{ + malloc/__dtoa + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:__dtoa +} +{ + malloc/tlv_get_addr + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:tlv_get_addr +} +{ + malloc/__cxa_get_globals + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:__cxa_get_globals +} +{ + malloc/ImageLoader + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:_ZN11ImageLoader15runInitializersERKNS_11LinkContextERNS_21InitializerTimingListE +} +{ + malloc/fwrite + Memcheck:Leak + fun:malloc + fun:__smakebuf + fun:__swsetup + fun:__sfvwrite + fun:fwrite + ... +} |