File 2222-compiler-Optimize-map-comprehensions-to-use-maps-fro.patch of Package erlang

From 7a16eba992e9f0d3fe2abb621db3a85273d64c10 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Muska=C5=82a?= <micmus@whatsapp.com>
Date: Thu, 5 Feb 2026 03:50:21 -0800
Subject: [PATCH 2/2] compiler: Optimize map comprehensions to use
 maps:from_keys/2

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
---
 lib/compiler/src/v3_core.erl   | 182 ++++++++++++++++++++++++++-------
 lib/compiler/test/lc_SUITE.erl |   9 ++
 lib/compiler/test/mc_SUITE.erl |  59 ++++++++++-
 3 files changed, 210 insertions(+), 40 deletions(-)

diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index f4d9e63bf9..9eab6bfc47 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -716,7 +716,8 @@ expr({cons,L,H0,T0}, St0) ->
     {annotate_cons(A, H1, T1, St1),Eps,St1};
 expr({lc,L,E,Qs0}, St0) ->
     {Qs1,St1} = preprocess_quals(L, Qs0, St0),
-    lc_tq(L, wrap_list(E), Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1);
+    {Lc, Pre, _OptInfo, St2} = lc_tq(L, {lc, wrap_list(E)}, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1),
+    {Lc, Pre, St2};
 expr({bc,L,E,Qs}, St) ->
     bc_tq(L, E, Qs, St);
 expr({mc,L,E,Qs0}, St0) ->
@@ -986,7 +987,7 @@ expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) ->
     %% number variables in the environment for letrec.
     {Mc,Mps,St1} = safe(More, St0),
     {Qs,St2} = preprocess_quals(Llc, Qs0, St1),
-    {Y,Yps,St} = lc_tq(Llc, wrap_list(E), Qs, Mc, St2),
+    {Y,Yps,_OptInfo,St} = lc_tq(Llc, {lc, wrap_list(E)}, Qs, Mc, St2),
     {Y,Mps++Yps,St};
 expr({op,_,'andalso',_,_}=E0, St0) ->
     {op,L,'andalso',E1,E2} = right_assoc(E0, 'andalso'),
@@ -1732,28 +1733,83 @@ fun_tq(Cs0, L, St0, NameInfo) ->
 wrap_list(List) when is_list(List) -> List;
 wrap_list(Other) -> [Other].
 
-%% lc_tq(Line, Exprs, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
+%% lc_tq(Line, Ctx, [Qualifier], Mc, State) -> {LetRec,[PreExp],OptInfo,State}.
+%%  Ctx = {lc, Es} (list comprehension)
+%%      | {mc, Pairs, BoundVars} (map comprehension context)
+%%  OptInfo = none | {from_keys, ValueExpr} | from_list
 %%  This TQ from Simon PJ pp 127-138.
 
-lc_tq(Line, Es, [#igen{}|_T] = Qs, Mc, St) ->
-    lc_tq1(Line, Es, Qs, Mc, St);
-lc_tq(Line, Es, [#izip{}=Zip|Qs], Mc, St) ->
-    zip_tq(Line, Es, Zip, Mc, St, Qs);
-lc_tq(Line, Es, [#ifilter{}=Filter|Qs], Mc, St) ->
-    filter_tq(Line, Es, Filter, Mc, St, Qs, fun lc_tq/5);
-lc_tq(Line, Es0, [], Mc0, St0) ->
-    {Hs1,Hps,St1} = safe_list(Es0, St0),
-    {T1,Tps,St} = force_safe(Mc0, St1),
-    Anno = lineno_anno(erl_anno:set_generated(true, Line), St),
-    E = ann_c_cons_all(Anno, Hs1, T1),
-    {E,Hps ++ Tps,St}.
+lc_tq(Line, Ctx, [#igen{}|_T] = Qs, Mc, St) ->
+    lc_tq1(Line, Ctx, Qs, Mc, St);
+lc_tq(Line, Ctx, [#izip{}=Zip|Qs], Mc, St) ->
+    zip_tq(Line, Ctx, Zip, Mc, St, Qs);
+lc_tq(Line, Ctx, [#ifilter{}=Filter|Qs], Mc, St) ->
+    filter_tq(Line, Ctx, Filter, Mc, St, Qs, fun lc_tq/5);
+lc_tq(Line, Ctx, [], Mc0, St0) ->
+    {Values,Pre,Opt,St} = lc_tq2(Ctx, St0),
+    {T1,Tps,St1} = force_safe(Mc0, St),
+    Anno = lineno_anno(erl_anno:set_generated(true, Line), St1),
+    E = ann_c_cons_all(Anno, Values, T1),
+    {E, Pre ++ Tps, Opt, St1}.
+
+lc_tq2({mc,Pairs,BoundVars}, St) ->
+    mc_pairs(Pairs, BoundVars, [], [], first, St);
+lc_tq2({lc,Es}, St0) ->
+    {Values,Pre,St1} = safe_list(Es, St0),
+    {Values,Pre,none,St1}.
+
+%% mc_pairs(Pairs, BoundVars, Keys, Pre, PrevVal, State) ->
+%%     {Keys,Pre,OptInfo,State}.
+%%  Verifies if it's safe to use maps:from_keys in the comprehension.
+%%  The conditions are:
+%%   1. All values are exactly the same, if comprehension has multiple values
+%%   2. Value is safe
+%%   3. Value is simple (no pre-expressions generated)
+%%   4. Value doesn't use any variables bound in generators
+mc_pairs([{map_field_assoc,Lf,Key,Val}|Pairs], BoundVars, Keys, Pre, PrevVal, St0) ->
+    {ValExpr,ValPre,St1} = expr(Val, St0),
+    {KeyExpr,KeyPre,St2} = safe(Key, St1),
+    case is_safe(ValExpr) andalso ValPre =:= [] andalso
+         not uses_bound_vars(ValExpr, BoundVars) andalso
+         (PrevVal =:= first orelse is_safe_same(ValExpr, PrevVal)) of
+        true ->
+            mc_pairs(Pairs, BoundVars, [KeyExpr|Keys], KeyPre ++ Pre, ValExpr, St2);
+        false ->
+            {ValExpr1,ValPre1,St3} = force_safe(ValExpr, St2),
+            Anno = lineno_anno(Lf, St3),
+            Tuple = ann_c_tuple(Anno, [KeyExpr,ValExpr1]),
+            Tuples = case PrevVal of
+                         first -> [Tuple];
+                         _ -> [Tuple|[ann_c_tuple(Anno, [K,PrevVal]) || K <- Keys]]
+                     end,
+            mc_tuples(Pairs, Tuples, ValPre ++ ValPre1 ++ KeyPre ++ Pre, St3)
+    end;
+mc_pairs([], _BoundVars, Keys, Pre, PrevVal, St) ->
+    {lists:reverse(Keys),Pre,{from_keys,PrevVal},St}.
+
+%% mc_tuples(Pairs, Tuples, Pre, State) -> {Tuples,Pre,from_list,State}.
+%%  Build tuples for the from_list path.
+mc_tuples([{map_field_assoc,Lf,Key,Val}|Pairs], Tuples, Pre, St0) ->
+    {Tuple,Pre1,St1} = safe({tuple,Lf,[Key,Val]}, St0),
+    mc_tuples(Pairs, [Tuple|Tuples], Pre ++ Pre1, St1);
+mc_tuples([], Tuples, Pre, St) ->
+    {lists:reverse(Tuples),Pre,from_list,St}.
+
+is_safe_same(#c_cons{hd=Hd1,tl=Tl1}, #c_cons{hd=Hd2,tl=Tl2}) ->
+    is_safe_same(Hd1, Hd2) andalso is_safe_same(Tl1, Tl2);
+is_safe_same(#c_tuple{es=Es1}, #c_tuple{es=Es2}) ->
+    length(Es1) =:= length(Es2) andalso
+        all(fun({E1,E2}) -> is_safe_same(E1, E2) end, lists:zip(Es1, Es2));
+is_safe_same(#c_var{name=N1}, #c_var{name=N2}) -> N1 =:= N2;
+is_safe_same(#c_literal{val=V1}, #c_literal{val=V2}) -> V1 =:= V2;
+is_safe_same(_, _) -> false.
 
 ann_c_cons_all(Anno, [H | Hs], T) ->
     ann_c_cons(Anno, H, ann_c_cons_all(Anno, Hs, T));
 ann_c_cons_all(_Anno, [], T) ->
     T.
 
-lc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
+lc_tq1(Line, Ctx, [#igen{anno=#a{anno=GA}=GAnno,
 		      acc_pat=AccPat,acc_guard=AccGuard,
                       nomatch_pat=NomatchPat,
                       nomatch_mode=NomatchMode,
@@ -1778,7 +1834,8 @@ lc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
     NomatchClause = make_clause([nomatch_clause,compiler_generated|LA],
                                 NomatchPat, [], [], [Nc]),
     TailClause = make_clause(LA, TailPat, [], [], [Mc]),
-    {Lc,Lps,St3} = lc_tq(Line, E, Qs, Sc, St2),
+    Ctx1 = add_bound_vars(Ctx, AccPat),
+    {Lc,Lps,OptInfo,St3} = lc_tq(Line, Ctx1, Qs, Sc, St2),
     AccClause = make_clause(LA, AccPat, [], AccGuard, Lps ++ [Lc]),
     AccClauseNoGuards = if
                             AccGuard =:= [] ->
@@ -1803,10 +1860,10 @@ lc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
     Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
     {#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
               body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
-     [],St3}.
+     [],OptInfo,St3}.
 
-%% zip_tq(Line, Exp, [Qualifier], Mc, State, TqFun) -> {LetRec,[PreExp],State}.
-zip_tq(Line, E, #izip{anno=#a{anno=GA}=GAnno,
+%% zip_tq(Line, Ctx, Zip, Mc, State, Qs) -> {LetRec,[PreExp],OptInfo,State}.
+zip_tq(Line, Ctx, #izip{anno=#a{anno=GA}=GAnno,
                       acc_pats=AccPats,acc_guard=AccGuard,
                       nomatch_total=NomatchTotal,
                       skip_pats=SkipPats,
@@ -1828,7 +1885,8 @@ zip_tq(Line, E, #izip{anno=#a{anno=GA}=GAnno,
     %% Generate the clauses for the letrec. First, the accumulating
     %% clause.
     Sc = #iapply{anno=GAnno,op=F,args=TailVars},
-    {Lc,Lps,St4} = lc_tq(Line, E, Qs, Sc, St3),
+    Ctx1 = lists:foldl(fun(Pat, C) -> add_bound_vars(C, Pat) end, Ctx, AccPats),
+    {Lc,Lps,OptInfo,St4} = lc_tq(Line, Ctx1, Qs, Sc, St3),
     AccClause = make_clause(LA, AccPats, AccGuard, Lps++[Lc]),
 
     %% Generate the skip clause unless all generators are strict, in
@@ -1857,7 +1915,7 @@ zip_tq(Line, E, #izip{anno=#a{anno=GA}=GAnno,
     {#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},
               defs=[{{Name,NumGenerators},Fun}],
               body=append(Pres) ++
-                  [#iapply{anno=GAnno,op=F,args=Args}]},[],St4}.
+                  [#iapply{anno=GAnno,op=F,args=Args}]},[],OptInfo,St4}.
 
 %% bc_tq(Line, Exp, [Qualifier], More, State) -> {LetRec,[PreExp],State}.
 %%  This TQ from Gustafsson ERLANG'05.
@@ -1943,7 +2001,12 @@ bc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
 bc_tq1(Line, E, [#izip{}=Zip|Qs], Mc, St) ->
     bzip_tq1(Line, E, Zip, Mc, St, Qs);
 bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
-    filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5);
+    TqFun = fun(L, {lc, Expr}, Qs1, Mc1, St1) ->
+                    {Bc, Bps, St2} = bc_tq1(L, Expr, Qs1, Mc1, St1),
+                    {Bc, Bps, none, St2}
+            end,
+    {Res, Pre, _OptInfo, St1} = filter_tq(Line, {lc, E}, Filter, Mc, St, Qs, TqFun),
+    {Res, Pre, St1};
 bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) ->
     bc_tq_build(Bl, [], AccVar, Elements, St0);
 bc_tq1(Line, E0, [], AccVar, St0) ->
@@ -2038,15 +2101,25 @@ bzip_tq1(Line, E, #izip{anno=#a{anno=_GA}=GAnno,
               body=append(Pres) ++
                   [#iapply{anno=LAnno,op=F,args=Args++[Mc]}]},[],St4}.
 
-mc_tq(Line, Es0, Qs, Mc, St0) ->
-    Es = map(fun({map_field_assoc,Lf,K,V}) -> {tuple,Lf,[K,V]} end, Es0),
-    {Lc,Pre0,St1} = lc_tq(Line, Es, Qs, Mc, St0),
-    {LcVar,St2} = new_var(St1),
+mc_tq(Line, Pairs, Qs, Mc, St0) ->
+    {Lc, Pre0, OptInfo, St1} = lc_tq(Line, {mc, Pairs, []}, Qs, Mc, St0),
+    {LcVar, St2} = new_var(St1),
+    Anno = #a{anno=lineno_anno(Line, St2)},
     Pre = Pre0 ++ [#iset{var=LcVar,arg=Lc}],
-    Call = #icall{module=#c_literal{val=maps},
-                  name=#c_literal{val=from_list},
-                  args=[LcVar]},
-    {Call,Pre,St2}.
+    case OptInfo of
+        {from_keys, ValueExpr} ->
+            Call = #icall{anno=Anno,
+                          module=#c_literal{val=maps},
+                          name=#c_literal{val=from_keys},
+                          args=[LcVar, ValueExpr]},
+            {Call, Pre, St2};
+        from_list ->
+            Call = #icall{anno=Anno,
+                          module=#c_literal{val=maps},
+                          name=#c_literal{val=from_list},
+                          args=[LcVar]},
+            {Call, Pre, St2}
+    end.
 
 make_refill([nomatch|RefillPats], Index, [_|Bodies], Args) ->
     make_refill(RefillPats, Index + 1, Bodies, Args);
@@ -2077,13 +2150,15 @@ make_clause(Anno, Pat, PatExtra, Guard, Body) ->
 %%  Transform an intermediate comprehension filter to its intermediate case
 %%  representation.
 
-filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg={Pre,Arg}},
+filter_tq(Line, Ctx0, #ifilter{anno=#a{anno=LA}=LAnno,arg={Pre,Arg}},
           Mc, St0, Qs, TqFun) ->
     %% The filter is an expression, it is compiled to a case of degree 1 with
     %% 3 clauses, one accumulating, one skipping and the final one throwing
     %% {case_clause,Value} where Value is the result of the filter and is not a
     %% boolean.
-    {Lc,Lps,St1} = TqFun(Line, E, Qs, Mc, St0),
+    %% Variables bound in Pre are visible in subsequent qualifiers and the body.
+    Ctx = add_pre_bound_vars(Ctx0, Pre),
+    {Lc,Lps,OptInfo,St1} = TqFun(Line, Ctx, Qs, Mc, St0),
     {FailPat,St2} = new_var(St1),
     Fc = fail_clause([FailPat], LA,
                      c_tuple([#c_literal{val=bad_filter},FailPat])),
@@ -2095,18 +2170,18 @@ filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg={Pre,Arg}},
                               pats=[#c_literal{val=false}],guard=[],
                               body=[Mc]}],
             fc=Fc},
-     Pre,St2};
-filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg=Guard},
+     Pre,OptInfo,St2};
+filter_tq(Line, Ctx, #ifilter{anno=#a{anno=LA}=LAnno,arg=Guard},
           Mc, St0, Qs, TqFun) when is_list(Guard) ->
     %% Otherwise it is a guard, compiled to a case of degree 0 with 2 clauses,
     %% the first matches if the guard succeeds and the comprehension continues
     %% or the second one is selected and the current element is skipped.
-    {Lc,Lps,St1} = TqFun(Line, E, Qs, Mc, St0),
+    {Lc,Lps,OptInfo,St1} = TqFun(Line, Ctx, Qs, Mc, St0),
     {#icase{anno=LAnno#a{anno=[list_comprehension|LA]},args=[],
             clauses=[#iclause{anno=LAnno,pats=[],guard=Guard,body=Lps ++ [Lc]}],
             fc=#iclause{anno=LAnno#a{anno=[compiler_generated|LA]},
                         pats=[],guard=[],body=[Mc]}},
-     [],St1}.
+     [],OptInfo,St1}.
 
 %% preprocess_quals(Line, [Qualifier], State) -> {[Qualifier'],State}.
 %%  Preprocess a list of Erlang qualifiers into its intermediate representation,
@@ -2690,6 +2765,37 @@ is_safe(#c_var{name=_}) -> true;                %Ordinary variable.
 is_safe(#c_literal{}) -> true;
 is_safe(_) -> false.
 
+%% add_bound_vars(Ctx, Pat) -> Ctx.
+%%  Add pattern vars to mc context (no-op for lc).
+%%  When Pat is 'nomatch', set BoundVars to 'nomatch' to disable optimization.
+add_bound_vars({lc, E}, _Pat) -> {lc, E};
+add_bound_vars({mc, Pairs, _BoundVars}, nomatch) -> {mc, Pairs, nomatch};
+add_bound_vars({mc, Pairs, nomatch}, _AccPat) -> {mc, Pairs, nomatch};
+add_bound_vars({mc, Pairs, BoundVars}, AccPat) ->
+    NewVars = lit_vars(AccPat),
+    {mc, Pairs, ordsets:union(NewVars, BoundVars)}.
+
+%% add_pre_bound_vars(Ctx, Pre) -> Ctx.
+%%  Add variables bound in pre-expressions (from filters) to mc context.
+add_pre_bound_vars({lc, E}, _Pre) -> {lc, E};
+add_pre_bound_vars({mc, Pairs, BoundVars}, Pre) ->
+    NewVars = ordsets:union([pre_expr_vars(E) || E <- Pre]),
+    {mc, Pairs, ordsets:union(NewVars, BoundVars)}.
+
+%% pre_bound_vars(Pre) -> ordset([Name]).
+%%  Extract variables bound in a list of pre-expressions.
+pre_expr_vars(#iset{var=#c_var{name=N}}) -> [N];
+pre_expr_vars(#imatch{pat=Pat}) -> lit_vars(Pat);
+pre_expr_vars(_) -> [].
+
+%% uses_bound_vars(Expr, BoundVars) -> boolean().
+%%  Check if a Core expression uses any of the bound variables.
+%%  When BoundVars is 'nomatch', always return true to disable optimization.
+uses_bound_vars(_Expr, nomatch) -> true;
+uses_bound_vars(Expr, BoundVars) ->
+    FreeVars = cerl_trees:free_variables(Expr),
+    not ordsets:is_disjoint(ordsets:from_list(FreeVars), BoundVars).
+
 %% fold_match(MatchExpr, Pat) -> {MatchPat,Expr}.
 %%  Fold nested matches into one match with aliased patterns.
 
@@ -4678,8 +4784,10 @@ lit_vars(#c_tuple{es=Es}, Vs) -> lit_list_vars(Es, Vs);
 lit_vars(#c_map{arg=V,es=Es}, Vs) -> lit_vars(V, lit_list_vars(Es, Vs));
 lit_vars(#c_map_pair{key=K,val=V}, Vs) -> lit_vars(K, lit_vars(V, Vs));
 lit_vars(#c_var{name=V}, Vs) -> add_element(V, Vs);
+lit_vars(#c_alias{var=#c_var{name=V},pat=P}, Vs) -> add_element(V, lit_vars(P, Vs));
 lit_vars(#imap{es=Es}, Vs) -> lit_list_vars(Es, Vs);
 lit_vars(#imappair{key=K,val=V}, Vs) -> lit_list_vars(K, lit_vars(V, Vs));
+lit_vars(#ibinary{segments=Segs}, Vs) -> ibitstr_vars(Segs, Vs);
 lit_vars(_, Vs) -> Vs.				%These are atomic
 
 lit_list_vars(Ls) -> lit_list_vars(Ls, []).
diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl
index 8406bbb297..dc848cb1a9 100644
--- a/lib/compiler/test/lc_SUITE.erl
+++ b/lib/compiler/test/lc_SUITE.erl
@@ -329,6 +329,9 @@ assignment_generator(_Config) ->
     ?assertEqual(singleton_generator_bin_1a(Bin),
                  assignment_generator_bin_1(Bin)),
 
+    ?assertEqual(singleton_generator_6(Seq),
+                 assignment_generator_6(Seq)),
+
     ok.
 
 assignment_generator_1(L) ->
@@ -387,6 +390,12 @@ singleton_generator_4b(L) ->
 assignment_generator_5(L) ->
     [Sqr || E <- L, is_integer(E), Sqr = E*E, Sqr < 100].
 
+singleton_generator_6(L) ->
+    #{E => true || E <- L, is_integer(E), Sqr <- [E*E], Sqr < 100}.
+
+assignment_generator_6(L) ->
+    #{E => true || E <- L, is_integer(E), Sqr = E*E, Sqr < 100}.
+
 singleton_generator_5a(L) ->
     [Sqr || E <- L, is_integer(E), Sqr <- [E*E], Sqr < 100].
 
diff --git a/lib/compiler/test/mc_SUITE.erl b/lib/compiler/test/mc_SUITE.erl
index 8e315eb395..e86d01c8f7 100644
--- a/lib/compiler/test/mc_SUITE.erl
+++ b/lib/compiler/test/mc_SUITE.erl
@@ -27,7 +27,7 @@
 -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
 	 init_per_group/2,end_per_group/2,
          basic/1,duplicate_keys/1,mixed/1,
-         shadow/1,bad_generators/1,multi/1]).
+         shadow/1,bad_generators/1,multi/1,from_keys_optimization/1]).
 
 -include_lib("common_test/include/ct.hrl").
 
@@ -43,7 +43,8 @@ groups() ->
        mixed,
        shadow,
        bad_generators,
-       multi]}].
+       multi,
+       from_keys_optimization]}].
 
 init_per_suite(Config) ->
     test_lib:recompile(?MODULE),
@@ -307,7 +308,59 @@ multi(_Config) ->
     Exp2 = #{1 => 1, 2 => 2, 5 => 5, 6 => 6},
     Exp2 = #{X => X, X + 1 => X + 1 || X <- [1, 5]},
     Exp3 = #{1 => 4, 5 => 8},
-    Exp3 = #{X => X+1, X => X+3 || X <- [1, 5]}.
+    Exp3 = #{X => X+1, X => X+3 || X <- [1, 5]},
+    ok.
+
+from_keys_optimization(_Config) ->
+    %% Literal values - should use from_keys
+    #{a := 42, b := 42} = #{K => 42 || K <- [a, b]},
+    #{a := foo, b := foo} = #{K => foo || K <- [a, b]},
+
+    %% Outer variable - should use from_keys
+    Value = id(make_ref()),
+    #{a := Value, b := Value} = #{K => Value || K <- [a, b]},
+
+    %% Safe expression on outer vars (tuple) - should use from_keys
+    X = id(1), Y = id(2),
+    #{a := {1, 2}, b := {1, 2}} = #{K => {X, Y} || K <- [a, b]},
+
+    %% With filter - should still use from_keys
+    #{2 := ok, 4 := ok} = #{K => ok || K <- [1,2,3,4], K rem 2 =:= 0},
+
+    %% Multiple expressions with same value - should use from_keys
+    #{a := 42, b := 42, 1 := 42, 2 := 42} =
+        #{K => 42, K2 => 42 || K <- [a, b], K2 <- [1, 2]},
+
+    %% Multiple expressions with outer var as value - should use from_keys
+    Z = id(outer),
+    #{a := outer, 1 := outer} = #{K => Z, K2 => Z || K <- [a], K2 <- [1]},
+
+    %% Multiple expressions with DIFFERENT values - should NOT use from_keys
+    #{a := 1, b := 1, 1 := 2, 2 := 2} =
+        #{K => 1, K2 => 2 || K <- [a, b], K2 <- [1, 2]},
+    #{2 := [Value], 3 := [Value], 4 := [Value], 5 := [Value]} =
+        #{2*K => [Value], 2*K+1 => [Value] || K <- [1, 2]},
+    #{2 := {val, Value}, 3 := {val, Value},
+      4 := {val, Value}, 5 := {val, Value}} =
+        #{2*K => {val, Value}, 2*K+1 => {val, Value} || K <- [1, 2]},
+    #{2 := [Value], 3 := [42], 4 := [Value], 5 := [42]} =
+        #{2*K => [Value], 2*K+1 => [42] || K <- [1, 2]},
+
+    %% Value from generator - should NOT use from_keys
+    #{a := 1, b := 2} = #{K => V || {K, V} <- [{a, 1}, {b, 2}]},
+
+    %% Value depends on key - should NOT use from_keys
+    #{1 := 2, 2 := 4} = #{K => K * 2 || K <- [1, 2]},
+
+    %% Value depends on filter - should NOT use from_keys
+    #{1 := 2, 2 := 4} = #{K => V || K <- [1, 2], is_integer(V = K * 2)},
+
+    %% Failable expression on outer vars - should NOT use from_keys
+    %% (if list is empty, the division would never execute)
+    A = id(1), B = id(0),
+    #{} = #{K => A div B || K <- [], K > 0},
+
+    ok.
 
 id(I) -> I.
 
-- 
2.51.0

openSUSE Build Service is sponsored by