list: Reject macros as APPLY/PREDICATE/COMPARATOR arguments

Fixes: #27781
This commit is contained in:
Mickaël Germain
2026-04-30 21:44:32 -07:00
parent 08288eb14a
commit 96c9a9c67d
21 changed files with 94 additions and 102 deletions

View File

@@ -139,7 +139,7 @@ Modification
:ref:`string(REGEX) <Regex Specification>`.
``PREDICATE``
Specify a user-defined callable as a predicate.
Specify a user-defined :command:`function` as a predicate.
.. code-block:: cmake
@@ -148,11 +148,11 @@ Modification
.. versionadded:: 4.4
``<function>`` is a user-defined :command:`function` that acts as a
unary predicate. The callable must accept exactly two parameters: the
input value and the name of an output variable. The callable must set the
unary predicate. The function must accept exactly two parameters: the
input value and the name of an output variable. The function must set the
output variable to a boolean value in the calling scope.
The output variable is interpreted using standard CMake boolean evaluation.
If the callable does not set the output variable, it is an error.
If the function does not set the output variable, it is an error.
Example:
@@ -290,9 +290,9 @@ Modification
See policy :policy:`CMP0186`.
``APPLY``
Invoke a user-defined callable for each element of the list.
The callable must accept exactly two parameters: the input value and the
name of an output variable. The callable must set the output variable
Invoke a user-defined :command:`function` for each element of the list.
The function must accept exactly two parameters: the input value and the
name of an output variable. The function must set the output variable
in the calling scope.
.. signature::
@@ -356,8 +356,8 @@ Modification
list(TRANSFORM <list> <ACTION> REGEX <regular_expression> ...)
``PREDICATE``
Specify a user-defined callable as a predicate.
Only elements for which the callable returns a true value will be
Specify a user-defined :command:`function` as a predicate.
Only elements for which the function returns a true value will be
transformed.
.. code-block:: cmake
@@ -368,9 +368,9 @@ Modification
``<function>`` is a user-defined :command:`function` with exactly
two formal parameters: the input value and the name of an output
variable. The callable must set the output variable to a boolean
variable. The function must set the output variable to a boolean
value. Standard CMake boolean evaluation is used.
If the callable does not set the output variable, it is an error.
If the function does not set the output variable, it is an error.
Example:
@@ -450,21 +450,21 @@ Ordering
Sorts the list in descending order.
Instead of the built-in ``COMPARE`` methods, a user-defined comparison
function may be used with the ``COMPARATOR`` keyword.
:command:`function` may be used with the ``COMPARATOR`` keyword.
.. versionadded:: 4.4
``COMPARATOR`` is mutually exclusive with ``COMPARE``. The ``CASE``
and ``ORDER`` options may still be used alongside ``COMPARATOR``:
the ``CASE`` filter is applied to both values before they are passed to
the callable, and ``ORDER DESCENDING`` reverses the comparison by
the function, and ``ORDER DESCENDING`` reverses the comparison by
swapping the two arguments.
The callable must accept exactly three parameters: two input values and
The function must accept exactly three parameters: two input values and
the name of an output variable. It must set the output variable to a
boolean value (``TRUE`` if the first value should come before the second,
``FALSE`` otherwise) in the calling scope.
If the callable does not set the output variable, it is an error.
If the function does not set the output variable, it is an error.
The comparator must define a
`strict weak ordering <https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings>`_.

View File

@@ -2,9 +2,9 @@ list-PREDICATE
--------------
* The :command:`list(TRANSFORM)` command gained a new ``PREDICATE`` selector
that invokes a user-defined callable to decide which elements are
that invokes a user-defined :command:`function` to decide which elements are
transformed.
* The :command:`list(FILTER)` command gained a new ``PREDICATE`` mode
that invokes a user-defined callable to decide which elements are
that invokes a user-defined :command:`function` to decide which elements are
included or excluded, complementing the existing ``REGEX`` mode.

View File

@@ -14,6 +14,7 @@
#include <utility>
#include <cm/memory>
#include <cm/optional>
#include "cmsys/RegularExpression.hxx"
@@ -24,6 +25,7 @@
#include "cmMakefile.h"
#include "cmRange.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmStringReplaceHelper.h"
#include "cmSystemTools.h"
@@ -96,10 +98,18 @@ void RequireFunction(cmMakefile const& makefile,
std::string const& functionName,
std::string const& errorPrefix)
{
if (!makefile.GetState()->GetCommand(functionName)) {
cm::optional<cmStateEnums::CommandType> type =
makefile.GetState()->GetCommandType(functionName);
if (!type) {
throw cmList::transform_error(
cmStrCat(errorPrefix, ": unknown function \"", functionName, "\"."));
}
if (*type == cmStateEnums::CommandType::Macro) {
throw cmList::transform_error(
cmStrCat(errorPrefix, ": macro \"", functionName,
"\" may not be used here;"
" define it as a function() instead."));
}
}
class PredicateEvaluator

View File

@@ -12,22 +12,6 @@ message("mylist was: ${mylist}")
list(FILTER mylist EXCLUDE PREDICATE starts_with_filter)
message("mylist is: ${mylist}")
# EXCLUDE with macro predicate
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 6)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist ab cdefgh ij klmnop qr)
list(FILTER mylist EXCLUDE PREDICATE is_short)
if(NOT mylist STREQUAL "cdefgh;klmnop")
message(FATAL_ERROR "FILTER(EXCLUDE PREDICATE macro) is \"${mylist}\", expected \"cdefgh;klmnop\"")
endif()
# EXCLUDE on empty list
set(empty_list "")
list(FILTER empty_list EXCLUDE PREDICATE starts_with_filter)

View File

@@ -7,28 +7,11 @@ function(starts_with_filter input result)
endif()
endfunction()
# Predicate as macro
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 6)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist FILTER_THIS_BIT DO_NOT_FILTER_THIS thisisanitem FILTER_THIS_THING)
message("mylist was: ${mylist}")
list(FILTER mylist INCLUDE PREDICATE starts_with_filter)
message("mylist is: ${mylist}")
# INCLUDE with macro predicate
set(mylist ab cdefgh ij klmnop qr)
list(FILTER mylist INCLUDE PREDICATE is_short)
if(NOT mylist STREQUAL "ab;ij;qr")
message(FATAL_ERROR "FILTER(INCLUDE PREDICATE macro) is \"${mylist}\", expected \"ab;ij;qr\"")
endif()
# INCLUDE on empty list
set(empty_list "")
list(FILTER empty_list INCLUDE PREDICATE starts_with_filter)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at FILTER-PREDICATE-Macro\.cmake:11 \(list\):
list sub-command FILTER, mode PREDICATE: macro "is_short" may not be used
here; define it as a function\(\) instead\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,11 @@
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 4)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist alpha bravo charlie)
list(FILTER mylist INCLUDE PREDICATE is_short)

View File

@@ -38,6 +38,7 @@ run_cmake(FILTER-REGEX-InvalidOperator)
run_cmake(FILTER-REGEX-Valid0)
run_cmake(FILTER-REGEX-Valid1)
run_cmake(FILTER-PREDICATE-UnknownFunction)
run_cmake(FILTER-PREDICATE-Macro)
run_cmake(FILTER-PREDICATE-NoOutput)
run_cmake(FILTER-PREDICATE-TooManyArguments)
run_cmake(FILTER-PREDICATE-Include)
@@ -71,6 +72,7 @@ run_cmake(TRANSFORM-REPLACE-InvalidReplace1)
run_cmake(TRANSFORM-REPLACE-InvalidReplace2)
run_cmake(TRANSFORM-APPLY-NoFunction)
run_cmake(TRANSFORM-APPLY-UnknownFunction)
run_cmake(TRANSFORM-APPLY-Macro)
run_cmake(TRANSFORM-APPLY-NoOutput)
# 'selector' oriented tests
run_cmake(TRANSFORM-Selector-REGEX-NoArguments)
@@ -89,6 +91,7 @@ run_cmake(TRANSFORM-Selector-FOR-NegativeStepArgument)
run_cmake(TRANSFORM-Selector-FOR-BackwardsRange)
run_cmake(TRANSFORM-Selector-PREDICATE-NoArguments)
run_cmake(TRANSFORM-Selector-PREDICATE-UnknownFunction)
run_cmake(TRANSFORM-Selector-PREDICATE-Macro)
run_cmake(TRANSFORM-Selector-PREDICATE-NoOutput)
# 'output' oriented tests
run_cmake(TRANSFORM-Output-OUTPUT_VARIABLE-NoArguments)
@@ -115,6 +118,7 @@ run_cmake(SORT-DuplicateCompareOption)
run_cmake(SORT-DuplicateCaseOption)
run_cmake(SORT-NoCaseOption)
run_cmake(SORT-COMPARATOR-UnknownFunction)
run_cmake(SORT-COMPARATOR-Macro)
run_cmake(SORT-COMPARATOR-NoOutput)
run_cmake(SORT-COMPARATOR-CompareConflict)
run_cmake(SORT-COMPARATOR-CompareConflictReverse)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at SORT-COMPARATOR-Macro\.cmake:10 \(list\):
list sub-command SORT, COMPARATOR: macro "string_less_macro" may not be
used here; define it as a function\(\) instead\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,10 @@
macro(string_less_macro a b result)
if(${a} STRLESS ${b})
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist c a b)
list(SORT mylist COMPARATOR string_less_macro)

View File

@@ -7,15 +7,6 @@ function(string_less a b result)
endif()
endfunction()
# Comparator as macro
macro(string_less_macro a b result)
if(${a} STRLESS ${b})
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
# Comparator: string length comparison
function(shorter_first a b result)
string(LENGTH "${a}" len_a)
@@ -34,13 +25,6 @@ if(NOT mylist STREQUAL "a;b;c")
message(FATAL_ERROR "SORT(COMPARATOR function) is \"${mylist}\", expected \"a;b;c\"")
endif()
## COMPARATOR with macro
set(mylist c a b)
list(SORT mylist COMPARATOR string_less_macro)
if(NOT mylist STREQUAL "a;b;c")
message(FATAL_ERROR "SORT(COMPARATOR macro) is \"${mylist}\", expected \"a;b;c\"")
endif()
## COMPARATOR with ORDER DESCENDING
set(mylist c a b)
list(SORT mylist COMPARATOR string_less ORDER DESCENDING)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error at TRANSFORM-APPLY-Macro\.cmake:6 \(list\):
list sub-command TRANSFORM, action APPLY: macro "wrap_angles" may not be
used here; define it as a function\(\) instead\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,6 @@
macro(wrap_angles in out)
set(${out} "<${in}>")
endmacro()
set(mylist alpha bravo charlie)
list(TRANSFORM mylist APPLY wrap_angles)

View File

@@ -8,11 +8,6 @@ function(add_prefix in out)
set(${out} "prefix_${in}" PARENT_SCOPE)
endfunction()
# Define a transform macro: wrap in angle brackets
macro(wrap_angles in out)
set(${out} "<${in}>")
endmacro()
set(mylist alpha bravo charlie delta)
# Basic APPLY - all elements
@@ -74,19 +69,6 @@ if(NOT output STREQUAL "prefix_alpha;prefix_bravo;prefix_charlie;prefix_delta")
message(FATAL_ERROR "TRANSFORM(APPLY add_prefix) is \"${output}\", expected is \"prefix_alpha;prefix_bravo;prefix_charlie;prefix_delta\"")
endif()
# APPLY with a macro
set(mylist alpha bravo charlie)
list(TRANSFORM mylist APPLY wrap_angles OUTPUT_VARIABLE output)
if(NOT output STREQUAL "<alpha>;<bravo>;<charlie>")
message(FATAL_ERROR "TRANSFORM(APPLY macro) is \"${output}\", expected is \"<alpha>;<bravo>;<charlie>\"")
endif()
# APPLY with macro and selector
list(TRANSFORM mylist APPLY wrap_angles AT 0 2 OUTPUT_VARIABLE output)
if(NOT output STREQUAL "<alpha>;bravo;<charlie>")
message(FATAL_ERROR "TRANSFORM(APPLY macro AT) is \"${output}\", expected is \"<alpha>;bravo;<charlie>\"")
endif()
# APPLY on empty list
set(empty_list "")
list(TRANSFORM empty_list APPLY add_src_prefix OUTPUT_VARIABLE output)

View File

@@ -7,16 +7,6 @@ function(starts_with_b input result)
endif()
endfunction()
# Predicate as macro
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 4)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist alpha bravo charlie bravo_two delta)
# Basic PREDICATE with TOUPPER - only items starting with "b" are uppercased
@@ -36,13 +26,6 @@ if(NOT mylist STREQUAL "alpha;BRAVO;charlie;BRAVO_TWO;delta")
message(FATAL_ERROR "TRANSFORM(TOUPPER PREDICATE in-place) is \"${mylist}\", expected \"alpha;BRAVO;charlie;BRAVO_TWO;delta\"")
endif()
# PREDICATE with macro
set(mylist ab cde fg hijklm no)
list(TRANSFORM mylist TOUPPER PREDICATE is_short OUTPUT_VARIABLE output)
if(NOT output STREQUAL "AB;CDE;FG;hijklm;NO")
message(FATAL_ERROR "TRANSFORM(TOUPPER PREDICATE macro) is \"${output}\", expected \"AB;CDE;FG;hijklm;NO\"")
endif()
# PREDICATE combined with APPLY
function(add_prefix in out)
set(${out} "prefix_${in}" PARENT_SCOPE)

View File

@@ -0,0 +1,5 @@
^CMake Error at TRANSFORM-Selector-PREDICATE-Macro\.cmake:11 \(list\):
list sub-command TRANSFORM, selector PREDICATE: macro "is_short" may not be
used here; define it as a function\(\) instead\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)$

View File

@@ -0,0 +1,11 @@
macro(is_short input result)
string(LENGTH "${input}" _len)
if(_len LESS 4)
set(${result} TRUE)
else()
set(${result} FALSE)
endif()
endmacro()
set(mylist alpha bravo charlie)
list(TRANSFORM mylist TOUPPER PREDICATE is_short)