1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2006-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%%%---------------------------------------------------------------- 21%%% Purpose:Test Suite for the 'erl_pp' module. 22%%%----------------------------------------------------------------- 23-module(erl_pp_SUITE). 24 25%%-define(debug, true). 26 27-ifdef(debug). 28-define(line, put(line, ?LINE), ). 29-define(config(X,Y), foo). 30-define(datadir, "erl_pp_SUITE_data"). 31-define(privdir, "erl_pp_SUITE_priv"). 32-define(t, test_server). 33-else. 34-include_lib("common_test/include/ct.hrl"). 35-define(datadir, proplists:get_value(data_dir, Config)). 36-define(privdir, proplists:get_value(priv_dir, Config)). 37-endif. 38 39-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 40 init_per_group/2,end_per_group/2, 41 init_per_testcase/2, end_per_testcase/2]). 42 43-export([ func/1, call/1, recs/1, try_catch/1, if_then/1, 44 receive_after/1, bits/1, head_tail/1, 45 cond1/1, block/1, case1/1, ops/1, messages/1, 46 import_export/1, misc_attrs/1, dialyzer_attrs/1, 47 hook/1, 48 neg_indent/1, 49 maps_syntax/1, 50 format_options/1, 51 quoted_atom_types/1, 52 53 otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1, 54 otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1, otp_9147/1, 55 otp_10302/1, otp_10820/1, otp_11100/1, otp_11861/1, pr_1014/1, 56 otp_13662/1, otp_14285/1, otp_15592/1, otp_15751/1, otp_15755/1, 57 otp_16435/1]). 58 59%% Internal export. 60-export([ehook/6]). 61 62init_per_testcase(_Case, Config) -> 63 Config. 64 65end_per_testcase(_Case, _Config) -> 66 ok. 67 68suite() -> 69 [{ct_hooks,[ts_install_cth]}, 70 {timetrap,{minutes,2}}]. 71 72all() -> 73 [{group, expr}, {group, attributes}, hook, neg_indent, 74 {group, tickets}]. 75 76groups() -> 77 [{expr, [], 78 [func, call, recs, try_catch, if_then, receive_after, 79 bits, head_tail, cond1, block, case1, ops, 80 messages, maps_syntax, quoted_atom_types, 81 format_options 82 ]}, 83 {attributes, [], [misc_attrs, import_export, dialyzer_attrs]}, 84 {tickets, [], 85 [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238, 86 otp_8473, otp_8522, otp_8567, otp_8664, otp_9147, 87 otp_10302, otp_10820, otp_11100, otp_11861, pr_1014, otp_13662, 88 otp_14285, otp_15592, otp_15751, otp_15755, otp_16435]}]. 89 90init_per_suite(Config) -> 91 Config. 92 93end_per_suite(_Config) -> 94 ok. 95 96init_per_group(_GroupName, Config) -> 97 Config. 98 99end_per_group(_GroupName, Config) -> 100 Config. 101 102 103 104func(Config) when is_list(Config) -> 105 Ts = [{func_1, 106 <<"-record(r1, {a,b}). 107 -record(r3, {a = fun(_) -> #r1{} end(1), b}). 108 109 t() -> 110 fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}). 111 ">>}, 112 {func_2, 113 <<"-record(r1, {a,b}). 114 -record(r3, {a = fun(_) -> #r1{} end(1), b}). 115 116 t() -> 117 fsdfsdfjsdfjkljf:sdlfjdsfjlf( 118 fun(sdfsd) -> {sdkjsdf,sdfjsdkljfsdl,sdfkjdklf} end). 119 ">>}, 120 {func_3, 121 <<"t() -> fun t/0.">>}, 122 {func_4, 123 <<"t() -> fun modul:foo/3.">>}, 124 {func_5, % 'when' is moved down one line 125 <<"tkjlksjflksdjflsdjlk() 126 when kljlsajflksjdfklsjdlkjfklsdklfjsdlf < 127 kljasjfdsjflsdjfklsdjfklsdjfklsd -> 128 foo.">>}, 129 {func_6, 130 <<"t() -> 131 (fun() -> 132 true 133 end)().">>}, 134 {func_7, 135 <<"t(M, F, A) -> fun M:F/A.">>}, 136 {func_8, 137 <<"-record(r1, {a,b}). 138 -record(r3, {a = fun Id(_) -> #r1{} end(1), b}). 139 140 t() -> 141 fun Id(A) when record(A#r3.a, r1) -> 7 end(#r3{}). 142 ">>}, 143 {func_9, 144 <<"-record(r1, {a,b}). 145 -record(r3, {a = fun Id(_) -> #r1{} end(1), b}). 146 147 t() -> 148 fsdfsdfjsdfjkljf:sdlfjdsfjlf( 149 fun Id(sdfsd) -> {sdkjsdf,sdfjsdkljfsdl,sdfkjdklf} end). 150 ">>}, 151 {func_10, 152 <<"t() -> 153 (fun True() -> 154 true 155 end)().">>} 156 ], 157 compile(Config, Ts), 158 ok. 159 160call(Config) when is_list(Config) -> 161 Ts = [{call_1, 162 <<"t() -> 163 fookjljsflj:barlkjlkjsdfj(kjdslfjsdl,hej,san,sa, 164 foo,sdfds,sdfsdf,sdfsd,sdfdsf,sdfdsf,sfdsf, 165 sfds,sdfsdf,sfds). 166 ">>} 167 ], 168 compile(Config, Ts), 169 ok. 170 171recs(Config) when is_list(Config) -> 172 %% Evolved while testing strict record tests in guards... 173 Ts = [{recs_1, 174 <<"-compile(strict_record_tests). 175 -record(r, {a = 4,b}). 176 -record(r1, {a,b}). 177 -record(r2, {a = #r1{},b,c=length([1,2,3])}). 178 -record(r3, {a = fun(_) -> #r1{} end(1), b}). 179 -record(r4, {a = fun R1(_) -> #r1{} end(1), b}). 180 181 t() -> 182 foo = fun(A) when A#r1.a > A#r1.b -> foo end(#r1{b = 2}), 183 0 = fun(A) when A#r2.a -> 0 end(#r2{a = true}), 184 1 = fun(A) when (#r1{a = A})#r1.a > 2 -> 1 end(3), 185 2 = fun(N) when ((#r2{a = #r{a = 4}, b = length([a,b,c])})#r2.a)#r.a > N -> 186 2 end(2), 187 3 = fun(A) when (A#r2.a)#r1.a =:= 3 -> 3 end(#r2{a = #r1{a = 3}}), 188 ok = fun() -> 189 F = fun(A) when record(A#r.a, r1) -> 4; 190 (A) when record(A#r1.a, r1) -> 5 191 end, 192 5 = F(#r1{a = #r1{}}), 193 4 = F(#r{a = #r1{}}), 194 ok 195 end(), 196 3 = fun(A) when record(A#r1.a, r), 197 (A#r1.a)#r.a > 3 -> 3 198 end(#r1{a = #r{a = 4}}), 199 7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}), 200 [#r1{a = 2,b = 1}] = 201 fun() -> 202 [A || A <- [#r1{a = 1, b = 3}, 203 #r2{a = 2,b = 1}, 204 #r1{a = 2, b = 1}], 205 A#r1.a > 206 A#r1.b] 207 end(), 208 {[_],b} = 209 fun(L) -> 210 %% A is checked only once: 211 R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b], 212 A = #r2{a = true}, 213 %% A is checked again: 214 B = if A#r1.a -> a; true -> b end, 215 {R1,B} 216 end([#r1{a = true, b = true}]), 217 218 p = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o; 219 (_) -> p 220 end(#r1{a = 2}), 221 222 o = fun(A) when (A#r1.a =:= 2) orelse (A#r2.a =:= 1) -> o; 223 (_) -> p 224 end(#r1{a = 2}), 225 226 %% The test done twice (an effect of doing the test as soon as possible). 227 3 = fun(A) when A#r1.a > 3, 228 record(A, r1) -> 3 229 end(#r1{a = 5}), 230 231 ok = fun() -> 232 F = fun(A) when (A#r2.a =:= 1) orelse (A#r2.a) -> 2; 233 (A) when (A#r1.a =:= 1) orelse (A#r1.a) -> 1; 234 (A) when (A#r2.a =:= 2) andalso (A#r2.b) -> 3 235 end, 236 1 = F(#r1{a = 1}), 237 2 = F(#r2{a = true}), 238 3 = F(#r2{a = 2, b = true}), 239 ok 240 end(), 241 242 b = fun(A) when false or not (A#r.a =:= 1) -> a; 243 (_) -> b 244 end(#r1{a = 1}), 245 b = fun(A) when not (A#r.a =:= 1) or false -> a; 246 (_) -> b 247 end(#r1{a = 1}), 248 249 ok = fun() -> 250 F = fun(A) when not (A#r.a =:= 1) -> yes; 251 (_) -> no 252 end, 253 no = F(#r1{a = 2}), 254 yes = F(#r{a = 2}), 255 no = F(#r{a = 1}), 256 ok 257 end(), 258 259 %% No extra check added: 260 a = fun(A) when record(A, r), 261 A#r.a =:= 1, 262 A#r.b =:= 2 ->a 263 end(#r{a = 1, b = 2}), 264 a = fun(A) when erlang:is_record(A, r), 265 A#r.a =:= 1, 266 A#r.b =:= 2 -> a 267 end(#r{a = 1, b = 2}), 268 a = fun(A) when is_record(A, r), 269 A#r.a =:= 1, 270 A#r.b =:= 2 -> a 271 end(#r{a = 1, b = 2}), 272 273 nop = fun(A) when (is_record(A, r1) and (A#r1.a > 3)) or (A#r2.a < 1) -> 274 japp; 275 (_) -> 276 nop 277 end(#r2{a = 0}), 278 nop = fun(A) when (A#r1.a > 3) or (A#r2.a < 1) -> japp; 279 (_) -> 280 nop 281 end(#r2{a = 0}), 282 283 ok = fun() -> 284 F = fun(A) when (A#r1.a =:= 2) or (A#r2.a =:= 1) -> o; 285 (_) -> p 286 end, 287 p = F(#r2{a = 1}), 288 p = F(#r1{a = 2}), 289 ok 290 end(), 291 292 ok = fun() -> 293 F = fun(A) when fail, A#r1.a; A#r1.a -> ab; 294 (_) -> bu 295 end, 296 ab = F(#r1{a = true}), 297 bu = F(#r2{a = true}), 298 ok 299 end(), 300 301 both = fun(A) when A#r.a, A#r.b -> both 302 end(#r{a = true, b = true}), 303 304 ok = fun() -> 305 F = fun(A, B) when ((A#r1.a) orelse (B#r2.a)) 306 or (B#r2.b) or (A#r1.b) -> 307 true; 308 (_, _) -> false 309 end, 310 true = F(#r1{a = false, b = false}, 311 #r2{a = false, b = true}), 312 false = F(#r1{a = true, b = true}, 313 #r1{a = false, b = true}), 314 ok 315 end(), 316 317 ok. 318 ">>}, 319 {recs_2, 320 <<"-record(r1, {a, b = foo:bar(kljlfjsdlf, kjlksdjf)}). 321 -record(r2, {c = #r1{}, d = #r1{a = bar:foo(kljklsjdf)}}). 322 323 t() -> 324 R = #r2{}, 325 R#r2{c = R, d = #r1{}}.">>} 326 ], 327 compile(Config, Ts), 328 329 ok = pp_expr(<<"case #r{a={1,2},b=#r{}} of 330 X=Y=#r{a=foo,b=bar} -> 331 {(foooo:baaaar(X))#r{a = rep},Y,#r.b} 332 end">>), 333 ok = pp_expr(<<"R#r{a = {kljasdklf,sdkfjsdl,sdafjkllsdf,sdfkjsd, 334 sdafjsd,sdf,sdafsd,sdfdsf,sdfdsf,dsfds}}">>), 335 ok. 336 337try_catch(Config) when is_list(Config) -> 338 Ts = [{try_1, % copied from erl_eval_SUITE 339 <<"t() -> try 1 of 1 -> 2 catch _:_ -> 3 end.">>}, 340 {try_2, 341 <<"t() -> try 1 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.">>}, 342 {try_3, 343 <<"t() -> try 3 of 1 -> 2; 3 -> 4 catch _:_ -> 5 end.">>}, 344 {try_4, 345 <<"t() -> try 1 after put(try_catch, 2) end.">>}, 346 {try_5, 347 <<"t() -> try 1 of 1 -> 2; 3 -> 4 348 after put(try_catch, 5) end.">>}, 349 {try_6, 350 <<"t() -> try 1=2 catch throw:{badmatch,2} -> 3 end.">>}, 351 {try_7, 352 <<"t() -> try 1=2 of 3 -> 4 353 catch error:{badmatch,2} -> 5 end.">>}, 354 {try_8, 355 <<"t() -> try 1=2 356 catch error:{badmatch,2} -> 3 357 after put(try_catch, 4) end.">>}, 358 {try_9, 359 <<"t() -> try 1=2 360 catch error:{badmatch,2} -> 3 361 after put(try_catch, 4) end.">>}, 362 {try_10, 363 <<"t() -> try a,b,c 364 catch exit:_ -> d; 365 throw:_ -> t; 366 error:{foo,bar} -> foo, 367 bar 368 end.">>}, 369 370 {catch_1, 371 <<"t() -> catch foo.">>}, 372 {catch_2, 373 <<"t() -> case catch foo of bar -> foo end.">>}, 374 {catch_3, 375 <<"t() -> catch begin begin foo, bar, foo:bar(kljsldkfjdls,kljsdl), 376 (catch bar:foo(foo)) end end.">>} 377 ], 378 compile(Config, Ts), 379 ok = pp_expr(<<"try 380 erl_internal:bif(M,F,length(Args)) 381 of 382 true -> 383 call(N,Args,Prec,Hook); 384 false -> 385 call(Name,Args,Prec,Hook) 386 after foo end">>), 387 ok. 388 389if_then(Config) when is_list(Config) -> 390 Ts = [{if_1, 391 <<"t() -> if 1 > 2 -> 1; true -> b end.">>}, 392 {if_2, 393 <<"t() -> if true -> true end.">>}, 394 {if_3, 395 <<"t() -> if 1 == 2 -> a; 1 > 2 -> b; 1 < 2 -> c end.">>} 396 ], 397 compile(Config, Ts), 398 ok. 399 400receive_after(Config) when is_list(Config) -> 401 Ts = [{rec_1, 402 <<"t() -> receive foo -> bar; bar -> foo end.">>}, 403 {rec_2, 404 <<"t() -> receive foo -> bar after foo:bar() -> 0 end.">>}, 405 {rec_3, 406 <<"t() -> receive after 1 -> ok end.">>}, 407 {rec_4, 408 <<"t() -> receive {X,Y} -> {a,X,Y} end.">>}, 409 {rec_5, 410 <<"t() -> receive 411 {X,Y} -> 412 {X,Y}; 413 Z -> 414 Z 415 after 416 foo:bar() -> 417 {3,4} 418 end.">>} 419 ], 420 compile(Config, Ts), 421 ok. 422 423bits(Config) when is_list(Config) -> 424 Ts = [{bit_1, % copied from shell_SUITE 425 <<"t() -> <<(<<\"abc\">>):3/binary>>.">>}, 426 {bit_2, 427 <<"t() -> <<(<<\"abc\">>)/binary>>.">>}, 428 {bit_3, 429 <<"t() -> <<3.14:64/float>>.">>}, 430 {bit_4, 431 <<"t() -> <<-20/signed>> = <<-20>>.">>}, 432 {bit_5, 433 <<"t() -> <<-300:16/signed>> = <<-300:16>>.">>}, 434 {bit_6, 435 <<"t() -> <<-(1 bsl 29):32/signed>> = <<-(1 bsl 29):32>>.">>}, 436 {bit_7, 437 <<"t() -> <<A:4,B:4,C:4,D:4,E:4,F:4>> = <<\"hej\">>.">>}, 438 {bit_8, 439 <<"t() -> <<>>.">>}, 440 {bit_9, 441 <<"">>} 442 ], 443 compile(Config, Ts), 444 ok = pp_expr(<<"<<(list_to_binary([1,2]))/binary>>">>), 445 ok = pp_expr( 446 <<"<<(list_to_binary([1,2])):all/binary-unit:8-unsigned-big>>">>), 447 ok = pp_expr(<<"<<<<\"hej\">>/binary>>">>), 448 ok = pp_expr(<<"<<(foo:bar())/binary>>">>), 449 ok = pp_expr(<<"<<(a)/binary>>">>), 450 ok = pp_expr(<<"<<a/binary>>">>), 451 ok = pp_expr(<<"<<{a,b}/binary>>">>), 452 ok = pp_expr(<<"<<{foo:bar(),b}/binary>>">>), 453 ok = pp_expr(<<"<<(foo:bar()):(foo:bar())/binary>>">>), 454 ok. 455 456head_tail(Config) when is_list(Config) -> 457 Ts = [{list_1, 458 <<"t() -> [a | b].">>}, 459 {list_2, 460 <<"t() -> [a,b,$\n].">>}, 461 {list_3, 462 <<"t() -> [].">>}, 463 {list_4, 464 <<"t() -> [a].">>}, 465 {list_5, 466 <<"t() -> 467 [foo:bar(lkjljlskdfj, klsdajflds, sdafkljsdlfkjdas, kjlsdadjl), 468 bar:foo(kljlkjsdf, lkjsdlfj, [kljsfj, sdfdsfsad])].">>} 469 ], 470 compile(Config, Ts), 471 ok. 472 473cond1(Config) when is_list(Config) -> 474 C = {'cond',1,[{clause,2,[],[[{tuple,2,[{atom,2,foo},{atom,2,bar}]}]], 475 [{cons,3,{atom,3,a},{cons,3,{atom,3,b},{nil,3}}}]}, 476 {clause,4,[],[[{atom,4,true}]], 477 [{tuple,5,[{atom,5,x},{atom,5,y}]}]}]}, 478 CChars = flat_expr1(C), 479 "cond\n" 480 " {foo, bar} ->\n" 481 " [a, b];\n" 482 " true ->\n" 483 " {x, y}\n" 484 "end" = CChars, 485 ok. 486 487block(Config) when is_list(Config) -> 488 Ts = [{block_1, 489 <<"t() -> begin a,{c,d} end.">>} 490 ], 491 compile(Config, Ts), 492 ok. 493 494case1(Config) when is_list(Config) -> 495 Ts = [{case_1, 496 <<"t() -> case {foo,bar} of 497 {A,B} when true -> 498 [A,B]; 499 _ -> 500 foo 501 end.">>} 502 ], 503 compile(Config, Ts), 504 ok = pp_expr(<<"case 505 erl_internal:bif(M,F,length(Args)) 506 of 507 true -> 508 call(N,Args,Prec,Hook); 509 false -> 510 call(Name,Args,Prec,Hook) 511 end">>), 512 ok. 513 514ops(Config) when is_list(Config) -> 515 Ts = [{ops_1, 516 <<"t() -> {a,b} + (3 - 2) + 4.">>}, 517 {ops_2, 518 <<"t() -> a - (3 + 4).">>}, 519 {ops_3, 520 <<"t() -> - (- (- (- (- 3)))).">>} 521 ], 522 compile(Config, Ts), 523 ok. 524 525messages(Config) when is_list(Config) -> 526 true = "{error,{some,\"error\"}}\n" =:= 527 lists:flatten(erl_pp:form({error,{some,"error"}})), 528 true = "{warning,{some,\"warning\"}}\n" =:= 529 lists:flatten(erl_pp:form({warning,{some,"warning"}})), 530 "\n" = flat_form({eof,0}), 531 ok. 532 533import_export(Config) when is_list(Config) -> 534 Ts = [{import_1, 535 <<"-import(lists, [max/1, reverse/1]). 536 -import(sofs, []). 537 t(L) -> 538 max(reverse(L)).">>}, 539 {export_1, 540 <<"-export([t/0]). 541 -export([]). 542 t() -> [].">>}, 543 {qlc_1, 544 <<"-include_lib(\"stdlib/include/qlc.hrl\"). 545 t() -> qlc:q([X || X <- []]).">>} 546 ], 547 compile(Config, Ts), 548 ok. 549 550format_options(Config) when is_list(Config) -> 551 "case 1 of\n" 552 " 2 ->\n" 553 " 3;\n" 554 " 4 ->\n" 555 " 5\n" 556 "end" = flat_parse_and_pp_expr("case 1 of 2 -> 3; 4 -> 5 end", 0, [{indent, 2}]), 557 558 "-spec foo(bar(),\n" 559 " qux()) ->\n" 560 " T |\n" 561 " baz(T)\n" 562 " when\n" 563 " T ::\n" 564 " tuple().\n" = 565 lists:flatten( 566 parse_and_pp_forms( 567 "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().", 568 [{indent, 2}, {linewidth, 20}] 569 ) 570 ), 571 572 "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().\n" = 573 lists:flatten( 574 parse_and_pp_forms( 575 "-spec foo(bar(), qux()) -> T | baz(T) when T :: tuple().", 576 [{indent, 2}, {linewidth, 1000}] 577 ) 578 ). 579 580misc_attrs(Config) when is_list(Config) -> 581 ok = pp_forms(<<"-module(m). ">>), 582 ok = pp_forms(<<"-module(m, [Aafjlksfjdlsjflsdfjlsdjflkdsfjlk," 583 "Blsjfdlslfjsdf]). ">>), 584 ok = pp_forms(<<"-export([]). ">>), 585 ok = pp_forms(<<"-export([foo/2, bar/0]). ">>), 586 ok = pp_forms(<<"-export([bar/0]). ">>), 587 ok = pp_forms(<<"-import(lists, []). ">>), 588 ok = pp_forms(<<"-import(lists, [map/2]). ">>), 589 ok = pp_forms(<<"-import(lists, [map/2, foreach/2]). ">>), 590 ok = pp_forms(<<"-'wild '({attr2,3}). ">>), 591 ok = pp_forms(<<"-record(a, {b,c}). ">>), 592 ok = pp_forms(<<"-record(' a ', {}). ">>), 593 ok = pp_forms(<<"-record(' a ', {foo = foo:bar()}). ">>), 594 ok = pp_forms(<<"-custom1(#{test1 => init/2, test2 => [val/1, val/2]}). ">>), 595 ok. 596 597dialyzer_attrs(Config) when is_list(Config) -> 598 ok = pp_forms(<<"-type foo() :: #bar{}. ">>), 599 ok = pp_forms(<<"-opaque foo() :: {bar, fun((X, [42,...]) -> X)}. ">>), 600 ok = pp_forms(<<"-spec foo(bar(), qux()) -> [T | baz(T)]. ">>), 601 ok = pp_forms(<<"-callback foo(<<_:32,_:_*4>>, T) -> T. ">>), 602 ok. 603 604hook(Config) when is_list(Config) -> 605 F = fun(H) -> H end, 606 do_hook(F). 607 608do_hook(HookFun) -> 609 Lc = parse_expr(binary_to_list(<<"[X || X <- [1,2,3]].">>)), 610 H = HookFun(fun hook/4), 611 A0 = erl_anno:new(0), 612 Expr = {call,A0,{atom,A0,fff},[{foo,Lc},{foo,Lc},{foo,Lc}]}, 613 EChars = lists:flatten(erl_pp:expr(Expr, 0, H)), 614 Call = {call,A0,{atom,A0,foo},[Lc]}, 615 Expr2 = {call,A0,{atom,A0,fff},[Call,Call,Call]}, 616 EChars2 = erl_pp:exprs([Expr2]), 617 true = EChars =:= lists:flatten(EChars2), 618 619 EsChars = erl_pp:exprs([Expr], H), 620 true = EChars =:= lists:flatten(EsChars), 621 622 A1 = erl_anno:new(1), 623 F = {function,A1,ffff,0,[{clause,A1,[],[],[Expr]}]}, 624 FuncChars = lists:flatten(erl_pp:function(F, H)), 625 F2 = {function,A1,ffff,0,[{clause,A1,[],[],[Expr2]}]}, 626 FuncChars2 = erl_pp:function(F2), 627 true = FuncChars =:= lists:flatten(FuncChars2), 628 FFormChars = erl_pp:form(F, H), 629 true = FuncChars =:= lists:flatten(FFormChars), 630 631 A = {attribute,A1,record,{r,[{record_field,A1,{atom,A1,a},Expr}]}}, 632 AChars = lists:flatten(erl_pp:attribute(A, H)), 633 A2 = {attribute,A1,record,{r,[{record_field,A1,{atom,A1,a},Expr2}]}}, 634 AChars2 = erl_pp:attribute(A2), 635 true = AChars =:= lists:flatten(AChars2), 636 AFormChars = erl_pp:form(A, H), 637 true = AChars =:= lists:flatten(AFormChars), 638 639 "INVALID-FORM:{foo,bar}:" = lists:flatten(erl_pp:expr({foo,bar})), 640 641 %% A list (as before R6), not a list of lists. 642 G = [{op,A1,'>',{atom,A1,a},{foo,{atom,A1,b}}}], % not a proper guard 643 GChars = lists:flatten(erl_pp:guard(G, H)), 644 G2 = [{op,A1,'>',{atom,A1,a}, 645 {call,A0,{atom,A0,foo},[{atom,A1,b}]}}], % not a proper guard 646 GChars2 = erl_pp:guard(G2), 647 true = GChars =:= lists:flatten(GChars2), 648 649 EH = HookFun({?MODULE, ehook, [foo,bar]}), 650 XEChars = erl_pp:expr(Expr, -1, EH), 651 true = remove_indentation(EChars) =:= lists:flatten(XEChars), 652 XEChars2 = erl_pp:expr(Expr, EH), 653 true = EChars =:= lists:flatten(XEChars2), 654 655 %% Note: no leading spaces before "begin". 656 Block = {block,A0,[{match,A0,{var,A0,'A'},{integer,A0,3}}, 657 {atom,A0,true}]}, 658 "begin\n A =" ++ _ = 659 lists:flatten(erl_pp:expr(Block, 17, none)), 660 661 %% Special... 662 true = 663 "{some,value}" =:= lists:flatten(erl_pp:expr({value,A0,{some,value}})), 664 665 %% More compatibility: before R6 666 OldIf = {'if',A0,[{clause,A0,[],[{atom,A0,true}],[{atom,A0,b}]}]}, 667 NewIf = {'if',A0,[{clause,A0,[],[[{atom,A0,true}]],[{atom,A0,b}]}]}, 668 OldIfChars = lists:flatten(erl_pp:expr(OldIf)), 669 NewIfChars = lists:flatten(erl_pp:expr(NewIf)), 670 true = OldIfChars =:= NewIfChars, 671 672 ok. 673 674remove_indentation(S) -> 675 %% T is for the very special leaf(" ") used for lc and bc. 676 T = re:replace(S, " \n *", "", [{return,list},global]), 677 re:replace(T, "\n *", " ", [{return,list},global]). 678 679ehook(HE, I, P, H, foo, bar) -> 680 hook(HE, I, P, H). 681 682hook({foo,E}, I, P, H) -> 683 A = erl_anno:new(0), 684 erl_pp:expr({call,A,{atom,A,foo},[E]}, I, P, H). 685 686neg_indent(Config) when is_list(Config) -> 687 ok = pp_expr(<<"begin a end">>), 688 ok = pp_expr(<<"begin a,b end">>), 689 ok = pp_expr(<<"try a,b,c 690 catch exit:_ -> d; 691 throw:_ -> t; 692 error:{foo,bar} -> foo, 693 bar 694 end">>), 695 ok = pp_expr( 696 <<"fun() -> 697 F = fun(A, B) when ((A#r1.a) orelse (B#r2.a)) 698 or (B#r2.b) or (A#r1.b) -> 699 true; 700 (_, _) -> false 701 end, 702 true = F(#r1{a = false, b = false}, 703 #r2{a = false, b = true}), 704 false = F(#r1{a = true, b = true}, 705 #r1{a = false, b = true}), 706 ok 707 end()">>), 708 709 ok = pp_expr(<<"[X || X <- a, true]">>), 710 ok = pp_expr(<<"{[a,b,c],[d,e|f]}">>), 711 ok = pp_expr(<<"f(a,b,c)">>), 712 ok = pp_expr(<<"fun() when a,b;c,d -> a end">>), 713 ok = pp_expr(<<"fun A() when a,b;c,d -> a end">>), 714 ok = pp_expr(<<"<<34:32,17:32>>">>), 715 ok = pp_expr(<<"if a,b,c -> d; e,f,g -> h,i end">>), 716 ok = pp_expr(<<"if a -> d; c -> d end">>), 717 ok = pp_expr(<<"receive after 1 -> 2 end">>), 718 ok = pp_expr(<<"begin a,b,c end">>), 719 720 "\"\"" = flat_expr({string,0,""}), 721 ok = pp_expr(<<"\"abc\"">>), 722 ok = pp_expr(<<"\"abc\n\n\n\n\nkjsd\n\n\n\n\nkljsddf\n\n\n\n\n" 723 "klafd\n\n\n\n\nkljsdf\n\n\n\n\nsdf\n\n\n\n\n\"">>), 724 ok = pp_expr(<<"fkjlskljklkkljlkjlkjkljlkjsljklf" 725 "lsdjlfdsjlfjsdlfjdslfjdlsjfsdjfklsdkfjsdf(" 726 "\"abc\n\n\n\n\nkjsd\n\n\n\n\nkljsddf\n\n\n\n\n" 727 "kljsafd\n\n\n\n\nkljsdf\n\n\n\n\nkjsdf" 728 "\n\n\n\n\n\")">>), 729 730 %% fun-info is skipped when everything is to fit on one single line 731 Fun1 = {'fun',1,{function,t,0},{0,45353021,'-t/0-fun-0-'}}, 732 "fun t/0" = flat_expr(Fun1), 733 Fun2 = {'fun',2,{clauses,[{clause,2,[],[],[{atom,3,true}]}]}, 734 {0,108059557,'-t/0-fun-0-'}}, 735 "fun() -> true end" = flat_expr(Fun2), 736 Fun3 = {named_fun,3,'True',[{clause,3,[],[],[{atom,3,true}]}], 737 {0,424242424,'-t/0-True-0-'}}, 738 "fun True() -> true end" = flat_expr(Fun3), 739 740 ok. 741 742 743%% OTP_6321. Bug fix of exprs(). 744otp_6321(Config) when is_list(Config) -> 745 Str = "S = hopp, {hej, S}. ", 746 {done, {ok, Tokens, _EndLine}, ""} = erl_scan:tokens("", Str, _L=1), 747 {ok, Exprs} = erl_parse:parse_exprs(Tokens), 748 "S = hopp, {hej, S}" = lists:flatten(erl_pp:exprs(Exprs)), 749 ok. 750 751%% OTP_6911. More newlines. 752otp_6911(Config) when is_list(Config) -> 753 F = {function,5,thomas,1, 754 [{clause,5, 755 [{var,5,'X'}], 756 [], 757 [{'case',6, 758 {var,6,'X'}, 759 [{clause,7,[{atom,7,true}],[],[{integer,7,12}]}, 760 {clause,8,[{atom,8,false}],[],[{integer,8,14}]}]}]}]}, 761 Chars = flat_form(F), 762 "thomas(X) ->\n" 763 " case X of\n" 764 " true ->\n" 765 " 12;\n" 766 " false ->\n" 767 " 14\n" 768 " end.\n" = Chars, 769 ok = pp_expr(<<"case X of true -> 12; false -> 14 end">>), 770 ok = pp_expr(<<"receive after 1 -> ok end">>), 771 ok. 772 773%% OTP_6914. Binary comprehensions. 774otp_6914(Config) when is_list(Config) -> 775 ok = pp_expr(<<"<< <<B:1>> || B <- [0,1,1] >>">>), 776 ok = pp_expr(<<"[ B || <<B:1>> <= <<\"hi\">>]">>), 777 ok = pp_expr(<<"<< <<1:1>> || true >>">>), 778 ok. 779 780%% OTP_8150. Types. 781otp_8150(Config) when is_list(Config) -> 782 _ = [{N,ok} = {N,pp_forms(B)} || 783 {N,B} <- type_examples() 784 ], 785 ok. 786 787%% OTP_8238. Bugfix 'E'. 788otp_8238(Config) when is_list(Config) -> 789 Ex = [<<"-record(rec1, {}).\n" 790 "-record(rec2, {a, b}).\n" 791 "-record(rec3, {f123, g, h}).\n">>, 792 <<"-type line() :: integer().\n">>, 793 <<"-type info_line() :: integer() | term().\n">>, 794 <<"-type column() :: pos_integer().\n">>, 795 [["\n", B] || {_,B} <- type_examples()], 796 <<"t1(T) ->\n" 797 " foo:bar(#rec1{}, #rec2{}),\n" 798 " T.\n" 799 "t2() ->\n" 800 " #r{}.\n">> 801 ], 802 compile(Config, [{otp_8238,iolist_to_binary(Ex)}]), 803 ok. 804 805type_examples() -> 806 [{ex1,<<"-type ann() :: Var :: integer(). ">>}, 807 {ex2,<<"-type ann2() :: Var :: " 808 "'return' | 'return_white_spaces' | 'return_comments'" 809 " | 'text' | ann(). ">>}, 810 {ex3,<<"-type paren() :: (ann2()). ">>}, 811 {ex4,<<"-type t1() :: atom(). ">>}, 812 {ex5,<<"-type t2() :: [t1()]. ">>}, 813 {ex56,<<"-type integer(A) :: A. ">>}, 814 {ex6,<<"-type t3(Atom) :: integer(Atom). ">>}, 815 {ex7,<<"-type '\\'t::4'() :: t3('\\'foobar'). ">>}, 816 {ex8,<<"-type t5() :: {t1(), t3(foo)}. ">>}, 817 {ex9,<<"-type t6() :: 1 | 2 | 3 | 'foo' | 'bar'. ">>}, 818 {ex10,<<"-type t7() :: []. ">>}, 819 {ex11,<<"-type t71() :: [_]. ">>}, 820 {ex12,<<"-type t8() :: {any(),none(),pid(),port()," 821 "reference(),float()}. ">>}, 822 {ex13,<<"-type t9() :: [1|2|3|foo|bar] | " 823 "list(a | b | c) | t71(). ">>}, 824 {ex14,<<"-type t10() :: {1|2|3|foo|t9()} | {}. ">>}, 825 {ex15,<<"-type t11() :: 1..2. ">>}, 826 {ex16,<<"-type t13() :: maybe_improper_list(integer(), t11()). ">>}, 827 {ex17,<<"-type t14() :: [erl_scan:foo() | " 828 "erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)]. ">>}, 829 {ex18,<<"-type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>," 830 "<<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>|" 831 "<<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>|" 832 "<<_:34>>|<<_:34>>|<<_:34>>]. ">>}, 833 {ex19,<<"-type t16() :: fun(). ">>}, 834 {ex20,<<"-type t17() :: fun((...) -> paren()). ">>}, 835 {ex21,<<"-type t18() :: fun(() -> t17() | t16()). ">>}, 836 {ex22,<<"-type t19() :: fun((t18()) -> t16()) |" 837 "fun((nonempty_maybe_improper_list('integer', any())|" 838 "1|2|3|a|b|<<_:3,_:_*14>>|integer()) ->" 839 "nonempty_maybe_improper_list('integer', any())|" 840 "1|2|3|a|b|<<_:3,_:_*14>>|integer()). ">>}, 841 {ex23,<<"-type t20() :: [t19(), ...]. ">>}, 842 {ex24,<<"-type t21() :: tuple(). ">>}, 843 {ex25,<<"-type t21(A) :: A. ">>}, 844 {ex26,<<"-type t22() :: t21(integer()). ">>}, 845 {ex27,<<"-type t23() :: #rec1{}. ">>}, 846 {ex28,<<"-type t24() :: #rec2{a :: t23(), b :: [atom()]}. ">>}, 847 {ex29,<<"-type t25() :: #rec3{f123 :: [t24() | " 848 "1|2|3|4|a|b|c|d| " 849 "nonempty_maybe_improper_list(integer, any())]}. ">>}, 850 {ex30,<<"-type t99() ::" 851 "{t2(),'\\'t::4'(),t5(),t6(),t7(),t8(),t10(),t14()," 852 "t15(),t20(),t21(), t22(),t25()}. ">>}, 853 %% Writing constraints as is_subtype(V, T) is not supported since 854 %% Erlang/OTP 19.0, but as long as the parser recognizes the 855 %% is_subtype(V, T) syntax, we need a few examples of the syntax. 856 {ex31,<<"-spec t1(FooBar :: t99()) -> t99();" 857 "(t2()) -> t2();" 858 "('\\'t::4'()) -> {'\\'t::4'(), B}" 859 " when is_subtype(B, '\\'t::4'());" 860 "(t23()) -> C when is_subtype(C, atom())," 861 " is_subtype(C, t14());" 862 "(t24()) -> D when is_subtype(D, atom())," 863 " is_subtype(D, t14())," 864 " is_subtype(D, '\\'t::4'()).">>}, 865 {ex32,<<"-spec erl_pp_test:t2() -> any(). ">>}, 866 {ex33,<<"-opaque attributes_data() :: " 867 "[{'column', column()} | {'line', info_line()} |" 868 " {'text', string()}] | {line(),column()}. ">>}, 869 {ex34,<<"-record(r,{" 870 "f1 :: attributes_data()," 871 "f222 = foo:bar(34, #rec3{}, 234234234423, " 872 " aassdsfsdfsdf, 2234242323) :: " 873 " [t24() | 1|2|3|4|a|b|c|d| " 874 " nonempty_maybe_improper_list(integer, any())]," 875 "f333 :: [t24() | 1|2|3|4|a|b|c|d| " 876 " nonempty_maybe_improper_list(integer, any())]," 877 "f3 = x:y()," 878 "f4 = x:z() :: t99()," 879 "f17 :: 'undefined'," 880 "f18 :: 1 | 2 | 'undefined'," 881 "f19 = 3 :: integer()|undefined," 882 "f5 = 3 :: undefined|integer()}). ">>}]. 883 884%% OTP_8473. Bugfix abstract type 'fun'. 885otp_8473(Config) when is_list(Config) -> 886 Ex = [{ex1,<<"-type 'fun'(A) :: A.\n" 887 "-type funkar() :: 'fun'(fun((integer()) -> atom())).\n">>}], 888 _ = [{N,ok} = {N,pp_forms(B)} || 889 {N,B} <- Ex], 890 ok. 891 892%% OTP_8522. Avoid duplicated 'undefined' in record field types. 893otp_8522(Config) when is_list(Config) -> 894 FileName = filename('otp_8522.erl', Config), 895 C = <<"-module(otp_8522).\n" 896 "-record(r, {f1 :: undefined,\n" 897 " f2 :: A :: undefined,\n" 898 " f3 :: (undefined),\n" 899 " f4 :: x | y | undefined | z,\n" 900 " f5 :: a}).\n">>, 901 ok = file:write_file(FileName, C), 902 {ok, _} = compile:file(FileName, [{outdir,?privdir},debug_info]), 903 BF = filename("otp_8522", Config), 904 {ok, A} = beam_lib:chunks(BF, [abstract_code]), 905 %% OTP-12719: Since 'undefined' is no longer added by the Erlang 906 %% Parser, the number of 'undefined' is 4. It used to be 5. 907 4 = count_atom(A, undefined), 908 ok. 909 910count_atom(A, A) -> 911 1; 912count_atom(T, A) when is_tuple(T) -> 913 count_atom(tuple_to_list(T), A); 914count_atom(L, A) when is_list(L) -> 915 lists:sum([count_atom(T, A) || T <- L]); 916count_atom(_, _) -> 917 0. 918 919maps_syntax(Config) when is_list(Config) -> 920 Ts = [{map_fun_1, 921 <<"t() ->\n" 922 " M0 = #{ 1 => hi, hi => 42, 1.0 => {hi,world}},\n" 923 " M1 = M0#{ 1 := hello, new_val => 1337 },\n" 924 " map_fun_2:val(M1).\n">>}, 925 {map_fun_2, 926 <<"val(#{ 1 := V1, hi := V2, new_val := V3}) -> {V1,V2,V3}.\n">>}], 927 compile(Config, Ts), 928 929 ok = pp_expr(<<"#{}">>), 930 ok = pp_expr(<<"#{ a => 1, <<\"hi\">> => \"world\", 33 => 1.0 }">>), 931 ok = pp_expr(<<"#{ a := V1, <<\"hi\">> := V2 } = M">>), 932 ok = pp_expr(<<"M#{ a => V1, <<\"hi\">> := V2 }">>), 933 F = <<"-module(maps_type_syntax).\n" 934 "-compile(export_all).\n" 935 "-type t1() :: map().\n" 936 "-type t2() :: #{ atom() => integer(), atom() => float() }.\n" 937 "-type t3() :: #{ atom() := integer(), atom() := float() }.\n" 938 "-type u() :: #{a => (I :: integer()) | (A :: atom()),\n" 939 " (X :: atom()) | (Y :: atom()) =>\n" 940 " (I :: integer()) | (A :: atom())}.\n" 941 "-spec f1(t1()) -> 'true'.\n" 942 "f1(M) when is_map(M) -> true.\n" 943 "-spec f2(t2()) -> integer().\n" 944 "f2(#{a := V1,b := V2}) -> V1 + V2.\n" 945 "\n">>, 946 ok = pp_forms(F), 947 ok. 948 949quoted_atom_types(Config) when is_list(Config) -> 950 Q = [{quote_singleton_atom_types, true}], 951 U = [{encoding,unicode}], 952 L = [{encoding,latin1}], 953 F = "-type t() :: a | a().", 954 "-type t() :: 'a' | a().\n" = 955 lists:flatten(parse_and_pp_forms(F, Q ++ L)), 956 "-type t() :: 'a' | a().\n" = 957 lists:flatten(parse_and_pp_forms(F, Q ++ U)), 958 UF = "-type t() :: '\x{400}' | '\x{400}'().", 959 "-type t() :: '\\x{400}' | '\\x{400}'().\n" = 960 lists:flatten(parse_and_pp_forms(UF, Q ++ L)), 961 "-type t() :: '\x{400}' | '\x{400}'().\n" = 962 lists:flatten(parse_and_pp_forms(UF, Q ++ U)), 963 ok. 964 965%% OTP_8567. Avoid duplicated 'undefined' in record field types. 966otp_8567(Config) when is_list(Config) -> 967 FileName = filename('otp_8567.erl', Config), 968 C = <<"-module otp_8567.\n" 969 "-compile export_all.\n" 970 "-spec(a).\n" 971 "-record r, {a}.\n" 972 "-record s, {a :: integer()}.\n" 973 "-type t() :: {#r{},#s{}}.\n">>, 974 ok = file:write_file(FileName, C), 975 {error,[{_,[{3,erl_parse,["syntax error before: ","')'"]}]}],_} = 976 compile:file(FileName, [return]), 977 978 F = <<"-module(otp_8567).\n" 979 "-compile(export_all).\n" 980 "-record(t, {a}).\n" 981 "-record(u, {a :: integer()}).\n" 982 "-opaque ot() :: {#t{}, #u{}}.\n" 983 "-opaque(ot1() :: atom()).\n" 984 "-type a() :: integer().\n" 985 "-spec t() -> a().\n" 986 "t() ->\n" 987 " 3.\n" 988 "\n" 989 "-spec(t2 (ot()) -> ot1()).\n" 990 "t2(A) ->\n" 991 " A.\n" 992 "\n" 993 "-spec(otp_8567:t4 (ot()) -> ot1()).\n" 994 "t4(A) ->\n" 995 " A.\n">>, 996 ok = pp_forms(F), 997 998 ok. 999 1000%% OTP_8664. Types with integer expressions. 1001otp_8664(Config) when is_list(Config) -> 1002 FileName = filename('otp_8664.erl', Config), 1003 C1 = <<"-module(otp_8664).\n" 1004 "-export([t/0]).\n" 1005 "-define(A, -3).\n" 1006 "-define(B, (?A*(-1 band (((2)))))).\n" 1007 "-type t1() :: ?B | ?A.\n" 1008 "-type t2() :: ?B-1 .. -?B.\n" 1009 "-type t3() :: 9 band (8 - 3) | 1+2 | 5 band 3.\n" 1010 "-type b1() :: <<_:_*(3-(-1))>>\n" 1011 " | <<_:(-(?B))>>\n" 1012 " | <<_:4>>.\n" 1013 "-type u() :: 1 .. 2 | 3.. 4 | (8-3) ..6 | 5+0..6.\n" 1014 "-type t() :: t1() | t2() | t3() | b1() | u().\n" 1015 "-spec t() -> t().\n" 1016 "t() -> 3.\n">>, 1017 ok = file:write_file(FileName, C1), 1018 {ok, _, []} = compile:file(FileName, [return]), 1019 1020 C2 = <<"-module(otp_8664).\n" 1021 "-export([t/0]).\n" 1022 "-spec t() -> 9 and 4.\n" 1023 "t() -> 0.\n">>, 1024 ok = file:write_file(FileName, C2), 1025 {error,[{_,[{3,erl_lint,{type_syntax,integer}}]}],_} = 1026 compile:file(FileName, [return]), 1027 1028 ok. 1029 1030%% OTP-9147. Create well-formed types when adding 'undefined'. 1031otp_9147(Config) when is_list(Config) -> 1032 FileName = filename('otp_9147.erl', Config), 1033 C1 = <<"-module(otp_9147).\n" 1034 "-export_type([undef/0]).\n" 1035 "-record(undef, {f1 :: F1 :: a | b}).\n" 1036 "-type undef() :: #undef{}.\n">>, 1037 ok = file:write_file(FileName, C1), 1038 {ok, _, []} = 1039 compile:file(FileName, [return,'P',{outdir,?privdir}]), 1040 PFileName = filename('otp_9147.P', Config), 1041 {ok, Bin} = file:read_file(PFileName), 1042 %% The parentheses around "F1 :: a | b" are new (bugfix). 1043 true = 1044 lists:member("-record(undef,{f1 :: F1 :: a | b}).", 1045 string:tokens(binary_to_list(Bin), "\n")), 1046 ok. 1047 1048%% OTP-10302. Unicode characters scanner/parser. 1049otp_10302(Config) when is_list(Config) -> 1050 Ts = [{uni_1, 1051 <<"t() -> <<(<<\"abc\\x{aaa}\">>):3/binary>>.">>} 1052 ], 1053 compile(Config, Ts), 1054 ok = pp_expr(<<"$\\x{aaa}">>), 1055 ok = pp_expr(<<"\"1\\x{aaa}\"">>), 1056 ok = pp_expr(<<"<<<<\"hej\">>/binary>>">>), 1057 ok = pp_expr(<<"<< <<\"1\\x{aaa}\">>/binary>>">>), 1058 1059 U = [{encoding,unicode}], 1060 1061 do_hook(fun(H) -> [{hook,H}] end), 1062 do_hook(fun(H) -> [{hook,H}]++U end), 1063 1064 ok = pp_expr(<<"$\\x{aaa}">>, [{hook,fun hook/4}]), 1065 1066 Opts = [{hook, fun unicode_hook/4},{encoding,unicode}], 1067 Lc = parse_expr("[X || X <- [\"\x{400}\",\"\xFF\"]]."), 1068 A0 = erl_anno:new(0), 1069 Expr = {call,A0,{atom,A0,fff},[{foo,{foo,Lc}},{foo,{foo,Lc}}]}, 1070 EChars = lists:flatten(erl_pp:expr(Expr, 0, Opts)), 1071 Call = {call,A0,{atom,A0,foo},[{call,A0,{atom,A0,foo},[Lc]}]}, 1072 Expr2 = {call,A0,{atom,A0,fff},[Call,Call]}, 1073 EChars2 = erl_pp:exprs([Expr2], U), 1074 EChars = lists:flatten(EChars2), 1075 [$\x{400},$\x{400}] = [C || C <- EChars, C > 255], 1076 1077 ok = pp_forms(<<"function() -> {\"\x{400}\",$\x{400}}. "/utf8>>, U), 1078 ok = pp_forms("function() -> {\"\x{400}\",$\x{400}}. ", []), 1079 ok. 1080 1081unicode_hook({foo,E}, I, P, H) -> 1082 A = erl_anno:new(0), 1083 erl_pp:expr({call,A,{atom,A,foo},[E]}, I, P, H). 1084 1085%% OTP-10820. Unicode filenames. 1086otp_10820(Config) when is_list(Config) -> 1087 C1 = <<"%% coding: utf-8\n -module(any).">>, 1088 ok = do_otp_10820(Config, C1, "+pc latin1"), 1089 ok = do_otp_10820(Config, C1, "+pc unicode"), 1090 C2 = <<"%% coding: latin-1\n -module(any).">>, 1091 ok = do_otp_10820(Config, C2, "+pc latin1"), 1092 ok = do_otp_10820(Config, C2, "+pc unicode"). 1093 1094do_otp_10820(Config, C, PC) -> 1095 {ok,Node} = start_node(erl_pp_helper, "+fnu " ++ PC), 1096 L = [915,953,959,973,957,953,954,959,957,964], 1097 FileName = filename(L++".erl", Config), 1098 ok = rpc:call(Node, file, write_file, [FileName, C]), 1099 {ok, _, []} = rpc:call(Node, compile, file, 1100 [FileName, [return,'P',{outdir,?privdir}]]), 1101 PFileName = filename(L++".P", Config), 1102 {ok, Bin} = rpc:call(Node, file, read_file, [PFileName]), 1103 true = test_server:stop_node(Node), 1104 true = file_attr_is_string(binary_to_list(Bin)), 1105 ok. 1106 1107file_attr_is_string("-file(\"" ++ _) -> true; 1108file_attr_is_string([_ | L]) -> 1109 file_attr_is_string(L). 1110 1111%% OTP-11100. Fix printing of invalid forms. 1112otp_11100(Config) when is_list(Config) -> 1113 %% There are a few places where the added code ("options(none)") 1114 %% doesn't make a difference (pp:bit_elem_type/1 is an example). 1115 1116 A1 = erl_anno:new(1), 1117 "-type foo() :: integer(INVALID-FORM:{foo,bar}:).\n" = 1118 pf({attribute,A1,type,{foo,{type,A1,integer,[{foo,bar}]},[]}}), 1119 pf({attribute,A1,type, 1120 {a,{type,A1,range,[{integer,A1,1},{foo,bar}]},[]}}), 1121 "-type foo(INVALID-FORM:{foo,bar}:) :: A.\n" = 1122 pf({attribute,A1,type,{foo,{var,A1,'A'},[{foo,bar}]}}), 1123 "-type foo() :: INVALID-FORM:{foo,bar}: :: [].\n" = 1124 pf({attribute,A1,type, 1125 {foo,{paren_type,A1, 1126 [{ann_type,A1,[{foo,bar},{type,A1,nil,[]}]}]}, 1127 []}}), 1128 "-type foo() :: <<_:INVALID-FORM:{foo,bar}:>>.\n" = 1129 pf({attribute,A1,type, 1130 {foo,{type,A1,binary,[{foo,bar},{integer,A1,0}]},[]}}), 1131 "-type foo() :: <<_:10, _:_*INVALID-FORM:{foo,bar}:>>.\n" = 1132 pf({attribute,A1,type, 1133 {foo,{type,A1,binary,[{integer,A1,10},{foo,bar}]},[]}}), 1134 "-type foo() :: #r{INVALID-FORM:{foo,bar}: :: integer()}.\n" = 1135 pf({attribute,A1,type, 1136 {foo,{type,A1,record, 1137 [{atom,A1,r}, 1138 {type,A1,field_type, 1139 [{foo,bar},{type,A1,integer,[]}]}]}, 1140 []}}), 1141 ok. 1142 1143%% OTP-11861. behaviour_info() and -callback. 1144otp_11861(Config) when is_list(Config) -> 1145 A3 = erl_anno:new(3), 1146 "-optional_callbacks([bar/0]).\n" = 1147 pf({attribute,A3,optional_callbacks,[{bar,0}]}), 1148 "-optional_callbacks([{bar, 1, bad}]).\n" = 1149 pf({attribute,A3,optional_callbacks,[{bar,1,bad}]}), 1150 ok. 1151 1152pf(Form) -> 1153 lists:flatten(erl_pp:form(Form, none)). 1154 1155pr_1014(Config) -> 1156 ok = pp_forms(<<"-type t() :: #{_ => _}. ">>), 1157 ok = pp_forms(<<"-type t() :: #{any() => _}. ">>), 1158 ok = pp_forms(<<"-type t() :: #{_ => any()}. ">>), 1159 ok = pp_forms(<<"-type t() :: #{any() => any()}. ">>), 1160 ok = pp_forms(<<"-type t() :: #{atom() := integer(), any() => any()}. ">>), 1161 1162 FileName = filename('pr_1014.erl', Config), 1163 C = <<"-module pr_1014.\n" 1164 "-compile export_all.\n" 1165 "-type m() :: #{..., a := integer()}.\n">>, 1166 ok = file:write_file(FileName, C), 1167 {error,[{_,[{3,erl_parse,["syntax error before: ","'...'"]}]}],_} = 1168 compile:file(FileName, [return]), 1169 1170 ok. 1171 1172otp_13662(Config) -> 1173 Include = "abcdefghijabcdefghijabcdefghijabcdefghijabcde" 1174 "fghij-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.hrl", 1175 IncludeFile = filename(Include, Config), 1176 ok = file:write_file(IncludeFile, <<>>), 1177 Ts = [{otp_13662, 1178 <<"-file(\"abcdefghijabcdefghijabcdefghijabcdefghijabcde\"\n 1179 \"fghij-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.erl\", 0).\n 1180 -include(\"abcdefghijabcdefghijabcdefghijabcdefghijabcde\" 1181 \"fghij-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.hrl\").\n 1182 -include_lib(\"abcdefghijabcdefghijabcdefghijabcdefghijabcde\" 1183 \"fghij-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.hrl\"). 1184 -compile(export_all).\n 1185 t() ->\n 1186 \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n 1187 \"aaaaaaaaaaaaaaaaaaaaaa\".\n">>} 1188 ], 1189 compile(Config, Ts). 1190 1191otp_14285(_Config) -> 1192 pp_forms(<<"-export([t/0, '\\x{400}\\''/0]).">>), 1193 pp_forms(<<"-import(lists, [append/2]).">>), 1194 pp_forms(<<"-optional_callbacks([]).">>), 1195 pp_forms(<<"-optional_callbacks(['\\x{400}\\''/1]).">>), 1196 pp_forms(<<"-'\\x{400}\\''('\\x{400}\\'').">>), 1197 pp_forms(<<"-type '\\x{400}\\''() :: '\\x{400}\\''.">>), 1198 pp_forms(<<"-record('\\x{400}\\'', {'\\x{400}\\''}).">>), 1199 pp_forms(<<"-callback '\\x{400}\\''(_) -> '\\x{400}\\''.">>), 1200 pp_forms(<<"t() -> '\\x{400}\\''('\\x{400}\\'').">>), 1201 pp_forms(<<"'\\x{400}\\''(_) -> '\\x{400}\\''.">>), 1202 pp_forms(<<"-spec '\\x{400}'() -> " 1203 "#'\\x{400}'{'\\x{400}' :: '\\x{400}'}.">>), 1204 pp_forms(<<"'\\x{400}\\''() ->" 1205 "R = #'\\x{400}\\''{}," 1206 "#'\\x{400}\\''{'\\x{400}\\'' =" 1207 "{'\\x{400}\\''," 1208 "fun '\\x{400}\\''/0," 1209 "R#'\\x{400}\\''.'\\x{400}\\''," 1210 "#'\\x{400}\\''.'\\x{400}\\''}}.">>), 1211 1212 %% Special... 1213 true = 1214 "{some,'\\x{400}\\''}" =:= 1215 lists:flatten(erl_pp:expr({value,erl_anno:new(0),{some,'\x{400}\''}}, 1216 [{encoding,latin1}])), 1217 ok. 1218 1219otp_15592(_Config) -> 1220 ok = pp_expr(<<"long12345678901234567890123456789012345678901234" 1221 "56789012345678901234:f(<<>>)">>), 1222 ok. 1223 1224otp_15751(_Config) -> 1225 Check = fun(L) -> 1226 ok = pp_expr(L), 1227 remove_indentation(flat_parse_and_pp_expr(L, 0, [])) 1228 end, 1229 "try foo:bar() catch Reason:Stacktrace -> {Reason, Stacktrace} end" = 1230 Check("try foo:bar() 1231 catch Reason:Stacktrace -> {Reason, Stacktrace} end"), 1232 1233 "try foo:bar() catch throw:Reason:Stacktrace -> {Reason, Stacktrace} end" = 1234 Check("try foo:bar() 1235 catch throw:Reason:Stacktrace -> {Reason, Stacktrace} end"), 1236 1237 "try foo:bar() catch Reason:_ -> Reason end" = 1238 Check("try foo:bar() 1239 catch Reason:_ -> Reason end"), 1240 1241 "try foo:bar() catch throw:Reason -> Reason end" = % ":_" removed 1242 Check("try foo:bar() 1243 catch throw:Reason:_-> Reason end"), 1244 1245 "try foo:bar() catch throw:Reason -> Reason end" = % "throw:" added 1246 Check("try foo:bar() 1247 catch Reason -> Reason end"), 1248 1249 "try foo:bar() catch throw:Reason -> Reason end" = 1250 Check("try foo:bar() 1251 catch throw:Reason -> Reason end"), 1252 1253 ok. 1254 1255otp_15755(_Config) -> 1256 "[{a, b}, c, {d, e} | t]" = 1257 flat_parse_and_pp_expr("[{a, b}, c, {d, e} | t]", 0, []), 1258 "[{a, b},\n c, d,\n {d, e},\n 1, 2.0,\n {d, e},\n <<>>, {},\n {d, e},\n" 1259 " [], [],\n {d, e} |\n t]" = 1260 flat_parse_and_pp_expr("[{a,b},c,d,{d,e},1,2.0,{d,e},<<>>," 1261 "{},{d,e},[],[],{d,e}|t]", 0, []), 1262 "[{a, b},\n c, d,\n {d, e},\n 1, 2.0,\n {d, e},\n <<>>, {},\n {d, e},\n" 1263 " [], [], d, e | t]" = 1264 flat_parse_and_pp_expr("[{a,b},c,d,{d,e},1,2.0,{d,e},<<>>," 1265 "{},{d,e},[],[],d,e|t]", 0, []), 1266 1267 "-type t() :: 1268 a | b | c | a | b | a | b | a | b | a | b | a | b | a | b | 1269 a | b | a | b | a | b.\n" = 1270 lists:flatten(parse_and_pp_forms( 1271 "-type t() :: a | b | c| a | b | a | b | a | b | a |" 1272 " b | a | b | a | b | a | b | a | b |a | b.", [])), 1273 1274 "-type t() :: 1275 {dict, 0, 16, 16, 8, 80, 48, 1276 {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], 1277 []}, 1278 {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], []}}}.\n" = 1279 lists:flatten(parse_and_pp_forms( 1280 "-type t() :: {dict,0,16,16,8,80,48," 1281 "{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}," 1282 "{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}.", [])), 1283 1284 "-type t() :: 1285 {{a}, 1286 0, 16, 1287 {16}, 1288 8, 80, 48, a, b, e, f, 'sf s sdf', [], {}, 1289 {[]}}.\n" = 1290 lists:flatten(parse_and_pp_forms( 1291 "-type t() :: {{a}, 0, 16, {16}, 8, 80, 48, a, b, e, f," 1292 " 'sf s sdf', [], {}, {[]}}.", [])), 1293 ok. 1294 1295otp_16435(_Config) -> 1296 CheckF = fun(S) -> S = lists:flatten(parse_and_pp_forms(S, [])) end, 1297 CheckF("-type t() :: A :: integer().\n"), 1298 CheckF("-type t() :: A :: (B :: integer()).\n"), 1299 CheckF("-type t() :: {A :: (B :: integer())}.\n"), 1300 CheckF("-record(r,{f :: {A :: (B :: integer())}}).\n"), 1301 CheckF("-record(r,{f = 3 :: {A :: (B :: integer())}}).\n"), 1302 CheckF("-type t() :: #r{f :: A :: (B :: integer())}.\n"), 1303 CheckF("-spec t(X) -> X when X :: Y :: (Z :: #r{}).\n"), 1304 1305 CheckF("f() ->\n << \n (catch <<1:4>>) ||\n" 1306 " A <- []\n >>.\n"), 1307 CheckF("f() ->\n [ \n (catch foo) ||\n A <- []\n ].\n"), 1308 CheckF("f() when erlang:float(3.0) ->\n true.\n"), 1309 1310 Check = fun(S) -> S = flat_parse_and_pp_expr(S, 0, []) end, 1311 Check("5 #r4.f1"), 1312 Check("17 #{[] => true}"), 1313 Check("0 #r1{f2 = foo}"), 1314 Check("fun foo:bar/17 #{}"), 1315 Check("fun a/2 #{}"), 1316 1317 Check("try foo:bar() of\n" 1318 " a ->\n" 1319 " b\n" 1320 "after\n" 1321 " d\n" 1322 "end"), 1323 1324 Check("try foo:bar() of\n" 1325 " a ->\n" 1326 " b\n" 1327 "catch\n" 1328 " _:_ ->\n" 1329 " c\n" 1330 "end"), 1331 1332 ok. 1333 1334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1335 1336compile(Config, Tests) -> 1337 F = fun({N,P}, BadL) -> 1338 case catch compile_file(Config, P) of 1339 ok -> 1340 case pp_forms(P) of 1341 ok -> 1342 BadL; 1343 not_ok -> 1344 io:format("~nTest ~p failed.~n", [N]), 1345 fail() 1346 end; 1347 Bad -> 1348 io:format("~nTest ~p failed. got~n ~p~n", 1349 [N, Bad]), 1350 fail() 1351 end 1352 end, 1353 lists:foldl(F, [], Tests). 1354 1355compile_file(Config, Test0) -> 1356 Test = ["-module(erl_pp_test).\n", 1357 "-compile(export_all).\n", 1358 Test0], 1359 case compile_file(Config, Test, ['E']) of 1360 {ok, RootFile} -> 1361 File = RootFile ++ ".E", 1362 {ok, Bin0} = file:read_file(File), 1363 %% A very simple check: just try to compile the output. 1364 case compile_file(Config, Bin0, []) of 1365 {ok, RootFile2} -> 1366 File2 = RootFile2 ++ ".E", 1367 {ok, Bin1} = file:read_file(File2), 1368 case Bin0 =:= Bin1 of 1369 true -> 1370 test_max_line(binary_to_list(Bin0)); 1371 false -> 1372 {error, file_contents_modified, {Bin0, Bin1}} 1373 end; 1374 Error -> 1375 {error, could_not_compile_E_file, Error} 1376 end; 1377 Error -> 1378 Error 1379 end. 1380 1381compile_file(Config, Test, Opts0) -> 1382 FileName = filename('erl_pp_test.erl', Config), 1383 Opts = [export_all,return,nowarn_unused_record,{outdir,?privdir} | Opts0], 1384 ok = file:write_file(FileName, Test), 1385 case compile:file(FileName, Opts) of 1386 {ok, _M, _Ws} -> 1387 {ok, filename:rootname(FileName)}; 1388 Error -> Error 1389 end. 1390 1391flat_expr1(Expr0) -> 1392 Expr = erl_parse:new_anno(Expr0), 1393 lists:flatten(erl_pp:expr(Expr)). 1394 1395flat_expr(Expr0) -> 1396 Expr = erl_parse:new_anno(Expr0), 1397 lists:flatten(erl_pp:expr(Expr, -1, none)). 1398 1399flat_form(Form0) -> 1400 Form = erl_parse:new_anno(Form0), 1401 lists:flatten(erl_pp:form(Form)). 1402 1403pp_forms(Bin) -> 1404 pp_forms(Bin, none). 1405 1406pp_forms(Bin, Options) when is_binary(Bin) -> 1407 pp_forms(to_list(Bin, Options), Options); 1408pp_forms(List, Options) when is_list(List) -> 1409 PP1 = (catch parse_and_pp_forms(List, Options)), 1410 PP2 = (catch parse_and_pp_forms(PP1, Options)), 1411 case PP1 =:= PP2 of % same line numbers 1412 true -> 1413 test_max_line(PP1); 1414 false -> 1415 not_ok 1416 end. 1417 1418parse_and_pp_forms(String, Options) -> 1419 lists:append(lists:map(fun(AF) -> erl_pp:form(AF, Options) 1420 end, parse_forms(String))). 1421 1422parse_forms(Chars) -> 1423 String = lists:flatten(Chars), 1424 parse_forms2(String, [], 1, []). 1425 1426parse_forms2([], _Cont, _Line, Forms) -> 1427 lists:reverse(Forms); 1428parse_forms2(String, Cont0, Line, Forms) -> 1429 case erl_scan:tokens(Cont0, String, Line) of 1430 {done, {ok, Tokens, EndLine}, Chars} -> 1431 {ok, Form} = erl_parse:parse_form(Tokens), 1432 parse_forms2(Chars, [], EndLine, [Form | Forms]); 1433 {more, Cont} when element(4, Cont) =:= [] -> 1434 %% extra spaces after forms... 1435 parse_forms2([], Cont, Line, Forms); 1436 {more, Cont} -> 1437 %% final dot needs a space... 1438 parse_forms2(" ", Cont, Line, Forms) 1439 end. 1440 1441pp_expr(Bin) -> 1442 pp_expr(Bin, none). 1443 1444%% Final dot is added. 1445pp_expr(Bin, Options) when is_binary(Bin) -> 1446 pp_expr(to_list(Bin, Options), Options); 1447pp_expr(List, Options) when is_list(List) -> 1448 PP1 = (catch parse_and_pp_expr(List, 0, Options)), 1449 PPneg = (catch parse_and_pp_expr(List, -1, Options)), 1450 PP2 = (catch parse_and_pp_expr(PPneg, 0, Options)), 1451 if 1452 PP1 =:= PP2 -> % same line numbers 1453 case 1454 (test_max_line(PP1) =:= ok) and (test_new_line(PPneg) =:= ok) 1455 of 1456 true -> 1457 ok; 1458 false -> 1459 not_ok 1460 end; 1461 true -> 1462 not_ok 1463 end. 1464 1465flat_parse_and_pp_expr(String, Indent, Options) -> 1466 lists:flatten(parse_and_pp_expr(String, Indent, Options)). 1467 1468parse_and_pp_expr(String, Indent, Options) -> 1469 StringDot = lists:flatten(String) ++ ".", 1470 erl_pp:expr(parse_expr(StringDot), Indent, Options). 1471 1472parse_expr(Chars) -> 1473 {ok, Tokens, _} = erl_scan:string(Chars, 1), 1474 {ok, [Expr]} = erl_parse:parse_exprs(Tokens), 1475 Expr. 1476 1477to_list(Bin, Options) when is_list(Options) -> 1478 case proplists:get_value(encoding, Options) of 1479 unicode -> unicode:characters_to_list(Bin); 1480 encoding -> binary_to_list(Bin); 1481 undefined -> binary_to_list(Bin) 1482 end; 1483to_list(Bin, _Hook) -> 1484 binary_to_list(Bin). 1485 1486test_new_line(String) -> 1487 case string:chr(String, $\n) of 1488 0 -> ok; 1489 _ -> not_ok 1490 end. 1491 1492test_max_line(String) -> 1493 case max_line(String) of 1494 ML when ML > 100 -> 1495 {error, max_line_too_big, {ML,String}}; 1496 _ML -> 1497 ok 1498 end. 1499 1500max_line(String) -> 1501 lists:max([0 | [length(Sub) || 1502 Sub <- string:tokens(String, "\n"), 1503 string:substr(Sub, 1, 5) =/= "-file"]]). 1504 1505filename(Name, Config) when is_atom(Name) -> 1506 filename(atom_to_list(Name), Config); 1507filename(Name, Config) -> 1508 filename:join(?privdir, Name). 1509 1510fail() -> 1511 ct:fail(failed). 1512 1513%% +fnu means a peer node has to be started; slave will not do 1514start_node(Name, Xargs) -> 1515 PA = filename:dirname(code:which(?MODULE)), 1516 test_server:start_node(Name, peer, [{args, "-pa " ++ PA ++ " " ++ Xargs}]). 1517