diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index c982e2f4015c..06e8660c6870 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -165,12 +165,14 @@ will_succeed(erlang, setelement, [Pos, Tuple0, _Value]=Args) -> {_, none} -> no; {#t_integer{elements={Min,Max}}=Pos, Tuple} -> - MaxTupleSize = max_tuple_size(Tuple), + TupleSizes = tuple_sizes(Tuple), + MaxTupleSize = lists:max(TupleSizes), + MinTupleSize = lists:min(TupleSizes), if MaxTupleSize < Min -> %% Index is always out of range. no; - Tuple0 =:= Tuple, Max =< MaxTupleSize -> + Tuple0 =:= Tuple, Max =< MinTupleSize -> %% We always have a tuple, and the index is always in %% range. yes; @@ -209,14 +211,14 @@ will_succeed(Mod, Func, Args) -> end end. -max_tuple_size(#t_union{tuple_set=[_|_]=Set}=Union) -> +tuple_sizes(#t_union{tuple_set=[_|_]=Set}=Union) -> Union = meet(Union, #t_tuple{}), %Assertion. Arities = [Arity || {{Arity, _Tag}, _Record} <:- Set], - lists:max(Arities); -max_tuple_size(#t_tuple{exact=true,size=Size}) -> - Size; -max_tuple_size(#t_tuple{exact=false}) -> - ?MAX_TUPLE_SIZE. + Arities; +tuple_sizes(#t_tuple{exact=true,size=Size}) -> + [Size]; +tuple_sizes(#t_tuple{exact=false,size=Size}) -> + [Size,?MAX_TUPLE_SIZE]. %% While we can't infer success for functions outside the 'erlang' %% module, it's safe to infer failure when we know they return `none` or diff --git a/lib/compiler/src/beam_core_to_ssa.erl b/lib/compiler/src/beam_core_to_ssa.erl index 8d6e167cd43c..dab8c9d52227 100644 --- a/lib/compiler/src/beam_core_to_ssa.erl +++ b/lib/compiler/src/beam_core_to_ssa.erl @@ -2799,7 +2799,7 @@ cg(#cg_goto{label=Label,args=As0}, St) -> Break = #cg_break{args=As,phi=Label}, {[Break],St}; cg(#cg_opaque{val=Check}, St) -> - {ssa_check_when,_,_,_,_} = Check, %Assertion. + {ssa_check_when,_,_,_,_,_} = Check, %Assertion. {[],St#cg{checks=[Check|St#cg.checks]}}. %% match_cg(Match, Fail, State) -> {[Ainstr],State}. diff --git a/lib/compiler/src/beam_doc.erl b/lib/compiler/src/beam_doc.erl index cc55f0cd9584..9bb4848bd55c 100644 --- a/lib/compiler/src/beam_doc.erl +++ b/lib/compiler/src/beam_doc.erl @@ -759,7 +759,9 @@ extract_documentation(AST, State) -> State1 = foldl(fun extract_documentation0/2, State, AST), State2 = purge_types_not_used_from_exported_functions(State1), State3 = purge_unreachable_types(State2), - warnings(AST, State3). + Ws = warnings(AST, State3), + digraph:delete(State3#docs.type_dependency), + Ws. %% %% purges types that are not used in exported functions. diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl index 0c8cf83aa32e..d0bb9f844866 100644 --- a/lib/compiler/src/beam_ssa_bsm.erl +++ b/lib/compiler/src/beam_ssa_bsm.erl @@ -127,19 +127,19 @@ analyze_module(#b_module{body=Fs}) -> end, #{}, Fs). has_bsm_ops(#b_function{bs=Blocks}) -> - hbo_blocks(maps:to_list(Blocks)). + hbo_blocks(maps:to_list(Blocks), false). -hbo_blocks([{_,#b_blk{is=Is}} | Blocks]) -> +hbo_blocks([{_,#b_blk{is=Is}} | Blocks], Acc) -> case hbo_is(Is) of - no -> hbo_blocks(Blocks); - yes -> true; + no -> hbo_blocks(Blocks, Acc); + yes -> hbo_blocks(Blocks, true); nif_start -> %% Disable optimizations for declared -nifs() %% to avoid leaking match contexts as NIF arguments. false end; -hbo_blocks([]) -> - false. +hbo_blocks([], Acc) -> + Acc. hbo_is([#b_set{op=bs_start_match} | _]) -> yes; hbo_is([#b_set{op=nif_start} | _]) -> nif_start; diff --git a/lib/compiler/src/beam_ssa_check.erl b/lib/compiler/src/beam_ssa_check.erl index 3a8f66c510b9..1b8e9f654419 100644 --- a/lib/compiler/src/beam_ssa_check.erl +++ b/lib/compiler/src/beam_ssa_check.erl @@ -56,19 +56,19 @@ functions(_Tag, []) -> function(Tag, F) -> run_checks(beam_ssa:get_anno(ssa_checks, F, []), F, Tag). -run_checks([{ssa_check_when,WantedResult,{atom,_,Tag},Args,Exprs}|Checks], +run_checks([{ssa_check_when,WantedResult,{atom,_,Tag},Args,CheckAnnos,Exprs}|Checks], F, Tag) -> - check_function(Args, Exprs, WantedResult, F) ++ run_checks(Checks, F, Tag); + check_function(Args, CheckAnnos, Exprs, WantedResult, F) ++ run_checks(Checks, F, Tag); run_checks([_|Checks], F, Tag) -> run_checks(Checks, F, Tag); run_checks([], _, _) -> []. -check_function(CheckArgs, Exprs, {atom,Loc,pass}, #b_function{args=_Args}=F) -> - run_check(CheckArgs, Exprs, Loc, F); -check_function(CheckArgs, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F) +check_function(CheckArgs, CheckAnnos, Exprs, {atom,Loc,pass}, #b_function{args=_Args}=F) -> + run_check(CheckArgs, CheckAnnos, Exprs, Loc, F); +check_function(CheckArgs, CheckAnnos, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F) when Key =:= fail ; Key =:= xfail -> - case run_check(CheckArgs, Exprs, Loc, F) of + case run_check(CheckArgs, CheckAnnos, Exprs, Loc, F) of [] -> %% This succeeded but should have failed {File,_} = beam_ssa:get_anno(location, F), @@ -76,14 +76,14 @@ check_function(CheckArgs, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F) _ -> [] end; -check_function(_, _, {atom,Loc,Result}, F) -> +check_function(_, _, _, {atom,Loc,Result}, F) -> {File,_} = beam_ssa:get_anno(location, F), [{File,[{Loc,?MODULE,{unknown_result_kind,Result}}]}]. -run_check(CheckArgs, Exprs, Loc, #b_function{args=FunArgs}=F) -> +run_check(CheckArgs, CheckAnnos, Exprs, Loc, #b_function{args=FunArgs}=F) -> + _ = check_annos(CheckAnnos, F#b_function.anno, #{}), init_and_run_check(CheckArgs, FunArgs, #{}, Loc, Exprs, F). - %% Create a mapping from each argument in the check pattern to the %% actual arguments of the SSA function. init_and_run_check([{var,Loc,'_'}|CheckArgs], diff --git a/lib/compiler/src/beam_ssa_destructive_update.erl b/lib/compiler/src/beam_ssa_destructive_update.erl index dd5d3025c7b3..d0a34c5c17d0 100644 --- a/lib/compiler/src/beam_ssa_destructive_update.erl +++ b/lib/compiler/src/beam_ssa_destructive_update.erl @@ -919,6 +919,8 @@ aggregate_ret_patches([{self,heap_tuple},TE={tuple_element,_,_,_}|Rest]) -> %% heap, the {self,heap_tuple} can be dropped. Due to the sort in %% patch_ret/3, self will always occur before tuple_element. aggregate_ret_patches([TE|Rest]); +aggregate_ret_patches([{hd,E0,D0},{hd,E1,D1}|Rest]) -> + aggregate_ret_patches([{hd, merge_patches(E0, E1), max(D0,D1)}|Rest]); aggregate_ret_patches([R={hd,_,_}]) -> R. @@ -968,9 +970,11 @@ merge_patches({tuple_element,I,E0,D0}, {tuple_element,I,E1,D1}) -> merge_patches({tuple_element,IA,EA,_}, {tuple_element,IB,EB,_}) -> {tuple_elements, [{IA,EA}, {IB,EB}]}; merge_patches({tuple_element,IA,EA,_}, {tuple_elements,Es}) -> - {tuple_elements,[{IA,EA}|Es]}; + {tuple_elements,merge_te(IA, EA, Es)}; merge_patches({tuple_elements,Es}, {tuple_element,IA,EA,_}) -> - {tuple_elements,[{IA,EA}|Es]}; + {tuple_elements,merge_te(IA, EA, Es)}; +merge_patches({hd,E0,D0},{hd,E1,D1}) -> + {hd, merge_patches(E0,E1), max(D0,D1)}; merge_patches(Patch, {self,heap_tuple}) -> %% If we find anything more specific than a heap_tuple, the more %% specific patch subsumes the heap_tuple @@ -980,6 +984,10 @@ merge_patches({self,heap_tuple}, Other) -> %% force the term onto the heap, we can ignore the new patch. Other. +merge_te(I, E, [{I,E0}|Es]) -> [{I, merge_patches(E0, E)}|Es]; +merge_te(I, E, [Other|Es]) -> [Other|merge_te(I, E, Es)]; +merge_te(I, E, []) -> [{I, E}]. + patch_phi(I0=#b_set{op=phi,args=Args0}, Patches, Cnt0) -> ?DP("Patching Phi:~n args: ~p~n patches: ~p~n", [Args0, Patches]), L2P = foldl(fun merge_phi_patch/2, #{}, Patches), diff --git a/lib/compiler/src/beam_ssa_throw.erl b/lib/compiler/src/beam_ssa_throw.erl index c704e16781e6..91b28770fd26 100644 --- a/lib/compiler/src/beam_ssa_throw.erl +++ b/lib/compiler/src/beam_ssa_throw.erl @@ -384,22 +384,28 @@ opt_throw([], _ThrownType, #b_set{args=[_, Reason]}=I) -> %% cases. opt_is_suitable(Start, Blocks, Vars, ThrownType) -> Ts = ois_init_ts(Vars, ThrownType), - ois_1([Start], Blocks, Ts). - -ois_1([Lbl | Lbls], Blocks, Ts0) -> - case Blocks of - #{ Lbl := #b_blk{last=Last,is=Is} } -> - case ois_is(Is, Ts0) of - {ok, Ts} -> - Next = ois_successors(Last, Ts), - ois_1(Next ++ Lbls, Blocks, Ts); - error -> - false - end; - #{} -> - ois_1(Lbls, Blocks, Ts0) + ois_1([Start], Blocks, Ts, sets:new()). + +ois_1([Lbl | Lbls], Blocks, Ts0, Seen0) -> + case sets:is_element(Lbl, Seen0) of + true -> + ois_1(Lbls, Blocks, Ts0, Seen0); + false -> + Seen1 = sets:add_element(Lbl, Seen0), + case Blocks of + #{ Lbl := #b_blk{last=Last,is=Is} } -> + case ois_is(Is, Ts0) of + {ok, Ts} -> + Next = ois_successors(Last, Ts), + ois_1(Next ++ Lbls, Blocks, Ts, Seen1); + error -> + false + end; + #{} -> + ois_1(Lbls, Blocks, Ts0, Seen1) + end end; -ois_1([], _Blocks, _Ts) -> +ois_1([], _Blocks, _Ts, _Seen) -> true. ois_successors(#b_switch{fail=Fail,list=List}, _Ts) -> diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 8eb279f91538..3d69722dbd48 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -1024,8 +1024,8 @@ expr({Op,Loc,Index}, St0) when Op =:= executable_line; {#iprimop{anno=#a{anno=lineno_anno(Loc, St0)}, name=#c_literal{val=Op}, args=[#c_literal{val=Index}]},[],St0}; -expr({ssa_check_when,L,WantedResult,Args,Tag,Clauses}, St) -> - {#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,Clauses}}, [], St}. +expr({ssa_check_when,L,WantedResult,Args,AnnoCheck,Tag,Clauses}, St) -> + {#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,AnnoCheck,Clauses}}, [], St}. blockify(L0, {sequential_match,_L1,First,Then}, E) -> [{single_match,L0,First,E}|blockify(L0, Then, E)]; diff --git a/lib/compiler/test/beam_ssa_check_SUITE.erl b/lib/compiler/test/beam_ssa_check_SUITE.erl index 63a44d4b7085..274f98969cf9 100644 --- a/lib/compiler/test/beam_ssa_check_SUITE.erl +++ b/lib/compiler/test/beam_ssa_check_SUITE.erl @@ -41,7 +41,8 @@ sanity_checks/1, tuple_inplace_checks/1, non_throwing_bifs/1, - phis_checks/1]). + phis_checks/1, + nifs_checks/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -60,7 +61,8 @@ groups() -> sanity_checks, tuple_inplace_checks, non_throwing_bifs, - phis_checks]}, + phis_checks, + nifs_checks]}, {post_ssa_opt_dynamic,test_lib:parallel(), [bs_size_unit_checks]}]. @@ -138,6 +140,10 @@ phis_checks(Config) when is_list(Config) -> run_pre_ssa_opt(phis, Config), run_post_ssa_opt(phis, Config). +nifs_checks(Config) when is_list(Config) -> + run_post_ssa_opt(nifs, Config), + run_post_ssa_opt(nifs_many_blocks, Config). + dynamic_workdir(Config) -> PrivDir = proplists:get_value(priv_dir, Config), filename:join(PrivDir, "dynamic"). diff --git a/lib/compiler/test/beam_ssa_check_SUITE_data/nifs_many_blocks.erl b/lib/compiler/test/beam_ssa_check_SUITE_data/nifs_many_blocks.erl new file mode 100644 index 000000000000..d8a8f24a69ea --- /dev/null +++ b/lib/compiler/test/beam_ssa_check_SUITE_data/nifs_many_blocks.erl @@ -0,0 +1,65 @@ +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2026. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% This module tests that beam_ssa_bsm:opt/2 correctly handles modules +%% containing nifs. +%% +-module(nifs_many_blocks). + +-export([load_nif/0, a_nif/2]). + +-nifs([a_nif/2]). + +load_nif() -> + ok = erlang:load_nif("dummy", 0). + +a_nif(1, <>) -> +%ssa% (_, _) { parameter_info => #{} } when post_ssa_opt -> +%ssa% ret(_). + case Tag of + $a -> {ok, a, a_nif(1, Rest)}; + $b -> {ok, b}; + $c -> {ok, c}; + $d -> {ok, d}; + $e -> {ok, e}; + $f -> {ok, f}; + $g -> {ok, g}; + $h -> {ok, h}; + $i -> {ok, i}; + $j -> {ok, j}; + $k -> {ok, k}; + $l -> {ok, l}; + $m -> {ok, m}; + $n -> {ok, n}; + $o -> {ok, o}; + $p -> {ok, p}; + $q -> {ok, q}; + $r -> {ok, r}; + $s -> {ok, s}; + $t -> {ok, t}; + $u -> {ok, u}; + $v -> {ok, v}; + $w -> {ok, w}; + $x -> {ok, x}; + $y -> {ok, y} + end; +a_nif(2, Bin) -> + <> = Bin, + Tag. diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl index 73bc2bc340f7..5d84ea194553 100644 --- a/lib/compiler/test/beam_type_SUITE.erl +++ b/lib/compiler/test/beam_type_SUITE.erl @@ -36,7 +36,7 @@ cover_maps_functions/1,min_max_mixed_types/1, not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1, funs/1,will_succeed/1,float_confusion/1, - cover_convert_ext/1]). + cover_convert_ext/1, catch_setelement/1]). %% Force id/1 to return 'any'. -export([id/1]). @@ -84,7 +84,8 @@ groups() -> funs, will_succeed, float_confusion, - cover_convert_ext + cover_convert_ext, + catch_setelement ]}]. init_per_suite(Config) -> @@ -1661,6 +1662,15 @@ cover_convert_ext(_Config) -> ok. +%% This used to cause an exception instead of returning an error. +catch_setelement(_Config) -> + {'EXIT', _} = catch_setelement_2(id(true), id(foo)), + ok. + +catch_setelement_2(B, V) -> + T = if B -> {1}; true -> {1,2} end, + catch setelement(2, T, V). + %%% %%% Common utilities. %%% diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl index dd25df57c2f2..8a643c29b0f2 100644 --- a/lib/compiler/test/bs_construct_SUITE.erl +++ b/lib/compiler/test/bs_construct_SUITE.erl @@ -755,6 +755,14 @@ private_append(_Config) -> {ok,<<>>} = private_append_3(id(<<>>)), {error,<<"wrong parity">>} = private_append_3(id(<<1>>)), + [{<<1,3>>,<<2,4>>}] = + private_append_4([{<<1>>,<<2>>},{<<3>>,<<4>>}]), + [{<<1,4>>,<<2,5>>,<<3,6>>}] = + private_append_5([{<<1>>,<<2>>,<<3>>},{<<4>>,<<5>>,<<6>>}]), + + [{<<1,4>>,{<<2,5>>,<<3,6>>}}] = + private_append_6([{<<1>>,<<2>>,<<3>>},{<<4>>,<<5>>,<<6>>}]), + ok. %% GH-7121: Alias analysis would not mark fun arguments as aliased, @@ -787,3 +795,49 @@ private_append_3(<>, {ok, Acc}) -> %% The compiler would fail to patch this tuple. private_append_3(<<>>, {error, <<"wrong parity">>}) end. + +%% Two appendable binaries tracked through the head of a *returned* list +%% would cause the private append pass to crash when it tried to patch the +%% {hd,...} tuple. +private_append_4(L) -> + private_append_4(L, private_append_4_seed()). + +private_append_4_seed() -> + [{<<>>, <<>>}]. + +private_append_4([{X,Y}|T], [{A,B}]) -> + private_append_4(T, [{<>, <>}]); +private_append_4([], Acc) -> + Acc. + +%% As above, but three accumulators -> three {hd,...} patches, so the +%% aggregate_ret_patches/1 fix has to fold an arbitrary number of them, +%% not just two. +private_append_5(L) -> + private_append_5(L, private_append_5_seed()). + +private_append_5_seed() -> + [{<<>>, <<>>, <<>>}]. + +private_append_5([{X,Y,Z}|T], [{A,B,C}]) -> + private_append_5(T, [{<>, + <>, + <>}]); +private_append_5([], Acc) -> + Acc. + + +%% A nested tuple in the list head ({B,C} both at outer index 1) made +%% merge_patches build a {tuple_elements,...} with a duplicate index, +%% which patch_literal_tuple could not apply. +private_append_6(L) -> + private_append_6(L, private_append_6_seed()). + +private_append_6_seed() -> + [{<<>>, {<<>>, <<>>}}]. + +private_append_6([{X,Y,Z}|T], [{A,{B,C}}]) -> + private_append_6(T, [{<>, + {<>, <>}}]); +private_append_6([], Acc) -> + Acc. diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl index cfad09f949d9..490db5860825 100644 --- a/lib/compiler/test/trycatch_SUITE.erl +++ b/lib/compiler/test/trycatch_SUITE.erl @@ -1585,9 +1585,10 @@ coverage_1() -> %% Cover some code in beam_ssa_throw. coverage_ssa_throw() -> cst_trivial(), - cst_raw(), - cst_stacktrace(), cst_types(), + cst_stacktrace(), + cst_raw(), + cst_hang(), ok. @@ -1655,6 +1656,20 @@ cst_raw() -> cst_raw_1() -> throw(id(gurka)). +cst_hang() -> + try cst_hang_1() + catch throw:gurka -> + ok; + _C:_R:Stack -> + %% Receive creates a label loop which used to cause + %% an infinite loop in the throw optimization. + Y = receive a -> 1; b -> 2 end, + id(Stack), + Y + end. + +cst_hang_1() -> throw(id(gurka)). + %% Cover some code in beam_ssa_pre_codegen. coverage_pre_codegen() -> try not (catch 22) of diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 823f129c7b61..563500eacac4 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -655,7 +655,7 @@ expr({executable_line,_,_}=E, St) -> {E, St}; expr({debug_line,_,_}=E, St) -> {E, St}; -expr({ssa_check_when,_,_,_,_,_}=E, St) -> +expr({ssa_check_when,_,_,_,_,_,_}=E, St) -> {E, St}. expr_list([E0 | Es0], St0) -> diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 2cd821d7827d..16571d410395 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -3282,7 +3282,7 @@ expr({remote,_Anno,M,_F}, _Vt, St) -> {[],add_error(erl_parse:first_anno(M), illegal_expr, St)}; expr({executable_line,_,_}, _Vt, St) -> {[], St}; -expr({ssa_check_when,_Anno,_WantedResult,_Args,_Tag,_Exprs}, _Vt, St) -> +expr({ssa_check_when,_Anno,_WantedResult,_Args,_AnnoCheck,_Tag,_Exprs}, _Vt, St) -> {[], St}. %% Check a call to function without a module name. This can be a call diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index 3182965d937a..956854846258 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -693,11 +693,17 @@ ssa_check_when_clauses -> ssa_check_when_clause ssa_check_when_clauses : ssa_check_when_clause -> '%ssa%' atom ssa_check_clause_args_ls 'when' atom '->' ssa_check_exprs '.' : - {ssa_check_when, ?anno('$1'), '$2', '$3', '$5', '$7'}. + {ssa_check_when, ?anno('$1'), '$2', '$3', [], '$5', '$7'}. +ssa_check_when_clause -> '%ssa%' atom ssa_check_clause_args_ls ssa_check_anno 'when' atom '->' + ssa_check_exprs '.' : + {ssa_check_when, ?anno('$1'), '$2', '$3', '$4', '$6', '$8'}. ssa_check_when_clause -> '%ssa%' ssa_check_clause_args_ls 'when' atom '->' ssa_check_exprs '.' : - {ssa_check_when, ?anno('$1'), {atom,?anno('$1'),pass}, '$2', '$4', '$6'}. + {ssa_check_when, ?anno('$1'), {atom,?anno('$1'),pass}, '$2', [], '$4', '$6'}. +ssa_check_when_clause -> '%ssa%' ssa_check_clause_args_ls ssa_check_anno 'when' atom '->' + ssa_check_exprs '.' : + {ssa_check_when, ?anno('$1'), {atom,?anno('$1'),pass}, '$2', '$3', '$5', '$7'}. ssa_check_exprs -> ssa_check_expr : [add_anno_check('$1', [])]. ssa_check_exprs -> ssa_check_expr ssa_check_anno : [add_anno_check('$1', '$2')].