diff --git a/DataFormats/PortableTestObjects/test/BuildFile.xml b/DataFormats/PortableTestObjects/test/BuildFile.xml
index 4f01bfa83d492..74069189d3e25 100644
--- a/DataFormats/PortableTestObjects/test/BuildFile.xml
+++ b/DataFormats/PortableTestObjects/test/BuildFile.xml
@@ -2,3 +2,8 @@
+
+
+
+
+
diff --git a/DataFormats/PortableTestObjects/test/run_edmTypeInfo.sh b/DataFormats/PortableTestObjects/test/run_edmTypeInfo.sh
new file mode 100755
index 0000000000000..9edb90cf7b6f6
--- /dev/null
+++ b/DataFormats/PortableTestObjects/test/run_edmTypeInfo.sh
@@ -0,0 +1,52 @@
+#! /bin/bash -e
+#
+# Run `edmTypeInfo` and compare its output against the expected results.
+
+function can_run() {
+ local CMD="$1"
+
+ OUTPUT=$(${CMD})
+}
+
+function compare() {
+ local CMD="$1"
+ local EXPECTED="$2"
+ local OUTPUT
+ OUTPUT=$(${CMD})
+
+ if [ "${OUTPUT}" != "${EXPECTED}" ]; then
+ echo "Error: unexpected output"
+ echo "Command:"
+ echo "${CMD}"
+ echo
+ echo "Expected output:"
+ echo "${EXPECTED}"
+ echo
+ echo "Actual output:"
+ echo "${OUTPUT}"
+ exit 1
+ fi
+
+ return 0
+}
+
+
+# Run edmTypeInfo portabletest::TestHostCollection
+CMD="edmTypeInfo portabletest::TestHostCollection"
+EXPECTED='`portabletest::TestHostCollection` resolves to `PortableHostCollection >`
+ with friendly class name `128falseportabletestTestSoALayoutPortableHostCollection`
+ with type info `22PortableHostCollectionIN12portabletest13TestSoALayoutILm128ELb0EEEE`'
+compare "${CMD}" "${EXPECTED}"
+
+# Run edmTypeInfo ALPAKA_ACCELERATOR_NAMESPACE::portabletest::TestDeviceCollection
+CMD="edmTypeInfo ALPAKA_ACCELERATOR_NAMESPACE::portabletest::TestDeviceCollection"
+EXPECTED='`alpaka_serial_sync::portabletest::TestDeviceCollection` does not resolve to any type known to ROOT
+
+`alpaka_cuda_async::portabletest::TestDeviceCollection` resolves to `PortableDeviceCollection,portabletest::TestSoALayout<128,false>,void>`
+ with friendly class name `alpakaDevCudaRt128falseportabletestTestSoALayoutvoidPortableDeviceCollection`
+ with type info `24PortableDeviceCollectionIN6alpaka19DevUniformCudaHipRtINS0_9ApiCudaRtEEEN12portabletest13TestSoALayoutILm128ELb0EEEvE`
+
+`alpaka_rocm_async::portabletest::TestDeviceCollection` resolves to `PortableDeviceCollection,portabletest::TestSoALayout<128,false>,void>`
+ with friendly class name `alpakaDevHipRt128falseportabletestTestSoALayoutvoidPortableDeviceCollection`
+ with type info `24PortableDeviceCollectionIN6alpaka19DevUniformCudaHipRtINS0_8ApiHipRtEEEN12portabletest13TestSoALayoutILm128ELb0EEEvE`'
+compare "${CMD}" "${EXPECTED}"
diff --git a/FWCore/Reflection/scripts/edmTypeInfo b/FWCore/Reflection/scripts/edmTypeInfo
new file mode 100755
index 0000000000000..73740209ec10e
--- /dev/null
+++ b/FWCore/Reflection/scripts/edmTypeInfo
@@ -0,0 +1,52 @@
+#! /usr/bin/env python3
+
+import os
+import sys
+import ROOT
+
+ROOT.gSystem.Load("libFWCoreReflection.so")
+ROOT.gInterpreter.Declare('#include "FWCore/Reflection/interface/TypeWithDict.h"')
+
+def usage(name):
+ print(f"""Usage: edmTypeInfo [options] [types]
+Resolve C++ types to the full C++ type name, friendly class name, and mangled name.
+
+Options:
+ -h, --help display this help and exit
+ -v, --version print version information and exit
+
+
+Resolve each C++ type name in "types" to its actual definition based on its ROOT dictionary, taking into account type aliases and the use of ALPAKA_ACCELERATOR_NAMESPACE.
+For each type print the full c++ name, the friendly class name, and the mangled name.
+""")
+
+
+def version(name):
+ print('edmTypeInfo : %s' % os.environ.get('CMSSW_VERSION'))
+
+def resolve(name):
+ type = ROOT.edm.TypeWithDict.byName(name)
+ if (type):
+ print(f'`{name}` resolves to `{type.name()}`')
+ print(f' with friendly class name `{type.friendlyClassName()}`')
+ print(f' with type info `{type.typeInfo().name()}`')
+ else:
+ print(f'`{name}` does not resolve to any type known to ROOT')
+ print()
+
+
+if '-h' in sys.argv[1:] or '--help' in sys.argv[1:]:
+ usage(sys.argv[0])
+ sys.exit(0)
+
+if '-v' in sys.argv[1:] or '--version' in sys.argv[1:]:
+ version(sys.argv[0])
+ sys.exit(0)
+
+for name in sys.argv[1:]:
+ if 'ALPAKA_ACCELERATOR_NAMESPACE' in name:
+ for ns in 'alpaka_serial_sync', 'alpaka_cuda_async', 'alpaka_rocm_async':
+ ns_name = name.replace('ALPAKA_ACCELERATOR_NAMESPACE', ns)
+ resolve(ns_name)
+ else:
+ resolve(name)
diff --git a/FWCore/Reflection/test/BuildFile.xml b/FWCore/Reflection/test/BuildFile.xml
index c110297ef919f..022d299102f48 100644
--- a/FWCore/Reflection/test/BuildFile.xml
+++ b/FWCore/Reflection/test/BuildFile.xml
@@ -7,3 +7,4 @@
+
diff --git a/FWCore/Reflection/test/run_edmTypeInfo.sh b/FWCore/Reflection/test/run_edmTypeInfo.sh
new file mode 100755
index 0000000000000..807afa9865242
--- /dev/null
+++ b/FWCore/Reflection/test/run_edmTypeInfo.sh
@@ -0,0 +1,47 @@
+#! /bin/bash -e
+#
+# Run `edmTypeInfo` and compare its output against the expected results.
+
+function can_run() {
+ local CMD="$1"
+
+ OUTPUT=$(${CMD})
+}
+
+function compare() {
+ local CMD="$1"
+ local EXPECTED="$2"
+ local OUTPUT
+ OUTPUT=$(${CMD})
+
+ if [ "${OUTPUT}" != "${EXPECTED}" ]; then
+ echo "Error: unexpected output"
+ echo "Command:"
+ echo "${CMD}"
+ echo
+ echo "Expected output:"
+ echo "${EXPECTED}"
+ echo
+ echo "Actual output:"
+ echo "${OUTPUT}"
+ exit 1
+ fi
+
+ return 0
+}
+
+
+# Run edmTypeInfo -h
+CMD="edmTypeInfo -h"
+can_run "${CMD}"
+
+# Run edmTypeInfo -v
+CMD="edmTypeInfo -v"
+can_run "${CMD}"
+
+# Run edmTypeInfo edmtest::reflection::IntObject
+CMD="edmTypeInfo edmtest::reflection::IntObject"
+EXPECTED='`edmtest::reflection::IntObject` resolves to `edmtest::reflection::IntObject`
+ with friendly class name `edmtestreflectionIntObject`
+ with type info `N7edmtest10reflection9IntObjectE`'
+compare "${CMD}" "${EXPECTED}"