Name-Matched shorthand for dict literals and function calls

Name-Matched Shorthand for Dict Literals and Function Calls

The Problem

Dict key typos are silent bugs:

name = parse_name(raw)
email = validate_email(raw)

# "emial" is a valid string key — no error, no warning
response = {"name": name, "emial": email}

No linter, type checker, or test catches this unless something downstream expects "email" and fails. The bug ships to production.

This happens because dict keys are strings — they bypass the compiler entirely. Meanwhile, a typo in a keyword argument does fail:

create_user(nmae=name)  # TypeError: unexpected keyword argument 'nmae'

Both cases share the same root pattern: repeating a variable name as a key or parameter name ("x": x, x=x). This redundancy is everywhere — API responses, template contexts, logging, dataclass construction, config dicts.

The Idea

A soft keyword use that forwards variable names as dict keys or keyword arguments:

# Dict literals
response = {use name email role}
# desugars to: {"name": name, "email": email, "role": role}

# Function calls
create_user(use name email role)
# desugars to: create_user(name=name, email=email, role=role)

One grammar production (use NAME+), two contexts, pure compile-time desugaring. No new runtime behavior.

The dict case turns a string-domain operation into an identifier-domain operation — {use emial} is a NameError because emial isn’t defined. The compiler catches the typo.

Mixing with regular entries

# Dict: shorthand + regular entries
{use name email, "extra_key": 42}

# Call: shorthand + positional args + kwargs
foo(42, use name email, z="extra")

Names are space-separated (not comma-separated) to avoid ambiguity with set literals in dicts and positional arguments in calls.

Relationship to PEP 736

I’m aware PEP 736 (f(x=)) was rejected — the Steering Council found the cost/benefit too low for call sites alone. This idea broadens the scope: the same grammar addition applies to dict literals, where it provides a qualitative safety improvement (catching key typos) beyond just reducing verbosity.

The grammar cost is nearly identical to PEP 736 (one soft keyword, one production). The benefit surface is wider.

Prior discussions

This topic has come up before on this forum:

This proposal differs in using a soft keyword with space-separated names (avoiding set/frozenset ambiguity) and leading with compile-time typo detection as the primary motivation rather than keystroke savings.

Prior art

Nix’s inherit keyword does exactly this for attribute sets:

let x = 1; y = 2;
in { inherit x y; }
# equivalent to { x = x; y = y; }

Questions for the community

  1. Does the dict typo safety angle change the cost/benefit calculus compared to PEP 736?
  2. Does use read well, or is there a better keyword? (Alternatives considered: bind, using, = prefix)
  3. Are there contexts beyond dicts and calls where this would be useful?

I have a full draft PEP if there’s interest, but wanted to gauge temperature first.

Doesn’t a TypedDict help with catching key typos via a type checker? What about typos in variable names (e.g. {use nmae email})?

1 Like

Personally find it less readable than PEP736.

1 Like

Yeah, TypedDict catches key typos. But you have to write the class, annotate things, and run a type checker. That’s worth it for API contracts and configs. For a throwaway dict in a template context or logging call? Most people aren’t writing a TypedDict for that.

use catches it at compile time. {use emial} is a NameError because emial isn’t defined.

They’re not really competing. TypedDict validates structure and types. use just says “this key is this variable.” They could work together.

On {use nmae email}, if nmae isn’t defined, NameError. If it happens to be a different variable, that’s the same bug as x = nmae + 1. use doesn’t make that worse, it just removes the extra typo surface that string keys add.

That’s actually the other way around: PEP 736 is more general, since supporting this for function calls gives it to you for dictionaries via the dict() constructor.

So I suspect that, given that the proposal that covers calls (including dictionaries) wasn’t considered to “have a high enough payoff”, I doubt that a narrower one will. Unless you can somehow come up with a syntax that feels right, makes minimally-invasive changes, and is easy to read, it’s unlikely that this will go anywhere.

I would very much like to see something like this happen (I mean, my name’s on that very PEP you mentioned), but I’m dubious that this proposal will be able to go anywhere.

2 Likes

I think the solution for that is

which makes it much easier to define one-time-use TypedDicts.

2 Likes

PEP 764 is nice, it definitely lowers the bar for TypedDict. But it’s solving a different problem. TypedDict (inline or not) gives you type validation on dict structure.

What I’m after is simpler: reducing the key=value / "key": value repetition when the variable name is the key. That’s not a typing problem, it’s a syntax problem. {use name email} instead of {"name": name, "email": email}. The typo safety is a bonus, not the core pitch.

1 Like

I dunno why, but this sounds more like a C++ idea, they have the keyword ‘using’.

Fair point, dict(use name email) would work through PEP 736’s call syntax. But dict() and {} aren’t interchangeable:

# dict() can't do this
{use name email, "computed_" + key: value, **other}
{use name email, 42: "numeric key"}

Dict literals support non-identifier keys, computed keys, and unpacking that dict() can’t. So “just use dict()” doesn’t fully cover the dict case.

Also, this proposal isn’t a subset of PEP 736, it’s a superset. PEP 736 covered calls only. This covers calls and dict literals with the same grammar production. The cost is roughly the same (one soft keyword, one production), the benefit surface is wider. Whether that’s enough to change the cost/benefit math, that’s what I’m here to find out.

The inspiration is actually Nix, not C++. Nix’s inherit keyword does exactly this for attribute sets:

let x = 1; y = 2;
in { inherit x y; }
# equivalent to { x = x; y = y; }

C++'s using is for namespace imports, which is a different problem. The keyword choice here (use vs using vs bind) is an open question in the proposal, but the mechanism itself is closer to Nix than to anything in C++.

IMO, PEP 736 is strictly better than this proposal. Being able to use {use name email} instead of dict(name=, email=) is no significant advantage, and I doubt that there are any realistic use cases for the two examples you gave of “dict() can’t do this”.

The syntax you propose is (again, IMO) far uglier than the PEP 726 syntax, as well.

I wasn’t a huge fan of PEP 726, but even so, I’d take it over this proposal every time. Sorry if that’s not the reaction you were hoping for.

6 Likes

Appreciate the honest take, aligned with my view or not, that’s exactly why I posted here. No point investing time into something the community doesn’t want. Fair point on syntax aesthetics, the keyword choice is an open question and I’m genuinely curious what the community thinks reads better.

3 Likes

That’s the trouble - if we knew of a slam-dunk perfect syntax, we’d already have proposed it :slight_smile:

1 Like
emial = validate_email(raw)  # typo

response = {use emial}
# you type `e` and accept autocomplete
# expands to
response = {"emial": emial}

create_user(**response)  # runtime explosion

I don’t think your proposal solves the typo issue. It’s really easy to typo the first occurrence and then keep using the misspelled name without noticing because autocomplete keeps suggesting it (I’m definitely guilty of this). You often don’t find out until a type checker catches it, or worse, you hit a runtime error later.

I think this is something the type system could solve with something like KwargsOf[F: Callable], e.g.

emial = validate_email(raw)  # typo

response: KwargsOf[create_user] = {
    "emial": emial,  # type error
}

create_user(**response)
1 Like

Stepping back, I think the typo safety angle was premature. Trying to squeeze a dynamic language into static-typed guarantees isn’t the right framing, and I don’t want the proposal to hang on that point.

The previous PEP was rejected for low value relative to complexity, and this one is looking like it falls to the same issue. I do see value here, and three similar threads in three years suggests the community sees it too. But recurring interest isn’t the same as a strong enough case to overturn the SC’s position.

I don’t want to open a PEP that doesn’t solve the main point of the previous rejection. Unless someone sees an angle that materially changes the cost/benefit, I think this is where this discussion lands.

Thanks everyone for the honest feedback. This is exactly what I came here for.

6 Likes

Related :

You can intentionally make field access fail fast instead of silently accepting typos:

name = "John"
email = "john@example.com"

fields = ['name', 'email']
data = globals()  # or locals(), a dict_object, etc.
response = {k: data[k] for k in fields}

A test suite should definitely catch these bugs; otherwise, it is not being properly tested.

This is part of the test too.

1 Like