-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Conversation
CT Test Results 2 files 33 suites 6m 19s ⏱️ 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 |
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. |
There was a problem hiding this 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.erl
Outdated
@@ -31,566 +34,41 @@ | |||
-define(SHOW, show). | |||
-define(SHOW_EXPORTED, show_exported). | |||
-define(ANNOTATE, annotate). | |||
-define(ANNOTATE_IN_PLACE, annotate_in_place). |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
Not at this time, no. If somebody (like @robertoaloi) eventually needs something else, they can make that change themselves ;) |
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. |
6904816
to
57f188c
Compare
Single commit and build fixed ✅ |
5ad7e2b
to
9da596b
Compare
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.
9da596b
to
9a07746
Compare
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). |
Thanks! Added to our daily builds. |
Thanks for your pull request! |
We (@pablocostass, @maco, and myself) are building
rebar3_typer
and, as part of our efforts to integrate TypEr intorebar3
, we found thattyper
(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 createdtyper_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 likeannotate
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.