Genex: add $<TARGET_FILE_PREFIX:...> and $<TARGET_FILE_SUFFIX:...>

These capabilities complement MR !3190
and is also needed to solve issue #18771.
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index e3a96bd..e9b3f4c 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -393,7 +393,18 @@
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on.
 ``$<TARGET_FILE:tgt>``
-  Full path to main file (.exe, .so.1.2, .a) where ``tgt`` is the name of a target.
+  Full path to main file (.exe, .so.1.2, .a) where ``tgt`` is the name of a
+  target.
+``$<TARGET_FILE_PREFIX:tgt>``
+  Prefix of main file where ``tgt`` is the name of a target.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+``$<TARGET_FILE_SUFFIX:tgt>``
+  Suffix of main file where ``tgt`` is the name of a target.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
 ``$<TARGET_FILE_NAME:tgt>``
   Name of main file (.exe, .so.1.2, .a).
 ``$<TARGET_FILE_DIR:tgt>``
@@ -405,6 +416,16 @@
   expression is evaluated on.
 ``$<TARGET_LINKER_FILE:tgt>``
   File used to link (.a, .lib, .so) where ``tgt`` is the name of a target.
+``$<TARGET_LINKER_FILE_PREFIX:tgt>``
+  Prefix of file used to link where ``tgt`` is the name of a target.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+``$<TARGET_LINKER_FILE_SUFFIX:tgt>``
+  Suffix of file used to link where ``tgt`` is the name of a target.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
 ``$<TARGET_LINKER_FILE_NAME:tgt>``
   Name of file used to link (.a, .lib, .so).
 ``$<TARGET_LINKER_FILE_DIR:tgt>``
diff --git a/Help/release/dev/genex-TARGET_FILE_PREFIX.rst b/Help/release/dev/genex-TARGET_FILE_PREFIX.rst
new file mode 100644
index 0000000..3e480bb
--- /dev/null
+++ b/Help/release/dev/genex-TARGET_FILE_PREFIX.rst
@@ -0,0 +1,7 @@
+genex-TARGET_FILE_PREFIX
+------------------------
+
+* New ``$<TARGET_FILE_PREFIX:...>``, ``$<TARGET_LINKER_FILE_PREFIX:...>``,
+  ``$<TARGET_FILE_SUFFIX:...>`` and ``$<TARGET_LINKER_FILE_SUFFIX:...>``
+  :manual:`generator expressions <cmake-generator-expressions(7)>` have been
+  added to retrieve the prefix and suffix of various artifacts.
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index d8e1c42..af409e4 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -2297,6 +2297,119 @@
 
 static const TargetOutputNameArtifact<ArtifactPdbTag> targetPdbOutputNameNode;
 
+class ArtifactFilePrefixTag;
+class ArtifactLinkerFilePrefixTag;
+class ArtifactFileSuffixTag;
+class ArtifactLinkerFileSuffixTag;
+
+template <typename ArtifactT>
+struct TargetFileArtifactResultGetter
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content);
+};
+
+template <>
+struct TargetFileArtifactResultGetter<ArtifactFilePrefixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent*)
+  {
+    return target->GetFilePrefix(context->Config);
+  }
+};
+template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerFilePrefixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable()) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_PREFIX is allowed only for libraries and "
+                    "executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    cmStateEnums::ArtifactType artifact =
+      target->HasImportLibrary(context->Config)
+      ? cmStateEnums::ImportLibraryArtifact
+      : cmStateEnums::RuntimeBinaryArtifact;
+
+    return target->GetFilePrefix(context->Config, artifact);
+  }
+};
+template <>
+struct TargetFileArtifactResultGetter<ArtifactFileSuffixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent*)
+  {
+    return target->GetFileSuffix(context->Config);
+  }
+};
+template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerFileSuffixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable()) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_SUFFIX is allowed only for libraries and "
+                    "executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    cmStateEnums::ArtifactType artifact =
+      target->HasImportLibrary(context->Config)
+      ? cmStateEnums::ImportLibraryArtifact
+      : cmStateEnums::RuntimeBinaryArtifact;
+
+    return target->GetFileSuffix(context->Config, artifact);
+  }
+};
+
+template <typename ArtifactT>
+struct TargetFileArtifact : public TargetArtifactBase
+{
+  TargetFileArtifact() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return 1; }
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    cmGeneratorTarget* target =
+      this->GetTarget(parameters, context, content, dagChecker);
+    if (!target) {
+      return std::string();
+    }
+
+    std::string result =
+      TargetFileArtifactResultGetter<ArtifactT>::Get(target, context, content);
+    if (context->HadError) {
+      return std::string();
+    }
+    return result;
+  }
+};
+
+static const TargetFileArtifact<ArtifactFilePrefixTag> targetFilePrefixNode;
+static const TargetFileArtifact<ArtifactLinkerFilePrefixTag>
+  targetLinkerFilePrefixNode;
+static const TargetFileArtifact<ArtifactFileSuffixTag> targetFileSuffixNode;
+static const TargetFileArtifact<ArtifactLinkerFileSuffixTag>
+  targetLinkerFileSuffixNode;
+
 static const struct ShellPathNode : public cmGeneratorExpressionNode
 {
   ShellPathNode() {} // NOLINT(modernize-use-equals-default)
@@ -2361,6 +2474,10 @@
     { "TARGET_LINKER_FILE", &targetLinkerNodeGroup.File },
     { "TARGET_SONAME_FILE", &targetSoNameNodeGroup.File },
     { "TARGET_PDB_FILE", &targetPdbNodeGroup.File },
+    { "TARGET_FILE_PREFIX", &targetFilePrefixNode },
+    { "TARGET_LINKER_FILE_PREFIX", &targetLinkerFilePrefixNode },
+    { "TARGET_FILE_SUFFIX", &targetFileSuffixNode },
+    { "TARGET_LINKER_FILE_SUFFIX", &targetLinkerFileSuffixNode },
     { "TARGET_FILE_NAME", &targetNodeGroup.FileName },
     { "TARGET_LINKER_FILE_NAME", &targetLinkerNodeGroup.FileName },
     { "TARGET_SONAME_FILE_NAME", &targetSoNameNodeGroup.FileName },
diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
index 4202064..0b0fb78 100644
--- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
@@ -33,6 +33,14 @@
 run_cmake(COMPILE_LANGUAGE-unknown-lang)
 run_cmake(TARGET_FILE-recursion)
 run_cmake(OUTPUT_NAME-recursion)
+run_cmake(TARGET_FILE_PREFIX)
+run_cmake(TARGET_FILE_PREFIX-imported-target)
+run_cmake(TARGET_FILE_PREFIX-non-valid-target)
+run_cmake(TARGET_LINKER_FILE_PREFIX-non-valid-target)
+run_cmake(TARGET_FILE_SUFFIX)
+run_cmake(TARGET_FILE_SUFFIX-imported-target)
+run_cmake(TARGET_FILE_SUFFIX-non-valid-target)
+run_cmake(TARGET_LINKER_FILE_SUFFIX-non-valid-target)
 run_cmake(TARGET_OUTPUT_NAME)
 run_cmake(TARGET_OUTPUT_NAME-imported-target)
 run_cmake(TARGET_OUTPUT_NAME-non-valid-target)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-check.cmake
new file mode 100644
index 0000000..676ad4b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_PREFIX-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target-check.cmake
new file mode 100644
index 0000000..676ad4b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_PREFIX-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target.cmake
new file mode 100644
index 0000000..34e500a
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-imported-target.cmake
@@ -0,0 +1,49 @@
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_language (C)
+
+set (win_platforms Windows CYGWIN)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_executable(exec1 IMPORTED)
+add_library (shared1 SHARED IMPORTED)
+add_library (static1 STATIC IMPORTED)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_PREFIX executable default\" \"$<TARGET_FILE_PREFIX:exec1>\" \"\")
+check_value (\"TARGET_FILE_PREFIX shared default\" \"$<TARGET_FILE_PREFIX:shared1>\" \"${CMAKE_SHARED_LIBRARY_PREFIX}\")
+check_value (\"TARGET_LINKER_FILE_PREFIX shared linker default\" \"$<TARGET_LINKER_FILE_PREFIX:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,${CMAKE_IMPORT_LIBRARY_PREFIX},${CMAKE_SHARED_LIBRARY_PREFIX}>\")
+check_value (\"TARGET_FILE_PREFIX static default\" \"$<TARGET_FILE_PREFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_PREFIX}\")
+check_value (\"TARGET_LINKER_FILE_PREFIX static linker default\" \"$<TARGET_LINKER_FILE_PREFIX:static1>\"  \"${CMAKE_STATIC_LIBRARY_PREFIX}\")\n")
+
+
+add_executable (exec2 IMPORTED)
+set_property (TARGET exec2 PROPERTY PREFIX exec2_prefix)
+set_property (TARGET exec2 PROPERTY ENABLE_EXPORTS TRUE)
+set_property (TARGET exec2 PROPERTY IMPORT_PREFIX exec2_import_prefix)
+add_library (shared2 SHARED IMPORTED)
+set_property (TARGET shared2 PROPERTY PREFIX shared2_prefix)
+set_property (TARGET shared2 PROPERTY IMPORT_PREFIX shared2_import_prefix)
+add_library (static2 STATIC IMPORTED)
+set_property (TARGET static2 PROPERTY PREFIX static2_prefix)
+set_property (TARGET static2 PROPERTY IMPORT_PREFIX static2_import_prefix)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_PREFIX executable custom\" \"$<TARGET_FILE_PREFIX:exec2>\" \"exec2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX executable linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,exec2_import_prefix,exec2_prefix>\")
+check_value (\"TARGET_FILE_PREFIX shared custom\" \"$<TARGET_FILE_PREFIX:shared2>\" \"shared2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX shared linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,shared2_import_prefix,shared2_prefix>\")
+check_value (\"TARGET_FILE_PREFIX static custom\" \"$<TARGET_FILE_PREFIX:static2>\" \"static2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX static linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:static2>\" \"static2_prefix\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_PREFIX-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-stderr.txt
new file mode 100644
index 0000000..81362ef
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_FILE_PREFIX-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_FILE_PREFIX:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target.cmake
new file mode 100644
index 0000000..d1095fa
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX-non-valid-target.cmake
@@ -0,0 +1,7 @@
+
+add_custom_target(empty)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_FILE_PREFIX:empty>]"
+)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX.cmake
new file mode 100644
index 0000000..6bb1e44
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_PREFIX.cmake
@@ -0,0 +1,49 @@
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_language (C)
+
+set (win_platforms Windows CYGWIN)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_executable (exec1 empty.c)
+add_library (shared1 SHARED empty.c)
+add_library (static1 STATIC empty.c)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_PREFIX executable default\" \"$<TARGET_FILE_PREFIX:exec1>\" \"\")
+check_value (\"TARGET_FILE_PREFIX shared default\" \"$<TARGET_FILE_PREFIX:shared1>\" \"${CMAKE_SHARED_LIBRARY_PREFIX}\")
+check_value (\"TARGET_LINKER_FILE_PREFIX shared linker default\" \"$<TARGET_LINKER_FILE_PREFIX:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,${CMAKE_IMPORT_LIBRARY_PREFIX},${CMAKE_SHARED_LIBRARY_PREFIX}>\")
+check_value (\"TARGET_FILE_PREFIX static default\" \"$<TARGET_FILE_PREFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_PREFIX}\")
+check_value (\"TARGET_LINKER_FILE_PREFIX static linker default\" \"$<TARGET_LINKER_FILE_PREFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_PREFIX}\")\n")
+
+
+add_executable (exec2 empty.c)
+set_property (TARGET exec2 PROPERTY PREFIX exec2_prefix)
+set_property (TARGET exec2 PROPERTY ENABLE_EXPORTS TRUE)
+set_property (TARGET exec2 PROPERTY IMPORT_PREFIX exec2_import_prefix)
+add_library (shared2 SHARED empty.c)
+set_property (TARGET shared2 PROPERTY PREFIX shared2_prefix)
+set_property (TARGET shared2 PROPERTY IMPORT_PREFIX shared2_import_prefix)
+add_library (static2 STATIC empty.c)
+set_property (TARGET static2 PROPERTY PREFIX static2_prefix)
+set_property (TARGET static2 PROPERTY IMPORT_PREFIX static2_import_prefix)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_PREFIX executable custom\" \"$<TARGET_FILE_PREFIX:exec2>\" \"exec2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX executable linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,exec2_import_prefix,exec2_prefix>\")
+check_value (\"TARGET_FILE_PREFIX shared custom\" \"$<TARGET_FILE_PREFIX:shared2>\" \"shared2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX shared linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,shared2_import_prefix,shared2_prefix>\")
+check_value (\"TARGET_FILE_PREFIX static custom\" \"$<TARGET_FILE_PREFIX:static2>\" \"static2_prefix\")
+check_value (\"TARGET_LINKER_FILE_PREFIX static linker custom\" \"$<TARGET_LINKER_FILE_PREFIX:static2>\" \"static2_prefix\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_PREFIX-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-check.cmake
new file mode 100644
index 0000000..f159370
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_SUFFIX-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target-check.cmake
new file mode 100644
index 0000000..f159370
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_FILE_SUFFIX-generated.cmake")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target.cmake
new file mode 100644
index 0000000..e1b7654
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-imported-target.cmake
@@ -0,0 +1,49 @@
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_language (C)
+
+set (win_platforms Windows CYGWIN)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_executable (exec1 IMPORTED)
+add_library (shared1 SHARED IMPORTED)
+add_library (static1 STATIC IMPORTED)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_SUFFIX executable default\" \"$<TARGET_FILE_SUFFIX:exec1>\" \"${CMAKE_EXECUTABLE_SUFFIX}\")
+check_value (\"TARGET_FILE_SUFFIX shared default\" \"$<TARGET_FILE_SUFFIX:shared1>\" \"${CMAKE_SHARED_LIBRARY_SUFFIX}\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX shared linker default\" \"$<TARGET_LINKER_FILE_SUFFIX:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,${CMAKE_IMPORT_LIBRARY_SUFFIX},${CMAKE_SHARED_LIBRARY_SUFFIX}>\")
+check_value (\"TARGET_FILE_SUFFIX static default\" \"$<TARGET_FILE_SUFFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_SUFFIX}\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX static linker default\" \"$<TARGET_LINKER_FILE_SUFFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n")
+
+
+add_executable (exec2 IMPORTED)
+set_property (TARGET exec2 PROPERTY SUFFIX exec2_suffix)
+set_property (TARGET exec2 PROPERTY ENABLE_EXPORTS TRUE)
+set_property (TARGET exec2 PROPERTY IMPORT_SUFFIX exec2_import_suffix)
+add_library (shared2 SHARED IMPORTED)
+set_property (TARGET shared2 PROPERTY SUFFIX shared2_suffix)
+set_property (TARGET shared2 PROPERTY IMPORT_SUFFIX shared2_import_suffix)
+add_library (static2 STATIC IMPORTED)
+set_property (TARGET static2 PROPERTY SUFFIX static2_suffix)
+set_property (TARGET static2 PROPERTY IMPORT_SUFFIX static2_import_suffix)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_SUFFIX executable custom\" \"$<TARGET_FILE_SUFFIX:exec2>\" \"exec2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX executable linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,exec2_import_suffix,exec2_suffix>\")
+check_value (\"TARGET_FILE_SUFFIX shared custom\" \"$<TARGET_FILE_SUFFIX:shared2>\" \"shared2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX shared linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,shared2_import_suffix,shared2_suffix>\")
+check_value (\"TARGET_FILE_SUFFIX static custom\" \"$<TARGET_FILE_SUFFIX:static2>\" \"static2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX static linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:static2>\" \"static2_suffix\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_SUFFIX-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-stderr.txt
new file mode 100644
index 0000000..9ea09d1
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_FILE_SUFFIX-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_FILE_SUFFIX:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target.cmake
new file mode 100644
index 0000000..f7089f9
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX-non-valid-target.cmake
@@ -0,0 +1,7 @@
+
+add_custom_target(empty)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_FILE_SUFFIX:empty>]"
+)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX.cmake
new file mode 100644
index 0000000..78afecd
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_FILE_SUFFIX.cmake
@@ -0,0 +1,49 @@
+
+cmake_minimum_required(VERSION 3.14)
+
+enable_language (C)
+
+set (win_platforms Windows CYGWIN)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_executable (exec1 empty.c)
+add_library (shared1 SHARED empty.c)
+add_library (static1 STATIC empty.c)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_SUFFIX executable default\" \"$<TARGET_FILE_SUFFIX:exec1>\" \"${CMAKE_EXECUTABLE_SUFFIX}\")
+check_value (\"TARGET_FILE_SUFFIX shared default\" \"$<TARGET_FILE_SUFFIX:shared1>\" \"${CMAKE_SHARED_LIBRARY_SUFFIX}\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX shared linker default\" \"$<TARGET_LINKER_FILE_SUFFIX:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,${CMAKE_IMPORT_LIBRARY_SUFFIX},${CMAKE_SHARED_LIBRARY_SUFFIX}>\")
+check_value (\"TARGET_FILE_SUFFIX static default\" \"$<TARGET_FILE_SUFFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_SUFFIX}\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX static linker default\" \"$<TARGET_LINKER_FILE_SUFFIX:static1>\" \"${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n")
+
+
+add_executable (exec2 empty.c)
+set_property (TARGET exec2 PROPERTY SUFFIX exec2_suffix)
+set_property (TARGET exec2 PROPERTY ENABLE_EXPORTS TRUE)
+set_property (TARGET exec2 PROPERTY IMPORT_SUFFIX exec2_import_suffix)
+add_library (shared2 SHARED empty.c)
+set_property (TARGET shared2 PROPERTY SUFFIX shared2_suffix)
+set_property (TARGET shared2 PROPERTY IMPORT_SUFFIX shared2_import_suffix)
+add_library (static2 STATIC empty.c)
+set_property (TARGET static2 PROPERTY SUFFIX static2_suffix)
+set_property (TARGET static2 PROPERTY IMPORT_SUFFIX static2_import_suffix)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_FILE_SUFFIX executable custom\" \"$<TARGET_FILE_SUFFIX:exec2>\" \"exec2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX executable linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,exec2_import_suffix,exec2_suffix>\")
+check_value (\"TARGET_FILE_SUFFIX shared custom\" \"$<TARGET_FILE_SUFFIX:shared2>\" \"shared2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX shared linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${win_platforms}>,shared2_import_suffix,shared2_suffix>\")
+check_value (\"TARGET_FILE_SUFFIX static custom\" \"$<TARGET_FILE_SUFFIX:static2>\" \"static2_suffix\")
+check_value (\"TARGET_LINKER_FILE_SUFFIX static linker custom\" \"$<TARGET_LINKER_FILE_SUFFIX:static2>\" \"static2_suffix\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_FILE_SUFFIX-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-stderr.txt
new file mode 100644
index 0000000..7a36cef
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_LINKER_FILE_PREFIX-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_LINKER_FILE_PREFIX:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target.cmake
new file mode 100644
index 0000000..8dad4da
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_PREFIX-non-valid-target.cmake
@@ -0,0 +1,7 @@
+
+add_custom_target(empty)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_LINKER_FILE_PREFIX:empty>]"
+)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-stderr.txt
new file mode 100644
index 0000000..cc5217a
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at TARGET_LINKER_FILE_SUFFIX-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_LINKER_FILE_SUFFIX:empty>
+
+  Target "empty" is not an executable or library\.
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target.cmake
new file mode 100644
index 0000000..82c2f3a
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_FILE_SUFFIX-non-valid-target.cmake
@@ -0,0 +1,7 @@
+
+add_custom_target(empty)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_LINKER_FILE_SUFFIX:empty>]"
+)
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
index 29f6211..0e09469 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target-stderr.txt
@@ -1,4 +1,4 @@
-CMake Error at TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake:6 \(file\):
+CMake Error at TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake:[0-9]+ \(file\):
   Error evaluating generator expression:
 
     \$<TARGET_LINKER_OUTPUT_NAME:empty>
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake
index e1496b4..c439535 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_LINKER_OUTPUT_NAME-non-valid-target.cmake
@@ -1,6 +1,4 @@
 
-enable_language(C)
-
 add_custom_target(empty)
 
 file(GENERATE
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
index e78ec01..9672a99 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target-stderr.txt
@@ -1,4 +1,4 @@
-CMake Error at TARGET_OUTPUT_NAME-non-valid-target.cmake:6 \(file\):
+CMake Error at TARGET_OUTPUT_NAME-non-valid-target.cmake:[0-9]+ \(file\):
   Error evaluating generator expression:
 
     \$<TARGET_OUTPUT_NAME:empty>
diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake
index 2ff733c..5248dfa 100644
--- a/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake
+++ b/Tests/RunCMake/GeneratorExpression/TARGET_OUTPUT_NAME-non-valid-target.cmake
@@ -1,6 +1,4 @@
 
-enable_language(C)
-
 add_custom_target(empty)
 
 file(GENERATE