Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split TypEr core out of typer module #5660

Merged
merged 1 commit into from
Mar 8, 2022
Merged

Conversation

elbrujohalcon
Copy link
Contributor

@elbrujohalcon elbrujohalcon commented Jan 28, 2022

We (@pablocostass, @maco, and myself) are building rebar3_typer and, as part of our efforts to integrate TypEr into rebar3, we found that typer (the module) didn't expose any interface that allowed us to use it from another Erlang module. That's why we decided to move some pieces of it into our project. In particular: we created typer_core with just the core functionality, detached from the parts that parse the command-line arguments and options.
Eventually, we came up with AdRoll/rebar3_typer#15, and we thought it would be great if we could contribute this back to OTP itself, so… for future versions of the plugin, we don't need to use our own version of typer and we can just use what comes with OTP.
As a bonus, we added a new mode: annotate_in_place. It's like annotate but it replaces the original files instead of creating new ones.

In our project, we also added several tests for typer_core. I didn't move them here since I don't know if they match the test structure of this project. If you want to check them out and let us know if you like them, we can migrate them here of course.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 28, 2022

CT Test Results

    2 files    33 suites   6m 19s ⏱️
395 tests 386 ✔️   9 💤 0
437 runs  427 ✔️ 10 💤 0

Results for commit 9a07746.

♻️ This comment has been updated with latest results.

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

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

Artifacts

// Erlang/OTP Github Action Bot

@rickard-green rickard-green added the team:VM Assigned to OTP team VM label Jan 31, 2022
@elbrujohalcon
Copy link
Contributor Author

Alternatively, since you said that you don't have time to fix TypEr bugs in #5653 and #5657… you might want to consider removing TypEr entirely from OTP and point people to our project/plugin if they ever come here looking for it.
Or maybe something in between, like having our repo moved to erlang/rebar3_typer or even erlang/typer (where we maintain both a shell tool - the current typer - and the rebar3 plugin).
If you decide to go with any of these options, we'll be extremely happy to help you with that.

Copy link
Contributor

@jhogberg jhogberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, I think this is a good idea.

Is there anything else you'd like to expose from typer_core? e.g. would you like to be able to return results rather than just have them written to files? It could perhaps be helpful for erlang-ls-like tools.

Alternatively, since you said that you don't have time to fix TypEr bugs in #5653 and #5657… you might want to consider removing TypEr entirely from OTP and point people to our project/plugin if they ever come here looking for it.

Sorry, I should've expressed myself better: I don't think we'll have the time to fix them before OTP 25, there's a lot of other things that need doing and it's hard to put these ahead as they've been broken as far back as the git history goes. We'd very much welcome PRs fixing them.

lib/dialyzer/src/typer_core.erl Show resolved Hide resolved
lib/dialyzer/src/typer.erl Outdated Show resolved Hide resolved
@@ -31,566 +34,41 @@
-define(SHOW, show).
-define(SHOW_EXPORTED, show_exported).
-define(ANNOTATE, annotate).
-define(ANNOTATE_IN_PLACE, annotate_in_place).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not too much trouble, can you split this addition into its own commit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey John! I went ahead and did pretty much the opposite. We decided to remove for now the annotate_in_place mode to make this PR easier to follow and more concise and so that we can try it more thoroughly with our rebar3 plugin.

We can add it back if you want to, but otherwise expect another PR to add it after this one has been done with :)

@jhogberg jhogberg added the waiting waiting for changes/input from author label Feb 2, 2022
@elbrujohalcon
Copy link
Contributor Author

Is there anything else you'd like to expose from typer_core? e.g. would you like to be able to return results rather than just have them written to files? It could perhaps be helpful for erlang-ls-like tools.

Not at this time, no. If somebody (like @robertoaloi) eventually needs something else, they can make that change themselves ;)

@bjorng
Copy link
Contributor

bjorng commented Mar 1, 2022

The pull request in its current state does not build. Please fix that. When you have done that, please squash commits. It seems to me that only one commit is needed, because the all the commits after the first only amends the first one.

@elbrujohalcon
Copy link
Contributor Author

Single commit and build fixed ✅

@elbrujohalcon elbrujohalcon force-pushed the typer_core branch 3 times, most recently from 5ad7e2b to 9da596b Compare March 1, 2022 10:38
We (@pablocostass, @maco, and myself) are building rebar3_typer and, as part of our efforts to integrate TypEr into rebar3, we found that typer (the module) didn't expose any interface that allowed us to use it from another Erlang module. That's why we decided to move some pieces of it into our project. In particular: we created typer_core with just the core functionality, detached from the parts that parse the command-line arguments and options.
Eventually, we came up with AdRoll/rebar3_typer#15, and we thought it would be great if we could contribute this back to OTP itself, so… for future versions of the plugin, we don't need to use our own version of typer and we can just use what comes with OTP.
As a bonus, we added a new mode: annotate_in_place. It's like annotate but it replaces the original files instead of creating new ones.

In our project, we also added several tests for typer_core. I didn't move them here since I don't know if they match the test structure of this project. If you want to check them out and let us know if you like them, we can migrate them here of course.
@elbrujohalcon
Copy link
Contributor Author

elbrujohalcon commented Mar 1, 2022

FWIW, since Github would not be very helpful when presenting the changes, here is the significant diff:

Diff between `typer.erl` and `typer_core.erl`
16c16
< %% File        : typer.erl
---
> %% File        : typer_core.erl
20c20,23
< %% Description : An Erlang/OTP application that shows type information
---
> %%               On 2022, Brujo, Pablo, and Mackenzie from NextRoll
> %%               started working on the rebar3 plugin for typer and,
> %%               with that in mind, splitted typer and typer_core apart.
> %% Description : An Erlang/OTP module that shows type information
25c28
< -module(typer).
---
> -module(typer_core).
27c30
< -export([start/0]).
---
> -export([run/1]).
43a47,66
> -type printer(Return) :: fun((io:format(), [term()]) -> Return).
> -type io() ::
>     #{debug := printer(_),
>       info := printer(_),
>       warn := printer(_),
>       abort := printer(no_return())}.
> -type opts() ::
>     #{mode := mode(),
>       show_succ => boolean(),
>       no_spec => boolean(),
>       edoc => boolean(),
>       plt => file:filename(),
>       trusted => files(),
>       files => files(),
>       files_r => files(),
>       macros => [{atom(), term()}],
>       includes => files(),
>       io => io()}.
> 
> -export_type([mode/0, opts/0]).
64c87,88
< 	 trust_plt  = dialyzer_plt:new() :: plt()}).
---
>          trust_plt  = dialyzer_plt:new() :: plt(),
>          io         = default_io()       :: io()}).
74c98
< -spec start() -> no_return().
---
> -spec run(opts()) -> ok.
76,81c100,102
< start() ->
<   _ = io:setopts(standard_error, [{encoding,unicode}]),
<   _ = io:setopts([{encoding,unicode}]),
<   {Args, Analysis} = process_cl_args(),
<   %% io:format("Args: ~p\n", [Args]),
<   %% io:format("Analysis: ~p\n", [Analysis]),
---
> run(Opts) ->
>   {Args, Analysis} = process_cl_args(Opts),
>   msg(debug, "Opts: ~p\nArgs: ~p\nAnalysis: ~p", [Opts, Args, Analysis], Analysis),
83c104
<   TrustedFiles = filter_fd(Args#args.trusted, [], fun is_erl_file/1),
---
>   TrustedFiles = filter_fd(Args#args.trusted, [], fun is_erl_file/1, Analysis),
85,86c106,107
<   All_Files = get_all_files(Args),
<   %% io:format("All_Files: ~tp\n", [All_Files]),
---
>   All_Files = get_all_files(Args, Analysis2),
>   msg(debug, "All_Files: ~tp", [All_Files], Analysis2),
89c110
<   %% io:format("Final: ~p\n", [Analysis4#analysis.fms]),
---
>   msg(debug, "Final: ~p", [Analysis4#analysis.fms], Analysis3),
93,94c114,115
<   %% io:format("\nTyper analysis finished\n"),
<   erlang:halt(0).
---
>   msg(debug, "\nTyper analysis finished", [], Analysis4),
>   ok.
103c124
<   %% io:format("--- Extracting trusted typer_info... "),
---
>   msg(debug, "Extracting trusted typer info...", [], Analysis),
123c144
< 		  {error, Reason} -> compile_error([Reason])
---
>                   {error, Reason} -> compile_error([Reason], Analysis)
125c146
< 	      {error, Reason} -> compile_error([Reason])
---
>               {error, Reason} -> compile_error([Reason], Analysis)
127c148
< 	  {error, Reason} -> compile_error(Reason)
---
>           {error, Reason} -> compile_error(Reason, Analysis)
144c165
< 	compile_error(ErrorMsg)
---
>         compile_error(ErrorMsg, Analysis)
159,160c180,181
<   StrippedCallGraph = remove_external(CallGraph, TrustPLT),
<   %% io:format("--- Analyzing callgraph... "),
---
>   StrippedCallGraph = remove_external(CallGraph, TrustPLT, Analysis),
>   msg(debug, "Analyizing callgraph...", [], Analysis),
170c191
< 				[{What, Stacktrace}]));
---
>                                 [{What, Stacktrace}]), Analysis);
172c193
<       fatal_error(io_lib:format("Analysis failed with message: ~ts", [Msg]))
---
>       fatal_error(io_lib:format("Analysis failed with message: ~ts", [Msg]), Analysis)
175c196
< -spec remove_external(callgraph(), plt()) -> callgraph().
---
> -spec remove_external(callgraph(), plt(), analysis()) -> callgraph().
177c198
< remove_external(CallGraph, PLT) ->
---
> remove_external(CallGraph, PLT, Analysis) ->
182c203
<       msg(io_lib:format(" Unknown functions: ~tp\n", [lists:usort(Externals)])),
---
>       msg(warn, " Unknown functions: ~tp", [lists:usort(Externals)], Analysis),
186c207
<         _ -> msg(io_lib:format(" Unknown types: ~tp\n", [ExtTypes]))
---
>         _ -> msg(warn, " Unknown types: ~tp", [ExtTypes], Analysis)
232c253
< 		write_typed_file(File, Info)
---
>                 write_typed_file(File, Info, Analysis)
237c258
<       write_inc_files(IncInfo)
---
>       write_inc_files(IncInfo, Analysis)
243c264
< 	    write_typed_file(File, Info),
---
>             write_typed_file(File, Info, Analysis),
245c266
< 	    collect_imported_functions(IncFuns, Info#info.types, Inc)
---
>             collect_imported_functions(IncFuns, Info#info.types, Inc, Analysis)
250c271
< write_inc_files(Inc) ->
---
> write_inc_files(Inc, Analysis) ->
262,265c283,286
< 	%% io:format("Types ~tp\n", [Info#info.types]),
< 	%% io:format("Functions ~tp\n", [Info#info.functions]),
< 	%% io:format("Records ~tp\n", [Info#info.records]),
< 	write_typed_file(File, Info)
---
>         msg(debug, "Types ~tp", [Info#info.types], Analysis),
>         msg(debug, "Functions ~tp", [Info#info.functions], Analysis),
>         msg(debug, "Records ~tp", [Info#info.records], Analysis),
>         write_typed_file(File, Info, Analysis)
272c293
< 	    show_type_info(File, Info)
---
>             show_type_info(File, Info, Analysis)
283c304
< collect_imported_functions(Functions, Types, Inc) ->
---
> collect_imported_functions(Functions, Types, Inc, Analysis) ->
293c314
< 		check_imported_functions(Obj, NewI, Types)
---
>                 check_imported_functions(Obj, NewI, Types, Analysis)
322c343
< check_imported_functions({File, {Line, F, A}}, Inc, Types) ->
---
> check_imported_functions({File, {Line, F, A}}, Inc, Types, Analysis) ->
325c346
<   Type = get_type_info(FA, Types),
---
>   Type = get_type_info(FA, Types, Analysis),
343c364
< 	  inc_warning(FA, File),
---
>           inc_warning(FA, File, Analysis),
353,355c374,379
< inc_warning({F, A}, File) ->
<   io:format("      ***Warning: Skip function ~tp/~p ", [F, A]),
<   io:format("in file ~tp because of inconsistent type\n", [File]).
---
> inc_warning({F, A}, File, Analysis) ->
>     msg(warn,
>         "      ***Warning: Skip function ~tp/~p "
>         "in file ~tp because of inconsistent type",
>         [F, A, File],
>         Analysis).
390c414
< 	[get_type(I, CodeServer, Records) || I <- TypeInfo]
---
>         [get_type(I, CodeServer, Records, Analysis) || I <- TypeInfo]
397c421
< get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records) ->
---
> get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records, Analysis) ->
416c440
< 	  fatal_error(Msg);
---
>           fatal_error(Msg, Analysis);
420c444
< 	  fatal_error(Msg)
---
>           fatal_error(Msg, Analysis)
452,453c476,477
< write_typed_file(File, Info) ->
<   io:format("      Processing file: ~tp\n", [File]),
---
> write_typed_file(File, Info, Analysis) ->
>   msg(info, "      Processing file: ~tp", [File], Analysis),
464,471c488,489
< 	  case file:delete(NewFileName) of
< 	    ok -> ok;
< 	    {error, enoent} -> ok;
< 	    {error, _} ->
< 	      Msg = io_lib:format("Error in deleting file ~ts\n", [NewFileName]),
< 	      fatal_error(Msg)
< 	  end,
< 	  write_typed_file(File, Info, NewFileName);
---
>           delete_file(NewFileName, Analysis),
>           write_typed_file(File, Info, NewFileName, Analysis);
473,474c491,492
< 	  Msg = io_lib:format("Not enough space in ~tp\n", [Dir]),
< 	  fatal_error(Msg);
---
>           Msg = io_lib:format("Not enough space in ~tp", [Dir]),
>           fatal_error(Msg, Analysis);
476,477c494,495
< 	  Msg = io_lib:format("No write permission in ~tp\n", [Dir]),
< 	  fatal_error(Msg);
---
>           Msg = io_lib:format("No write permission in ~tp", [Dir]),
>           fatal_error(Msg, Analysis);
479c497
< 	  Msg = io_lib:format("Unhandled error ~ts when writing ~tp\n",
---
>           Msg = io_lib:format("Unhandled error ~ts when writing ~tp",
481c499
< 	  fatal_error(Msg)
---
>           fatal_error(Msg, Analysis)
484c502,514
<       write_typed_file(File, Info, NewFileName)
---
>         write_typed_file(File, Info, NewFileName, Analysis)
>   end.
> 
> -spec delete_file(file:filename_all(), analysis()) -> ok.
> delete_file(File, Analysis) ->
>   case file:delete(File) of
>     ok ->
>       ok;
>     {error, enoent} ->
>       ok;
>     {error, _} ->
>       Msg = io_lib:format("Error in deleting file ~ts", [File]),
>       fatal_error(Msg, Analysis)
487c517
< write_typed_file(File, Info, NewFileName) ->
---
> write_typed_file(File, Info, NewFileName, Analysis) ->
490,491c520,521
<   write_typed_file(Chars, NewFileName, Info, 1, []),
<   io:format("             Saved as: ~tp\n", [NewFileName]).
---
>   write_typed_file(Chars, NewFileName, Info, 1, [], Analysis),
>   msg(info, "             Saved as: ~tp", [NewFileName], Analysis).
493c523
< write_typed_file(Chars, File, #info{functions = []}, _LNo, _Acc) ->
---
> write_typed_file(Chars, File, #info{functions = []}, _LNo, _Acc, _Analysis) ->
495c525
< write_typed_file([Ch|Chs] = Chars, File, Info, LineNo, Acc) ->
---
> write_typed_file([Ch|Chs] = Chars, File, Info, LineNo, Acc, Analysis) ->
499c529
<       ok = raw_write(F, A, Info, File, []),
---
>       ok = raw_write(F, A, Info, File, [], Analysis),
502c532
<       write_typed_file(Chars, File, NewInfo, Line, NewAcc);
---
>       write_typed_file(Chars, File, NewInfo, Line, NewAcc, Analysis);
510c540
< 		ok = raw_write(F, A, Info, File, [Ch|Acc]),
---
>                 ok = raw_write(F, A, Info, File, [Ch|Acc], Analysis),
515c545
< 	  write_typed_file(Chs, File, NewInfo, NewLineNo, NewAcc);
---
>           write_typed_file(Chs, File, NewInfo, NewLineNo, NewAcc, Analysis);
517c547
< 	  write_typed_file(Chs, File, Info, LineNo, [Ch|Acc])
---
>           write_typed_file(Chs, File, Info, LineNo, [Ch|Acc], Analysis)
521,522c551,552
< raw_write(F, A, Info, File, Content) ->
<   TypeInfo = get_type_string(F, A, Info, file),
---
> raw_write(F, A, Info, File, Content, Analysis) ->
>   TypeInfo = get_type_string(F, A, Info, file, Analysis),
527,528c557,558
< get_type_string(F, A, Info, Mode) ->
<   Type = get_type_info({F,A}, Info#info.types),
---
> get_type_string(F, A, Info, Mode, Analysis) ->
>   Type = get_type_info({F,A}, Info#info.types, Analysis),
550,553c580,583
< show_type_info(File, Info) ->
<   io:format("\n%% File: ~tp\n%% ", [File]),
<   OutputString = lists:concat(["~.", length(File)+8, "c~n"]),
<   io:fwrite(OutputString, [$-]),
---
> show_type_info(File, Info, Analysis) ->
>   msg(info, "\n%% File: ~tp", [File], Analysis),
>   OutputString = lists:concat(["~.", length(File) + 8, "c"]),
>   msg(info, [$%, $%, $\s | OutputString], [$-], Analysis),
555,556c585,586
< 	    TypeInfo = get_type_string(F, A, Info, show),
< 	    io:format("~ts\n", [TypeInfo])
---
>             TypeInfo = get_type_string(F, A, Info, show, Analysis),
>             msg(info, "~ts", [TypeInfo], Analysis)
560c590
< get_type_info(Func, Types) ->
---
> get_type_info(Func, Types, Analysis) ->
566,567c596,597
<       Msg = io_lib:format("No type info for function: ~tp\n", [Func]),
<       fatal_error(Msg);
---
>       Msg = io_lib:format("No type info for function: ~tp", [Func]),
>       fatal_error(Msg, Analysis);
576c606
< -spec process_cl_args() -> {args(), analysis()}.
---
> -spec process_cl_args(opts()) -> {args(), analysis()}.
578,586c608,609
< process_cl_args() ->
<   ArgList = init:get_plain_arguments(),
<   %% io:format("Args is ~tp\n", [ArgList]),
<   {Args, Analysis} = analyze_args(ArgList, #args{}, #analysis{}),
<   %% if the mode has not been set, set it to the default mode (show)
<   {Args, case Analysis#analysis.mode of
< 	   undefined -> Analysis#analysis{mode = ?SHOW};
< 	   Mode when is_atom(Mode) -> Analysis
< 	 end}.
---
> process_cl_args(Opts) ->
>     analyze_args(maps:to_list(Opts), #args{}, #analysis{}).
590,591c613
< analyze_args(ArgList, Args, Analysis) ->
<   {Result, Rest} = cl(ArgList),
---
> analyze_args([Result | Rest], Args, Analysis) ->
595,647d616
< cl(["-h"|_])     -> help_message();
< cl(["--help"|_]) -> help_message();
< cl(["-v"|_])        -> version_message();
< cl(["--version"|_]) -> version_message();
< cl(["--edoc"|Opts]) -> {edoc, Opts};
< cl(["--show"|Opts]) -> {{mode, ?SHOW}, Opts};
< cl(["--show_exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts};
< cl(["--show-exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts};
< cl(["--show_success_typings"|Opts]) -> {show_succ, Opts};
< cl(["--show-success-typings"|Opts]) -> {show_succ, Opts};
< cl(["--annotate"|Opts]) -> {{mode, ?ANNOTATE}, Opts};
< cl(["--annotate-inc-files"|Opts]) -> {{mode, ?ANNOTATE_INC_FILES}, Opts};
< cl(["--no_spec"|Opts]) -> {no_spec, Opts};
< cl(["--plt",Plt|Opts]) -> {{plt, Plt}, Opts};
< cl(["-D"++Def|Opts]) ->
<   case Def of
<     "" -> fatal_error("no variable name specified after -D");
<     _ ->
<       DefPair = process_def_list(re:split(Def, "=", [{return, list}, unicode])),
<       {{def, DefPair}, Opts}
<   end;
< cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts};
< cl(["-I"++Dir|Opts]) ->
<   case Dir of
<     "" -> fatal_error("no include directory specified after -I");
<     _ -> {{inc, Dir}, Opts}
<   end;
< cl(["-T"|Opts]) ->
<   {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
<   case Files of
<     [] -> fatal_error("no file or directory specified after -T");
<     [_|_] -> {{trusted, Files}, RestOpts}
<   end;
< cl(["-r"|Opts]) ->
<   {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
<   {{files_r, Files}, RestOpts};
< cl(["-pa",Dir|Opts]) -> {{pa,Dir}, Opts};
< cl(["-pz",Dir|Opts]) -> {{pz,Dir}, Opts};
< cl(["-"++H|_]) -> fatal_error("unknown option -"++H);
< cl(Opts) -> 
<   {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
<   {{files, Files}, RestOpts}.
< 
< process_def_list(L) ->
<   case L of
<     [Name, Value] ->
<       {ok, Tokens, _} = erl_scan:string(Value ++ "."),
<       {ok, ErlValue} = erl_parse:parse_term(Tokens),
<       {list_to_atom(Name), ErlValue};
<     [Name] ->
<       {list_to_atom(Name), true}
<   end.
< 
658,659c627,630
< analyze_result(edoc, Args, Analysis) ->
<   {Args, Analysis#analysis{edoc = true}};
---
> analyze_result({edoc, Value}, Args, Analysis) ->
>   {Args, Analysis#analysis{edoc = Value}};
> analyze_result({io, Val}, Args, Analysis) ->
>   {Args, Analysis#analysis{io = Val}};
662,671c633,637
<   case Analysis#analysis.mode of
<     undefined -> {Args, Analysis#analysis{mode = Mode}};
<     OldMode -> mode_error(OldMode, Mode)
<   end;
< analyze_result({def, Val}, Args, Analysis) ->
<   NewVal = Analysis#analysis.macros ++ [Val],
<   {Args, Analysis#analysis{macros = NewVal}};
< analyze_result({inc, Val}, Args, Analysis) ->
<   NewVal = Analysis#analysis.includes ++ [Val],
<   {Args, Analysis#analysis{includes = NewVal}};
---
>   {Args, Analysis#analysis{mode = Mode}};
> analyze_result({macros, Macros}, Args, Analysis) ->
>   {Args, Analysis#analysis{macros = Macros}};
> analyze_result({includes, Includes}, Args, Analysis) ->
>   {Args, Analysis#analysis{includes = Includes}};
674,683c640,643
< analyze_result(show_succ, Args, Analysis) ->
<   {Args, Analysis#analysis{show_succ = true}};
< analyze_result(no_spec, Args, Analysis) ->
<   {Args, Analysis#analysis{no_spec = true}};
< analyze_result({pa, Dir}, Args, Analysis) ->
<   true = code:add_patha(Dir),
<   {Args, Analysis};
< analyze_result({pz, Dir}, Args, Analysis) ->
<   true = code:add_pathz(Dir),
<   {Args, Analysis}.
---
> analyze_result({show_succ, Value}, Args, Analysis) ->
>   {Args, Analysis#analysis{show_succ = Value}};
> analyze_result({no_spec, Value}, Args, Analysis) ->
>   {Args, Analysis#analysis{no_spec = Value}}.
689c649
< -spec get_all_files(args()) -> [file:filename(),...].
---
> -spec get_all_files(args(), analysis()) -> [file:filename(), ...].
691,694c651,656
< get_all_files(#args{files = Fs, files_r = Ds}) ->
<   case filter_fd(Fs, Ds, fun test_erl_file_exclude_ann/1) of
<     [] -> fatal_error("no file(s) to analyze");
<     AllFiles -> AllFiles
---
> get_all_files(#args{files = Fs, files_r = Ds}, Analysis) ->
>     case filter_fd(Fs, Ds, fun test_erl_file_exclude_ann/1, Analysis) of
>         [] ->
>             fatal_error("no file(s) to analyze", Analysis);
>         AllFiles ->
>             AllFiles
716c678
< -spec filter_fd(files(), files(), test_file_fun()) -> files().
---
> -spec filter_fd(files(), files(), test_file_fun(), analysis()) -> files().
718,720c680,682
< filter_fd(File_Dir, Dir_R, Fun) ->
<   All_File_1 = process_file_and_dir(File_Dir, Fun),
<   All_File_2 = process_dir_rec(Dir_R, Fun),
---
> filter_fd(File_Dir, Dir_R, Fun, Analysis) ->
>   All_File_1 = process_file_and_dir(File_Dir, Fun, Analysis),
>   All_File_2 = process_dir_rec(Dir_R, Fun, Analysis),
723c685
< -spec process_file_and_dir(files(), test_file_fun()) -> files().
---
> -spec process_file_and_dir(files(), test_file_fun(), analysis()) -> files().
725c687
< process_file_and_dir(File_Dir, TestFun) ->
---
> process_file_and_dir(File_Dir, TestFun, Analysis) ->
730c692
< 	  false -> check_dir(Elem, false, Acc, TestFun)
---
>           false -> check_dir(Elem, false, Acc, TestFun, Analysis)
735c697
< -spec process_dir_rec(files(), test_file_fun()) -> files().
---
> -spec process_dir_rec(files(), test_file_fun(), analysis()) -> files().
737,738c699,700
< process_dir_rec(Dirs, TestFun) ->
<   Fun = fun (Dir, Acc) -> check_dir(Dir, true, Acc, TestFun) end,
---
> process_dir_rec(Dirs, TestFun, Analysis) ->
>   Fun = fun (Dir, Acc) -> check_dir(Dir, true, Acc, TestFun, Analysis) end,
741c703
< -spec check_dir(file:filename(), boolean(), files(), test_file_fun()) -> files().
---
> -spec check_dir(file:filename(), boolean(), files(), test_file_fun(), analysis()) -> files().
743c705
< check_dir(Dir, Recursive, Acc, Fun) ->
---
> check_dir(Dir, Recursive, Acc, Fun, Analysis) ->
749c711
< 	  FinalFiles = process_file_and_dir(TmpFiles, Fun),
---
>           FinalFiles = process_file_and_dir(TmpFiles, Fun, Analysis),
752,753c714,715
< 	  TmpAcc1 = process_file_and_dir(TmpFiles, Fun),
< 	  TmpAcc2 = process_dir_rec(TmpDirs, Fun),
---
>           TmpAcc1 = process_file_and_dir(TmpFiles, Fun, Analysis),
>           TmpAcc2 = process_dir_rec(TmpDirs, Fun, Analysis),
757c719
<       fatal_error("no access permission to dir \""++Dir++"\"");
---
>       fatal_error("no access permission to dir \""++Dir++"\"", Analysis);
759c721
<       fatal_error("cannot access "++Dir++": No such file or directory");
---
>       fatal_error("cannot access "++Dir++": No such file or directory", Analysis);
761c723
<       fatal_error("error involving a use of file:list_dir/1")
---
>       fatal_error("error involving a use of file:list_dir/1", Analysis)
822c784,785
< 	fatal_error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it")
---
>         fatal_error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it",
>                     Analysis)
841c804
< 	fatal_error(ErrorMsg)
---
>         fatal_error(ErrorMsg, NewAnalysis)
853,854c816,817
<       %% io:format("File=~tp\n,Options=~p\n,Error=~p\n", [File,Options,Reason]),
<       compile_error(Reason);
---
>       msg(debug, "File=~tp\n,Options=~p\n,Error=~p", [File, Options, Reason], Analysis),
>       compile_error(Reason, Analysis);
857c820
< 	{error, Reason} -> compile_error([Reason]);
---
>         {error, Reason} -> compile_error([Reason], Analysis);
861c824
< 	    {error, Reason} -> compile_error([Reason]);
---
>             {error, Reason} -> compile_error([Reason], Analysis);
927d889
< 	%% io:format("Added function ~tp\n", [{LineNo, F, A}]),
975c937
< -spec fatal_error(string()) -> no_return().
---
> -spec default_io() -> io().
977,979c939,943
< fatal_error(Slogan) ->
<   msg(io_lib:format("typer: ~ts\n", [Slogan])),
<   erlang:halt(1).
---
> default_io() ->
>   #{debug => fun swallow_output/2,
>     info => fun format/2,
>     warn => fun format_on_stderr/2,
>     abort => fun format_and_halt/2}.
981c945
< -spec mode_error(mode(), mode()) -> no_return().
---
> -spec fatal_error(string(), analysis()) -> no_return().
983,987c947,948
< mode_error(OldMode, NewMode) ->
<   Msg = io_lib:format("Mode was previously set to '~s'; "
< 		      "cannot set it to '~s' now",
< 		      [OldMode, NewMode]),
<   fatal_error(Msg).
---
> fatal_error(Slogan, Analysis) ->
>   msg(abort, "typer: ~ts", [Slogan], Analysis).
989c950
< -spec compile_error([string()]) -> no_return().
---
> -spec compile_error([string()], analysis()) -> no_return().
991c952
< compile_error(Reason) ->
---
> compile_error(Reason, Analysis) ->
994c955
<   fatal_error(Msg).
---
>   fatal_error(Msg, Analysis).
996c957
< -spec msg(string()) -> 'ok'.
---
> -spec msg(debug | info | warn | abort, io:format(), [term()], analysis()) -> _.
998,999c959,961
< msg(Msg) ->
<   io:format(standard_error, "~ts", [Msg]).
---
> msg(Level, Format, Data, #analysis{io = Io}) ->
>   Printer = maps:get(Level, Io, fun swallow_output/2),
>   Printer(Format, Data).
1001,1003c963
< %%--------------------------------------------------------------------
< %% Version and help messages.
< %%--------------------------------------------------------------------
---
> -spec format(io:format(), [term()]) -> ok.
1005c965,966
< -spec version_message() -> no_return().
---
> format(Format, Data) ->
>   io:format(Format ++ "\n", Data).
1007,1060c968,982
< version_message() ->
<   io:format("TypEr version "++?VSN++"\n"),
<   erlang:halt(0).
< 
< -spec help_message() -> no_return().
< 
< help_message() ->
<   S = <<" Usage: typer [--help] [--version] [--plt PLT] [--edoc]
<               [--show | --show-exported | --annotate | --annotate-inc-files]
<               [-Ddefine]* [-I include_dir]* [-pa dir]* [-pz dir]*
<               [-T application]* [-r] file*
< 
<  Options:
<    -r dir*
<        search directories recursively for .erl files below them
<    --show
<        Prints type specifications for all functions on stdout.
<        (this is the default behaviour; this option is not really needed)
<    --show-exported (or --show_exported)
<        Same as --show, but prints specifications for exported functions only
<        Specs are displayed sorted alphabetically on the function's name
<    --annotate
<        Annotates the specified files with type specifications
<    --annotate-inc-files
<        Same as --annotate but annotates all -include() files as well as
<        all .erl files (use this option with caution - has not been tested much)
<    --edoc
<        Prints type information as Edoc @spec comments, not as type specs
<    --plt PLT
<        Use the specified dialyzer PLT file rather than the default one
<    -T file*
<        The specified file(s) already contain type specifications and these
<        are to be trusted in order to print specs for the rest of the files
<        (Multiple files or dirs, separated by spaces, can be specified.)
<    -Dname (or -Dname=value)
<        pass the defined name(s) to TypEr
<        (The syntax of defines is the same as that used by \"erlc\".)
<    -I include_dir
<        pass the include_dir to TypEr
<        (The syntax of includes is the same as that used by \"erlc\".)
<    -pa dir
<    -pz dir
<        Set code path options to TypEr
<        (This is useful for files that use parse transforms.)
<    --version (or -v)
<        prints the Typer version and exits
<    --help (or -h)
<        prints this message and exits
< 
<  Note:
<    * denotes that multiple occurrences of these options are possible.
< ">>,
<   io:put_chars(S),
<   erlang:halt(0).
---
> -spec swallow_output(io:format(), [term()]) -> ok.
> 
> swallow_output(_Format, _Data) ->
>   ok.
> 
> -spec format_on_stderr(io:format(), [term()]) -> ok.
> 
> format_on_stderr(Format, Data) ->
>   io:format(standard_error, Format ++ "\n", Data).
> 
> -spec format_and_halt(io:format(), [term()]) -> no_return().
> 
> format_and_halt(Format, Data) ->
>   format_on_stderr(Format, Data),
>   erlang:halt(1).

@bjorng bjorng added testing currently being tested, tag is used by OTP internal CI and removed waiting waiting for changes/input from author labels Mar 7, 2022
@bjorng
Copy link
Contributor

bjorng commented Mar 7, 2022

Thanks! Added to our daily builds.

@bjorng bjorng merged commit 269cc13 into erlang:master Mar 8, 2022
@bjorng
Copy link
Contributor

bjorng commented Mar 8, 2022

Thanks for your pull request!

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
team:VM Assigned to OTP team VM testing currently being tested, tag is used by OTP internal CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants