diff --git a/erts/emulator/test/bs_utf_SUITE.erl b/erts/emulator/test/bs_utf_SUITE.erl index 224fea691399..9d1de12dcdb8 100644 --- a/erts/emulator/test/bs_utf_SUITE.erl +++ b/erts/emulator/test/bs_utf_SUITE.erl @@ -359,8 +359,8 @@ utf32_illegal_sequences(Config) when is_list(Config) -> utf32_fail_range(16#D800, 16#DFFF), %Reserved for UTF-16. utf32_fail_range(-100, -1), - <<>> = id(<< 0 || <> <= <<"àxxx">>, _ = X >>), - <<>> = id(<< 0 || <> <= <<"àxxx">>, _ = X >>), + <<>> = id(<< 0 || <> <= <<"àxxx">>, true =:= X >>), + <<>> = id(<< 0 || <> <= <<"àxxx">>, true =:= X >>), ok. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 98b4f26cbcc9..18e87aab701f 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -2090,6 +2090,7 @@ get_nomatch_total(NomatchModes) -> end end. +is_generator({match,_,_,_}) -> true; is_generator({generate,_,_,_}) -> true; is_generator({generate_strict,_,_,_}) -> true; is_generator({b_generate,_,_,_}) -> true; @@ -2189,6 +2190,8 @@ get_qual_anno(Abstract) -> element(2, Abstract). %% generator(Line, Generator, Guard, State) -> {Generator',State}. %% Transform a given generator into its #igen{} representation. +generator(Line, {match,L,P,E}, Gs, StrictPats, St0) -> + generator(Line, {generate_strict,L,P,{cons,L,E,{nil,L}}}, Gs, StrictPats, St0); generator(Line, {Generate,Lg,P0,E}, Gs, StrictPats, St0) when Generate =:= generate; Generate =:= generate_strict -> diff --git a/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl b/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl index 66f0bb312a14..ce0ea8b907db 100644 --- a/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl +++ b/lib/compiler/test/beam_validator_SUITE_data/singleton_inference.erl @@ -1,7 +1,17 @@ +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 1998-2025. All Rights Reserved. +%% +%% %CopyrightEnd% -module(singleton_inference). -export([test/0]). test() -> {'EXIT',{{badmatch,true}, _}} = - catch [0 || (X = (true or (X = is_port(node()))))], + catch case X = (true or (X = is_port(node()))) of + true -> 1; + false -> 0 + end, ok. diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl index dcc714179dc6..cdf2f6e50a51 100644 --- a/lib/compiler/test/bs_bincomp_SUITE.erl +++ b/lib/compiler/test/bs_bincomp_SUITE.erl @@ -261,7 +261,7 @@ inconsistent_types_2() -> << Y || _ <- Y, - (not ((false = Y) = (Y /= []))), (_ = Y) + (not ((false = Y) = (Y /= []))), true =:= (_ = Y) >> end >>. diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl index 5ab76e89e04f..76977c71e1f1 100644 --- a/lib/compiler/test/receive_SUITE.erl +++ b/lib/compiler/test/receive_SUITE.erl @@ -192,7 +192,7 @@ coverage(Config) when is_list(Config) -> %% Cover code for handling a non-boolean `br` in beam_ssa_dead. self() ! whatever, - {'EXIT',{{badmatch,_},_}} = (catch [a || other = receive whatever -> false end]), + {'EXIT',{{badmatch,_},_}} = (catch [a || true =:= (other = receive whatever -> false end)]), %% Cover code in beam_ssa_pre_codegen. self() ! 0, diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl index 0ae2170634ad..5550751f9265 100644 --- a/lib/compiler/test/trycatch_SUITE.erl +++ b/lib/compiler/test/trycatch_SUITE.erl @@ -1175,7 +1175,7 @@ grab_bag_3() -> try 2 of true -> << - "" || [V0] = door + "" || true =:= ([V0] = door) >> catch error:true:V0 -> diff --git a/lib/debugger/test/int_eval_SUITE_data/my_int_eval_module.erl b/lib/debugger/test/int_eval_SUITE_data/my_int_eval_module.erl index 8e4aebe35246..75d26afd3fbc 100644 --- a/lib/debugger/test/int_eval_SUITE_data/my_int_eval_module.erl +++ b/lib/debugger/test/int_eval_SUITE_data/my_int_eval_module.erl @@ -232,10 +232,10 @@ otp_8310() -> a = if (false orelse a) =:= a -> a; true -> b end, F1 = fun() -> a end, {'EXIT',{{bad_filter,a},_}} = - (catch {a, [X || X <- [1,2,3], _ = F1()]}), + (catch {a, [X || X <- [1,2,3], F1()]}), F2 = fun() -> << 3:8 >> end, {'EXIT',{{bad_filter,<<3>>},_}} = - (catch {a, << << X >> || << X >> <= << 7:8 >>,_ = F2() >>}), + (catch {a, << << X >> || << X >> <= << 7:8 >>, F2() >>}), {'EXIT',{{bad_generator,a},_}} = (catch {a, [X || X <- a]}), {'EXIT',{{bad_generator,b},_}} = diff --git a/lib/stdlib/src/erl_features.erl b/lib/stdlib/src/erl_features.erl index 3a84c3444788..1066758b0957 100644 --- a/lib/stdlib/src/erl_features.erl +++ b/lib/stdlib/src/erl_features.erl @@ -76,6 +76,14 @@ feature_specs() -> experimental => 25, approved => 27, keywords => ['maybe', 'else'], + type => extension}, + compr_assign => + #{short => "Assignment in comprehensions (EEP77)", + description => + "Implementation of '=' assignments in comprehensions proposed in EEP77.", + status => experimental, + experimental => 29, + keywords => [], type => extension}}. %% Return all currently known features. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 5bd529e50587..c4cf4f200ad0 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -405,6 +405,13 @@ format_error_1(update_literal) -> ~"expression updates a literal"; format_error_1(illegal_zip_generator) -> ~"only generators are allowed in a zip generator."; +format_error_1(compr_assign) -> + ~""" + matches using '=' are not allowed in comprehension qualifiers + unless the experimental 'compr_assign' language feature is enabled. + With 'compr_assign' enabled, a match 'P = E' will behave as a + strict generator 'P <-:- [E]'." + """; %% --- patterns and guards --- format_error_1(illegal_map_assoc_in_pattern) -> ~"illegal pattern, did you mean to use `:=`?"; format_error_1(illegal_pattern) -> ~"illegal pattern"; @@ -4114,12 +4121,20 @@ lc_quals([F|Qs], Vt, Uvt, St0) -> Info = is_guard_test2_info(St0), {Fvt,St1} = case is_guard_test2(F, Info) of true -> guard_test(F, Vt, St0); - false -> expr(F, Vt, St0) + false -> expr(F, Vt, check_compr_assign(F, St0)) end, lc_quals(Qs, vtupdate(Fvt, Vt), Uvt, St1); lc_quals([], Vt, Uvt, St) -> {Vt, Uvt, St}. +check_compr_assign({match,Anno,_,_}, St) -> + case lists:member(compr_assign, erl_features:enabled()) of + true -> St; + false -> add_error(Anno, compr_assign, St) + end; +check_compr_assign(_, St) -> + St. + is_guard_test2_info(#lint{records=RDs,locals=Locals,imports=Imports}) -> {RDs,fun(FA) -> is_local_function(Locals, FA) orelse diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index c897eb348e4e..f75711b3fc58 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -365,9 +365,9 @@ unused_vars_warn_lc(Config) when is_list(Config) -> [Z || Z <- (Y = X), % Y unused. Y > X]; % Y unbound. k(X) -> - [Y || Y = X > 3, Z = X]; % Z unused. + [Y || true =:= (Y = X > 3), true =:= (Z = X)]; % Z unused. k(X) -> - [Z || Y = X > 3, Z = X]. % Y unused. + [Z || true =:= (Y = X > 3), true =:= (Z = X)]. % Y unused. ">>, [warn_unused_vars], {error,[{{8,21},erl_lint,{unbound_var,'Y'}}, @@ -376,8 +376,8 @@ unused_vars_warn_lc(Config) when is_list(Config) -> {{4,34},erl_lint,{unused_var,'Y'}}, {{8,34},erl_lint,{unused_var,'Y'}}, {{10,31},erl_lint,{unused_var,'Y'}}, - {{13,36},erl_lint,{unused_var,'Z'}}, - {{15,25},erl_lint,{unused_var,'Y'}}]}}, + {{13,57},erl_lint,{unused_var,'Z'}}, + {{15,35},erl_lint,{unused_var,'Y'}}]}}, {lc14, <<"lc2() -> diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl index 2de85d6d53b3..af2e7fab9078 100644 --- a/lib/stdlib/test/qlc_SUITE.erl +++ b/lib/stdlib/test/qlc_SUITE.erl @@ -2977,14 +2977,14 @@ lookup2(Config) when is_list(Config) -> <<"%% Only guards are inspected. No lookup. etsc(fun(E) -> Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E), - Y = (X =:= 3)]), + true =:= (Y = (X =:= 3))]), {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q)) end, [{false,3},{true,3}])">>, <<"%% Only guards are inspected. No lookup. etsc(fun(E) -> Q = qlc:q([{X,Y} || {X,Y} <- ets:table(E), - Y = (X =:= 3)]), + true =:= (Y = (X =:= 3))]), {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q)) end, [{3,true},{4,true}])">>, @@ -2995,7 +2995,7 @@ lookup2(Config) when is_list(Config) -> true = ets:insert(E2, [{true,1},{false,2}]), Q = qlc:q([{X,Z} || {_,X} <- ets:table(E1), {Y,Z} <- ets:table(E2), - Y = (X =:= 3)]), + true =:= (Y = (X =:= 3))]), {'EXIT', {{badmatch,false},_}} = (catch qlc:e(Q)), ets:delete(E1), ets:delete(E2)">>, diff --git a/system/doc/reference_manual/features.md b/system/doc/reference_manual/features.md index 60e4b1f86791..61623cddd6ae 100644 --- a/system/doc/reference_manual/features.md +++ b/system/doc/reference_manual/features.md @@ -133,3 +133,6 @@ The following configurable features exist: [`maybe`](expressions.md#maybe) expression proposed in [EEP 49](https://www.erlang.org/eeps/eep-0049). It was approved in Erlang/OTP 27. +- **`compr_assign` (experimental)** - Implementation of `Pattern = Expr` + assignments in [comprehensions](expressions.md#comprehensions) + proposed in [EEP 77](https://www.erlang.org/eeps/eep-0077).