Skip to content

Make invalid types passed to cv_typeid_v<T> a compile-time error#938

Merged
pcchen merged 5 commits into
Cytnx-dev:masterfrom
ianmccul:variant-index-hard-error
Jul 1, 2026
Merged

Make invalid types passed to cv_typeid_v<T> a compile-time error#938
pcchen merged 5 commits into
Cytnx-dev:masterfrom
ianmccul:variant-index-hard-error

Conversation

@ianmccul

Copy link
Copy Markdown
Collaborator

Short PR to make cy_typeid_v<T> a compile error if T is an invalid type.

cy_typeid_v<T> is a compile-time integer that returns the dtype corresponding to the type T. Due to an oversight, if T is not one of the 12-supported types with dtype = 0 .. 11, cy_typeid_v<T> would get the value 12. If this is used as an index into an array, it will likely result in reading one-past-the-end.

Root cause:

This was an oversight when the cy_typeid_v<T> mechanism was added. There is a variant_index<T, Type_list> mechanism added which returns the index of a type in a std::variant. If the type does not exist in the supplied variant, the previous behavior was to return the size of the variant (i.e. one-past-the-end of a valid index). This is now a compile-time static_assert: "variant_index<T, Variant>: T is not in Variant".

In keeping with common C++ conventions, cy_typeid_v<T> expects an exact match for the type T. So cy_typeid_v<const double> is a compile-time error, as is other cv or reference-qualified types. This is a good fix because previous behavior would result in cy_typeid_v<const double> == 12.

If T is not expected to be an exact type, then normalize the type with:

cy_typeid_v<std::remove_cvref_t<T>>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces variant_contains_v and adds a compile-time check for variant_index when a type is not found in a variant, along with corresponding unit tests. The reviewer suggested simplifying the implementation of variant_contains_v by using a partially specialized variable template instead of a helper struct to reduce boilerplate.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread include/Type.hpp Outdated
Comment on lines +112 to +120
template <typename T, typename Variant>
struct variant_index;
struct variant_contains;

template <typename T, typename... Types>
struct variant_index<T, std::variant<Types...>> {
static constexpr std::size_t value = std::variant_size_v<std::variant<Types...>>;
struct variant_contains<T, std::variant<Types...>>
: std::bool_constant<(std::is_same_v<T, Types> || ...)> {};

template <typename T, typename Variant>
inline constexpr bool variant_contains_v = variant_contains<T, Variant>::value;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

We can simplify the implementation of variant_contains_v by defining it directly as a partially specialized variable template. This eliminates the need for the helper struct variant_contains entirely, reducing boilerplate and template instantiation overhead.

Suggested change
template <typename T, typename Variant>
struct variant_index;
struct variant_contains;
template <typename T, typename... Types>
struct variant_index<T, std::variant<Types...>> {
static constexpr std::size_t value = std::variant_size_v<std::variant<Types...>>;
struct variant_contains<T, std::variant<Types...>>
: std::bool_constant<(std::is_same_v<T, Types> || ...)> {};
template <typename T, typename Variant>
inline constexpr bool variant_contains_v = variant_contains<T, Variant>::value;
template <typename T, typename Variant>
inline constexpr bool variant_contains_v = false;
template <typename T, typename... Types>
inline constexpr bool variant_contains_v<T, std::variant<Types...>> =
(std::is_same_v<T, Types> || ...);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is a nice improvement.

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 30.87%. Comparing base (4177e4f) to head (6e1c1ab).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #938   +/-   ##
=======================================
  Coverage   30.87%   30.87%           
=======================================
  Files         229      229           
  Lines       34757    34757           
  Branches    14409    14409           
=======================================
  Hits        10730    10730           
  Misses      16720    16720           
  Partials     7307     7307           
Flag Coverage Δ
cpp 30.38% <ø> (ø)
python 59.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 32.19% <ø> (ø)
Python bindings 17.28% <ø> (ø)
Python package 59.41% <ø> (ø)
Files with missing lines Coverage Δ
include/Type.hpp 50.00% <ø> (ø)
src/linalg/Kron.cpp 37.14% <ø> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4177e4f...6e1c1ab. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pcchen pcchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the change. This is a clean, well-scoped, correct fix. Replacing the silent "return one-past-the-end" fallback with a static_assert is the right call, and the template metaprogramming is sound.

Correctness — verified, no issues:

  • The recursion terminates correctly: a missing type strips the variant down to std::variant<>, which hits the static_assert. always_false_v<T> is the correct dependent-false idiom (won't fire prematurely), and no partial-specialization ambiguity is introduced.
  • Audited all callers of variant_index_v / cy_typeid_v / cy_typeid_gpu_v / cy_typeid. Every instantiation uses a type guaranteed to be in the relevant list, so nothing that previously compiled now breaks:
    • Tensor.hpp:522 (ptr_as) → host Type_list; Tensor.hpp:545 (gpu_ptr_as) correctly uses the separate cy_typeid_gpu_v over Type_list_gpu.
    • Add.cpp:20 (AddScalarTensorImpl<TL>) is protected — the generic Add<T> has static_assert(!is_same_v<T, Scalar>) and Add<Scalar> is specialized separately, so cy_typeid_v<Scalar> is never instantiated.
    • GPU dispatch files consistently use cy_typeid_gpu_v<TO> over GPU-list types.

Minor, non-blocking:

  • tests/Type_test.cpp — the PR's stated subject cy_typeid_v isn't directly tested; the tests exercise variant_index_v / variant_contains_v. A static_assert(cy_typeid_v<cytnx_double> == 3) would lock the user-facing API. (The negative/compile-error case is necessarily untestable in gtest — fine.)
  • The runtime TEST(TypeTest, VariantContainsRecognizesSupportedTypes) duplicates the three static_assert(variant_contains_v<...>) lines directly above it, so its body adds no coverage beyond serving as a gtest anchor.

LGTM — the two notes are optional test polish, not blockers.


Posted by Claude Code on behalf of @pektiong

@IvanaGyro

Copy link
Copy Markdown
Member

Format check is not passed.

@ianmccul

Copy link
Copy Markdown
Collaborator Author

Format check is not passed.

I think that is a difference between clang-format-14 and newer clang-format.

@IvanaGyro

Copy link
Copy Markdown
Member

Format check is not passed.

I think that is a difference between clang-format-14 and newer clang-format.

Yes, different clang-format has different default formats.

@pcchen

pcchen commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

@IvanaGyro are we using clang-format-14 or newer clang-format?

@IvanaGyro

Copy link
Copy Markdown
Member

@IvanaGyro are we using clang-format-14 or newer clang-format?

We use version 14. Here is the pre-commit setting:

- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v14.0.6 # Use the sha / tag you want to point at
hooks:
- id: clang-format
types_or: [c++, c, cuda]

The Formatting Check CI runs clang-format 14 (pinned in
.pre-commit-config.yaml as v14.0.6). Reflow the variant_contains_v
fold expression to match; no functional change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 868d1165e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread include/Type.hpp

template <typename T>
struct variant_index<T, std::variant<>> {
static_assert(always_false_v<T>, "variant_index<T, Variant>: T is not in Variant");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix CUDA complex dispatch before asserting

In CUDA builds (UNI_GPU), the existing GPU Kron dispatch instantiates out.ptr_as<out_type>() in src/linalg/Kron.cpp:90 for every Type_list_gpu alternative; when either input is complex, out_type becomes cuDoubleComplex or cuComplex, which are intentionally not in the CPU Type_list. This new base-case assertion therefore fires while compiling Type_class::cy_typeid_v<out_type> inside ptr_as, so the CUDA build breaks for complex Kron unless that path is updated to use the GPU dtype mapping before making missing indices hard errors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 6e1c1ab1. src/linalg/Kron.cpp:90 (the GPU dispatch) now calls out.gpu_ptr_as<out_type>() instead of out.ptr_as<out_type>(), so the lookup for a Type_list_gpu out_type (cuDoubleComplex/cuComplex) goes through cy_typeid_gpu_v/Type_list_gpu instead of the CPU Type_list — avoiding the new hard variant_index error and keeping the UNI_GPU complex-Kron build compiling. The CPU call at line 75 stays ptr_as (its out_type is a CPU Type_list member).

Caveat: CI has no CUDA build, so this is correct-by-inspection (and by Type_list/Type_list_gpu index alignment) rather than compile-verified here — a real UNI_GPU build is the confirming check.


Posted by Claude Code on behalf of @pcchen

pcchen and others added 2 commits July 1, 2026 10:41
# Conflicts:
#	tests/CMakeLists.txt
The GPU Kron path derives out_type from Type_promote_from_gpu_pointer_t
(a Type_list_gpu member, e.g. cuDoubleComplex/cuComplex for complex
inputs), but called out.ptr_as<out_type>(), which maps through the CPU
Type_list via cy_typeid_v. With variant_index now a hard compile-time
error for types not in the list (this PR), that instantiation breaks the
UNI_GPU build for complex Kron. Use gpu_ptr_as<out_type>() so the lookup
goes through Type_list_gpu, as the ptr_as/gpu_ptr_as contract intends.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pcchen

pcchen commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Pushed two commits to variant-index-hard-error:

1. Merged current master (3e16377e). This clears the failing Formatting Check — the violation it reported was in src/backend/linalg_internal_interface.cpp (stale-master code, not anything this PR touched); it was already fixed on master by #960, which this merge brings in. Also resolves the merge conflict in tests/CMakeLists.txt by keeping both Type_test.cpp and Tensor_strides_test.cpp.

2. Fixed the P1 CUDA-dispatch issue (6e1c1ab1) flagged by @chatgpt-codex-connector. src/linalg/Kron.cpp:90 (GPU path) now uses out.gpu_ptr_as<out_type>() instead of out.ptr_as<out_type>(). That path's out_type comes from Type_list_gpu (e.g. cuDoubleComplex/cuComplex for complex inputs); ptr_as maps through the CPU Type_list via cy_typeid_v, which — now that this PR makes variant_index a hard compile-time error for unlisted types — would break the UNI_GPU build for complex Kron. gpu_ptr_as routes through Type_list_gpu, per the ptr_as/gpu_ptr_as contract. The CPU call at line 75 is left as ptr_as (its out_type is a CPU Type_list member, which is correct).

Note: CI has no CUDA build, so the Kron.cpp fix is correct-by-inspection (and by the Type_list/Type_list_gpu index alignment) but not compile-verified here — a real UNI_GPU build is the confirming check. The gemini variant_contains_v simplification is already satisfied by the current code and can be marked resolved.


Posted by Claude Code on behalf of @pcchen

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Added compile-time coverage for the user-facing cy_typeid_v API by importing it in tests/Type_test.cpp and asserting representative dtype mappings for cytnx_double and cytnx_bool. tests/Type_test.cppL10-L23
  • Committed the change with commit 097fb856 and created a PR titled “Add cy_typeid_v mapping coverage.”

Testing

  • ⚠️ clang-format-14 -i tests/Type_test.cpp — unavailable in this environment (clang-format-14: command not found).
  • clang-format --version && clang-format -i tests/Type_test.cpp — ran with available clang-format version 20.1.8.
  • git diff --check

View task →

@pcchen
pcchen merged commit 6190bd1 into Cytnx-dev:master Jul 1, 2026
19 checks passed
@ianmccul

ianmccul commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Interesting. Kron on GPU would fail at runtime for the last 2 years - almost a perfect demonstration of ptr_as<T>() correctly failing when T does not match dtype.

Using the CUDA ABI types is consistent with the old Kron kernel back from 128071a but its not really needed. Better to deprecate gpu_ptr_as<T> and stop using the CUDA cuComplex except as implementation detail when calling some CUDA functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants