Skip to content

compiler: Optimize map comprehensions to use maps:from_keys/2#10646

Merged
bjorng merged 2 commits intoerlang:masterfrom
michalmuskala:map-comprehensions-keys
Feb 7, 2026
Merged

compiler: Optimize map comprehensions to use maps:from_keys/2#10646
bjorng merged 2 commits intoerlang:masterfrom
michalmuskala:map-comprehensions-keys

Conversation

@michalmuskala
Copy link
Contributor

@michalmuskala michalmuskala commented Feb 5, 2026

Map comprehensions with constant values that don't depend on generator
variables are now compiled to use maps:from_keys/2 instead of
maps:from_list/1. This avoids creating intermediate {Key, Value} tuples.

Before: #{K => Value || K <- List} -> maps:from_list([{K, Value} || K <- List])
After: #{K => Value || K <- List} -> maps:from_keys([K || K <- List], Value)

The optimization applies when the value expression:

  • Is safe (cannot fail at runtime)
  • Has no pre-expressions (is a simple value)
  • Does not reference any generator-bound variables
  • For multi-valued comprehensions all values are the same

Examples that are optimized:

  • #{K => 42 || K <- List} % literal
  • #{K => X || K <- List} % outer variable
  • #{K => {X, Y} || K <- List} % tuple of outer vars
  • #{K => X, {x,K} => X || K <- List} % all values the same

Examples that are NOT optimized:

  • #{K => V || {K, V} <- List} % value from generator
  • #{K => K * 2 || K <- List} % value depends on key
  • #{K => f(X) || K <- List} % function call (not safe)
  • #{K => a, {x,K} => b || K <- List} % different values

@bjorng
Copy link
Contributor

bjorng commented Feb 5, 2026

Please rebase on the latest master.

@michalmuskala michalmuskala force-pushed the map-comprehensions-keys branch from e1b1ee1 to 999b155 Compare February 5, 2026 15:25
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

CT Test Results

    2 files    335 suites   9m 6s ⏱️
  865 tests   861 ✅ 4 💤 0 ❌
5 701 runs  5 697 ✅ 4 💤 0 ❌

Results for commit 7a16eba.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@michalmuskala
Copy link
Contributor Author

Sorry about that, should be correctly rebased on master now

@michalmuskala michalmuskala force-pushed the map-comprehensions-keys branch 4 times, most recently from e6b5dd1 to f0ea609 Compare February 5, 2026 15:57
@bjorng
Copy link
Contributor

bjorng commented Feb 5, 2026

Multiple test suite modules fail to compile (try_catch_SUITE, bs_bincomp_SUITE, and so on). Also, Elixir.Base fails to compile.

@michalmuskala michalmuskala force-pushed the map-comprehensions-keys branch 2 times, most recently from 22e36c9 to a1b733a Compare February 5, 2026 17:30
@michalmuskala
Copy link
Contributor Author

This should be now resolved, I successfully run the whole compile test suite

@bjorng bjorng self-assigned this Feb 6, 2026
@bjorng bjorng added the team:VM Assigned to OTP team VM label Feb 6, 2026
Map comprehensions with constant values that don't depend on generator
variables are now compiled to use maps:from_keys/2 instead of
maps:from_list/1. This avoids creating intermediate {Key, Value} tuples.

Before: #{K => Value || K <- List} -> maps:from_list([{K, Value} || K <-
List])
After:  #{K => Value || K <- List} -> maps:from_keys([K || K <- List],
Value)

The optimization applies when the value expression:
- Is safe (cannot fail at runtime)
- Has no pre-expressions (is a simple value)
- Does not reference any generator-bound variables
- For multi-valued comprehensions all values are the same

Examples that are optimized:
- #{K => 42 || K <- List}            % literal
- #{K => X || K <- List}             % outer variable
- #{K => {X, Y} || K <- List}        % tuple of outer vars
- #{K => X, {x,K} => X || K <- List} % all values the same

Examples that are NOT optimized:
- #{K => V || {K, V} <- List}        % value from generator
- #{K => K * 2 || K <- List}         % value depends on key
- #{K => f(X) || K <- List}          % function call (not safe)
- #{K => a, {x,K} => b || K <- List} % different values
@bjorng bjorng force-pushed the map-comprehensions-keys branch from a1b733a to 7a16eba Compare February 6, 2026 11:52
@bjorng
Copy link
Contributor

bjorng commented Feb 6, 2026

I've force-pushed a rebased and slightly updated version of this branch. Here is what I did:

  1. I added a few more tests to mc_SUITE to ensure that all of v3_core:is_safe_same/2 is covered.
  2. Removed the clause that handled nomatch in v3_core:add_pre_bound_vars/2. As far as I understand, that clause can never be executed because matching in a filter is an error in OTP 29. If the new compr_assign feature is enabled, a match will handled as a generator. I also added a simple smoke-test of assignment for a map comprehension in lc_SUITE.
  3. Two test cases in beam_debug_info_SUITE failed for me, so I've added a separate commit to properly handle multi comprehensions.

I've added this PR along with an updated primary bootstrap to our daily builds.

@michalmuskala
Copy link
Contributor Author

Amazing, thank you for fixing this up

@bjorng bjorng merged commit 33ae628 into erlang:master Feb 7, 2026
27 checks passed
@bjorng
Copy link
Contributor

bjorng commented Feb 7, 2026

Thanks for your pull request.

@michalmuskala michalmuskala deleted the map-comprehensions-keys branch February 7, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team:VM Assigned to OTP team VM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants