Add VTKM_DEPRECATED macro

The `VTKM_DEPRECATED` macro allows us to remove (and usually replace)
features from VTK-m in minor releases while still following the conventions
of semantic versioning. The idea is that when we want to remove or replace
a feature, we first mark the old feature as deprecated. The old feature
will continue to work, but compilers that support it will start to issue a
warning that the use is deprecated and should stop being used. The
deprecated features should remain viable until at least the next major
version. At the next major version, deprecated features from the previous
version may be removed.
This commit is contained in:
Kenneth Moreland 2019-11-05 11:12:28 -05:00
parent 05917e11a1
commit ed4d0d50c3
8 changed files with 279 additions and 6 deletions

@ -0,0 +1,70 @@
# Add VTKM_DEPRECATED macro
The `VTKM_DEPRECATED` macro allows us to remove (and usually replace)
features from VTK-m in minor releases while still following the conventions
of semantic versioning. The idea is that when we want to remove or replace
a feature, we first mark the old feature as deprecated. The old feature
will continue to work, but compilers that support it will start to issue a
warning that the use is deprecated and should stop being used. The
deprecated features should remain viable until at least the next major
version. At the next major version, deprecated features from the previous
version may be removed.
Classes and methods are marked deprecated using the `VTKM_DEPRECATED`
macro. The first argument of `VTKM_DEPRECATED` should be set to the first
version in which the feature is deprecated. For example, if the last
released version of VTK-m was 1.5, and on the master branch a developer
wants to deprecate a class foo, then the `VTKM_DEPRECATED` release version
should be given as 1.6, which will be the next minor release of VTK-m. The
second argument of `VTKM_DEPRECATED`, which is optional but highly
encouraged, is a short message that should clue developers on how to update
their code to the new changes. For example, it could point to the
replacement class or method for the changed feature.
`VTKM_DEPRECATED` can be used to deprecate a class by adding it between the
`struct` or `class` keyword and the class name.
``` cpp
struct VTKM_DEPRECATED(1.6, "OldClass replaced with NewClass.") OldClass
{
};
```
Aliases can similarly be depreciated, except the `VTKM_DEPRECATED` macro
goes after the name in this case.
``` cpp
using OldAlias VTKM_DEPRECATED(1.6, "Use NewClass instead.") = NewClass;
```
Functions and methods are marked as deprecated by adding `VTKM_DEPRECATED`
as a modifier before the return value.
``` cpp
VTKM_EXEC_CONT
VTKM_DEPRECATED(1.6, "You must now specify a tolerance.") void ImportantMethod(double x)
{
this->ImportantMethod(x, 1e-6);
}
```
`enum`s can be deprecated like classes using similar syntax.
``` cpp
enum struct VTKM_DEPRECATED(1.7, "Use NewEnum instead.") OldEnum
{
OLD_VALUE
};
```
Individual items in an `enum` can also be marked as deprecated and
intermixed with regular items.
``` cpp
enum struct NewEnum
{
OLD_VALUE1 VTKM_DEPRECATED(1.7, "Use NEW_VALUE instead."),
NEW_VALUE,
OLD_VALUE2 VTKM_DEPRECATED(1.7) = 42
};
```

@ -25,6 +25,7 @@ set(headers
CellClassification.h
CellShape.h
CellTraits.h
Deprecated.h
Flags.h
Geometry.h
Hash.h

38
vtkm/Deprecated.h Normal file

@ -0,0 +1,38 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#ifndef vtk_m_Deprecated_h
#define vtk_m_Deprecated_h
#include <vtkm/internal/CompilerFeatures.h>
#include <vtkm/StaticAssert.h>
#include <vtkm/Types.h>
#define VTK_M_DEPRECATED_MAKE_MESSAGE(...) \
VTKM_EXPAND(VTK_M_DEPRECATED_MAKE_MESSAGE_IMPL(__VA_ARGS__, "", vtkm::internal::NullType{}))
#define VTK_M_DEPRECATED_MAKE_MESSAGE_IMPL(version, message, ...) \
message " Deprecated in version " #version "."
/// \def VTKM_DEPRECATED(version, message)
///
/// Classes and methods are marked deprecated using the `VTKM_DEPRECATED`
/// macro. The first argument of `VTKM_DEPRECATED` should be set to the first
/// version in which the feature is deprecated. For example, if the last
/// released version of VTK-m was 1.5, and on the master branch a developer
/// wants to deprecate a class foo, then the `VTKM_DEPRECATED` release version
/// should be given as 1.6, which will be the next minor release of VTK-m. The
/// second argument of `VTKM_DEPRECATED`, which is optional but highly
/// encouraged, is a short message that should clue developers on how to update
/// their code to the new changes. For example, it could point to the
/// replacement class or method for the changed feature.
///
#define VTKM_DEPRECATED(...) VTK_M_DEPRECATED_MSG(VTK_M_DEPRECATED_MAKE_MESSAGE(__VA_ARGS__))
#endif // vtk_m_Deprecated_h

@ -33,8 +33,18 @@ vtkm_get_kit_name(kit_name kit_dir)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Configure.h.in
${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/Configure.h
@ONLY)
vtkm_install_headers(
vtkm/internal ${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/Configure.h)
include(WriteCompilerDetectionHeader)
write_compiler_detection_header(
FILE ${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/CompilerFeatures.h
PREFIX VTK_M
COMPILERS ${CMAKE_CXX_COMPILER_ID} GNU Clang AppleClang MSVC SunPro Intel
FEATURES cxx_attribute_deprecated
ALLOW_UNKNOWN_COMPILERS
)
vtkm_install_headers(vtkm/internal
${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/Configure.h
${VTKm_BINARY_INCLUDE_DIR}/${kit_dir}/CompilerFeatures.h
)
unset(VTKM_ENABLE_MPI)
unset(VTKM_ENABLE_TBB)

@ -278,11 +278,44 @@
while (false)
#endif
// Use a trick to pass arguments containing commas through a macro. This is
// helpful for mixing template code with macros.
// See https://stackoverflow.com/questions/13842468
/// \def VTKM_PASS_COMMAS(...)
///
/// A trick to pass arguments containing commas through a macro. This is
/// helpful for mixing template code with macros.
///
/// See https://stackoverflow.com/questions/13842468
#define VTKM_PASS_COMMAS(...) __VA_ARGS__
/// \def VTKM_EXPAND(expr)
///
/// A utility macro to expand the arguments of macro before invoking it in the
/// preprocessor. This is mostly used to handle the `__VA_ARGS__` created for
/// variadic preprocessor macros. Often you will have to pass `__VA_ARGS__` to
/// another macro to tease out particular parameters. For example, to get the
/// first argument, you might make something like this.
///
/// ```cpp
/// #define GET_FIRST_ARGUMENT(...) GET_FIRST_ARGUMENT_IMPL(__VA_ARGS__, no_arg)
/// #define GET_FIRST_ARGUMENT_IMPL(first, ...) first
/// ```
///
/// You would expect this pair of macros to give you the first argument or the
/// token `no_arg` if no arguments were given, and for most compilers that
/// is what you would get. But Visual Studio in particular has a weird
/// interpretation of the standard that causes `__VA_ARGS__` to be treated
/// as a single argument when passed to another macro. Consequently, for the
/// example above, Visual Studio actually returns all args passed instead of
/// the first. To get around the problem, you can wrap the entire call to
/// the secondary macro in VTKM_EXPAND to get Visual Studio (and all other
/// compilers) to properly treat `__VA_ARGS__` as separate arguments.
///
/// ```cpp
/// #define GET_FIRST_ARGUMENT(...) VTKM_EXPAND(GET_FIRST_ARGUMENT_IMPL(__VA_ARGS__, no_arg))
/// #define GET_FIRST_ARGUMENT_IMPL(first, ...) first
/// ```
///
#define VTKM_EXPAND(expr) expr
#ifdef VTKM_MSVC
//With MSVC the types that we generate cause warning C4503 (long symbol names)
//this doesn't affect the resulting binary so we just suppress that warning

@ -22,6 +22,7 @@ set(unit_tests
UnitTestBinaryOperators.cxx
UnitTestBounds.cxx
UnitTestCellShape.cxx
UnitTestDeprecated.cxx
UnitTestExceptions.cxx
UnitTestHash.cxx
UnitTestListTag.cxx

@ -50,7 +50,6 @@
#define VTKM_STRINGIFY_FIRST(...) VTKM_EXPAND(VTK_M_STRINGIFY_FIRST_IMPL(__VA_ARGS__, dummy))
#define VTK_M_STRINGIFY_FIRST_IMPL(first, ...) #first
#define VTKM_EXPAND(x) x
/// \def VTKM_TEST_ASSERT(condition, messages..)
///
@ -570,6 +569,30 @@ struct TestEqualImpl<std::string, std::string>
return string1 == string2;
}
};
template <typename T>
struct TestEqualImpl<const char*, T>
{
VTKM_CONT bool operator()(const char* string1, T value2, vtkm::Float64 tolerance) const
{
return TestEqualImpl<std::string, T>()(string1, value2, tolerance);
}
};
template <typename T>
struct TestEqualImpl<T, const char*>
{
VTKM_CONT bool operator()(T value1, const char* string2, vtkm::Float64 tolerance) const
{
return TestEqualImpl<T, std::string>()(value1, string2, tolerance);
}
};
template <>
struct TestEqualImpl<const char*, const char*>
{
VTKM_CONT bool operator()(const char* string1, const char* string2, vtkm::Float64 tolerance) const
{
return TestEqualImpl<std::string, std::string>()(string1, string2, tolerance);
}
};
/// Special implementation of test_equal for Pairs, which are a bit different
/// than a vector of numbers of the same type.

@ -0,0 +1,97 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/Deprecated.h>
#include <vtkm/testing/Testing.h>
namespace
{
struct NewClass
{
VTKM_EXEC_CONT
void ImportantMethod(double x, double tolerance)
{
std::cout << "Using " << x << " with tolerance " << tolerance << std::endl;
}
VTKM_EXEC_CONT
VTKM_DEPRECATED(1.6, "You must now specify a tolerance.") void ImportantMethod(double x)
{
this->ImportantMethod(x, 1e-6);
}
};
struct VTKM_DEPRECATED(1.6, "OldClass replaced with NewClass.") OldClass
{
};
using OldAlias VTKM_DEPRECATED(1.6, "Use NewClass instead.") = NewClass;
//// Should be OK for one deprecated alias to use another deprecated thing (you would think).
//using OlderAlias VTKM_DEPRECATED(1.6, "Use NewClass instead.") = OldAlias;
enum struct VTKM_DEPRECATED(1.7, "Use NewEnum instead.") OldEnum
{
OLD_VALUE
};
enum struct NewEnum
{
OLD_VALUE1 VTKM_DEPRECATED(1.7, "Use NEW_VALUE instead."),
NEW_VALUE,
OLD_VALUE2 VTKM_DEPRECATED(1.7) = 42
};
template <typename T>
void DoSomethingWithObject(T)
{
std::cout << "Looking at " << typeid(T).name() << std::endl;
}
static void DoTest()
{
std::cout << "C++14 deprecated supported: " << VTK_M_COMPILER_CXX_ATTRIBUTE_DEPRECATED
<< std::endl;
std::cout << "Deprecation is: " << VTKM_STRINGIFY_FIRST(VTKM_DEPRECATED(X.Y, "Message."))
<< std::endl;
VTKM_TEST_ASSERT(test_equal(VTK_M_DEPRECATED_MAKE_MESSAGE(X.Y), " Deprecated in version X.Y."));
VTKM_TEST_ASSERT(test_equal(VTK_M_DEPRECATED_MAKE_MESSAGE(X.Y.Z, "Use feature foo instead."),
"Use feature foo instead. Deprecated in version X.Y.Z."));
// Using valid classes with unused deprecated parts should be fine.
NewClass useIt;
DoSomethingWithObject(useIt);
useIt.ImportantMethod(1.1, 1e-8);
DoSomethingWithObject(NewEnum::NEW_VALUE);
// These should each give compiler warnings.
#if 0
OldClass useOldClass;
DoSomethingWithObject(useOldClass);
OldAlias useOldAlias;
DoSomethingWithObject(useOldAlias);
//OlderAlias useOlderAlias;
//DoSomethingWithObject(useOlderAlias);
useIt.ImportantMethod(1.1);
DoSomethingWithObject(OldEnum::OLD_VALUE);
DoSomethingWithObject(NewEnum::OLD_VALUE1);
DoSomethingWithObject(NewEnum::OLD_VALUE2);
#endif
}
} // anonymous namespace
int UnitTestDeprecated(int argc, char* argv[])
{
return vtkm::testing::Testing::Run(DoTest, argc, argv);
}