Skip to content

fix(core): report unhashable string-enum arguments as ValueError, not TypeError - #2540

Open
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:core-validators-unhashable
Open

fix(core): report unhashable string-enum arguments as ValueError, not TypeError#2540
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:core-validators-unhashable

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

cuda/core/_utils/validators.py::check_str_enum exists to turn a bad option value into a ValueError that names the accepted values. It tests membership against a set:

if value not in {m.value for m in enum_class}:
    valid = sorted(m.value for m in enum_class)
    ...
    raise ValueError(f"{value!r} is not a valid {enum_class.__name__}. Must be {format_or_list(valid)}")

x in <set> hashes x, so an unhashable argument dies inside the membership test, before the ValueError is ever built:

>>> WorkqueueResourceOptions(sharing_scope=["green_ctx_balanced"])
TypeError: unhashable type: 'list'

>>> WorkqueueResourceOptions(sharing_scope="bogus")
ValueError: 'bogus' is not a valid WorkqueueSharingScopeType. Must be 'device' or 'green_ctx_balanced'

The outcome depends on the type of the argument rather than on its validity. Measured against check_str_enum(value, SourceCodeType) on main:

argument result on main
"fortran" ValueError (intended)
5, None, object() ValueError (intended)
{"c++"} (a set) ValueError — CPython special-cases set.__contains__ to retry an unhashable argument as a frozenset
["c++"] TypeError: unhashable type: 'list'
{"code_type": "c++"} TypeError: unhashable type: 'dict'
bytearray(b"c++") TypeError: unhashable type: 'bytearray'

A helper whose entire job is to raise ValueError leaks a TypeError from its own implementation detail, and the message says nothing about which values are accepted.

WorkqueueResourceOptions.__post_init__ is a plain dataclass hook — the failure happens at construction, with no device or context involved.

Fix

Test membership against a tuple. Tuple membership compares with == and never hashes, so an unhashable value simply compares unequal and falls into the existing error path.

  • Every currently-accepted value stays accepted, including enum members themselves (SourceCodeType.CXX) and the allow_none=True path.
  • Every currently-rejected value keeps its exact message.
  • Only the three TypeError rows above change, and they change to the ValueError the function documents.

The enums involved have at most a handful of members, so tuple membership is not a meaningful cost.

Where this is reachable

check_str_enum validates user input from:

  • WorkqueueResourceOptions.__post_init__ (_device_resources.pyx:152)
  • ManagedMemoryResourceOptions preferred-location handling (_memory/_managed_memory_resource.pyx:178)
  • make_program_cache_key(code_type=...) (utils/_program_cache/_keys.py:756; the preceding code_type.lower() if isinstance(code_type, str) else code_type passes non-str values straight through)
  • graph alloc-node memory types (graph/_graph_node.pyx:851)

Tests

validators.py had no tests at all — grep -rn "check_str_enum" cuda_core/tests/ returns nothing today. This adds cuda_core/tests/test_utils_validators.py covering format_or_list (0/1/2/3 values and the None-first shape used by allow_none), check_str_enum's accept path (raw values and enum members), the allow_none behavior, the invalid-value path parametrized over hashable and unhashable arguments, and the public WorkqueueResourceOptions entry point. None of it needs a GPU.

Verification I could and could not do

  • Loaded validators.py standalone (it has no imports) and ran the full parametrization against both the upstream/main implementation and the fixed one. On main the list, dict, and bytearray cases raise TypeError and fail; with the fix all cases raise the expected ValueError. Every accept case and every format_or_list output is byte-identical before and after.
  • ruff check / ruff format --check clean on both changed Python files; toolshed/check_spdx.py clean on the new file; python -m py_compile clean.
  • Not run: pytest cuda_core/tests/test_utils_validators.py as written. I have no environment where cuda.core is importable (no CUDA driver, no built extension modules), so the file's imports could not be exercised locally. The two names it pulls from cuda.coreWorkqueueResourceOptions and cuda.core.typing.SourceCodeType — follow existing usage in test_green_context.py and test_program_cache.py. Please treat CI as the first real run.

`check_str_enum` exists to turn a bad option value into a ValueError that
names the accepted values. It tests membership against a set:

    if value not in {m.value for m in enum_class}:

`x in <set>` hashes `x`, so an unhashable argument dies inside the check
before the ValueError is ever constructed:

    >>> WorkqueueResourceOptions(sharing_scope=["green_ctx_balanced"])
    TypeError: unhashable type: 'list'

    >>> WorkqueueResourceOptions(sharing_scope="bogus")
    ValueError: 'bogus' is not a valid WorkqueueSharingScopeType.
                Must be 'device' or 'green_ctx_balanced'

The result is inconsistent by argument type rather than by validity: `5`,
`None`, `object()`, and even a `set` all get the intended ValueError (CPython
special-cases `set.__contains__` to retry unhashable arguments as a
frozenset), while a list, dict, or bytearray gets a TypeError from a helper
whose entire job is to raise ValueError.

Test membership against a tuple instead. Tuple membership compares with
`==` and never hashes, so unhashable values simply compare unequal and fall
into the existing error path. Every currently-accepted value keeps being
accepted, and every currently-rejected value keeps its exact message.

The affected validator reaches user input from
`WorkqueueResourceOptions.__post_init__` (_device_resources.pyx),
`ManagedMemoryResourceOptions` handling (_managed_memory_resource.pyx),
`make_program_cache_key(code_type=...)` (utils/_program_cache/_keys.py,
which passes non-str values straight through), and graph alloc-node memory
types.

`validators.py` had no tests; this adds the first ones for both
`check_str_enum` and `format_or_list`.
@copy-pr-bot

copy-pr-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the cuda.core Everything related to the cuda.core module label Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.core Everything related to the cuda.core module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant