diff --git a/src/gradualizer.erl b/src/gradualizer.erl index 81a29055..b69a6fbc 100644 --- a/src/gradualizer.erl +++ b/src/gradualizer.erl @@ -178,6 +178,7 @@ type_check_file(File, Opts) -> ".beam" -> case gradualizer_file_utils:get_forms_from_beam(File) of {ok, Forms} -> + ok = gradualizer_db:import_beam_files([File]), type_check_forms(File, Forms, Opts); Error -> throw(Error) @@ -195,6 +196,10 @@ type_check_file(File, Opts) -> lint_and_check_forms(Forms, File, Opts) -> case erl_lint:module(Forms, File, [return_errors]) of {ok, _Warnings} -> + % import the currently checked file so that it can + % reference itself even without adding its path via --pa + ok = gradualizer_db:import_erl_files([File]), + type_check_forms(File, Forms, Opts); {error, Errors, _Warnings} -> %% If there are lint errors (i.e. compile errors like undefined diff --git a/src/typechecker.erl b/src/typechecker.erl index d865ed85..7046fe47 100644 --- a/src/typechecker.erl +++ b/src/typechecker.erl @@ -1767,7 +1767,8 @@ do_type_check_expr(Env, {call, _, {atom, _, TypeOp}, [Expr, {string, _, TypeStr} do_type_check_expr(Env, {call, _, {atom, _, record_info}, [_, _]} = Call) -> Ty = get_record_info_type(Call, Env), {Ty, Env, constraints:empty()}; -do_type_check_expr(Env, {call, P, {remote, _, _Mod, {atom, _, module_info}} = Name, Args}) -> +do_type_check_expr(Env, {call, P, {remote, _, _Mod, {atom, _, module_info}} = Name, Args}) + when length(Args) == 0 orelse length(Args) == 1 -> Arity = arity(length(Args)), FunTy = get_module_info_type(Arity), type_check_call_ty(Env, expect_fun_type(Env, FunTy, Arity), Args, {Name, P, FunTy}); @@ -2678,7 +2679,8 @@ do_type_check_expr_in(Env, ResTy, {call, _, {atom, _, record_info}, [_, _]} = Ca false -> throw(type_error(Call, ResTy, Ty)) end; -do_type_check_expr_in(Env, ResTy, {call, P, {remote, _, _Mod, {atom, _, module_info}} = Name, Args} = Call) -> +do_type_check_expr_in(Env, ResTy, {call, P, {remote, _, _Mod, {atom, _, module_info}} = Name, Args} = Call) + when length(Args) == 0 orelse length(Args) == 1 -> Arity = arity(length(Args)), FunTy = get_module_info_type(Arity), type_check_call(Env, ResTy, Call, expect_fun_type(Env, FunTy, Arity), diff --git a/test/gradualizer_tests.erl b/test/gradualizer_tests.erl index 5e236ce6..a38322d8 100644 --- a/test/gradualizer_tests.erl +++ b/test/gradualizer_tests.erl @@ -5,14 +5,29 @@ -define(passing, "test/should_pass/any.erl"). -define(failing, "test/should_fail/arg.erl"). -type_check_erl_file_test_() -> +global_test_() -> + {setup, + fun setup_app/0, + fun cleanup_app/1, + [{generator, fun type_check_erl_file/0}, + {generator, fun type_check_erl_files/0}, + {generator, fun type_check_forms/0}, + {generator, fun type_check_beam_file/0}, + {generator, fun type_check_module/0}, + {generator, fun type_check_dir/0}, + {generator, fun not_found/0}, + {generator, fun bad_content/0}, + {generator, fun beam_without_forms/0} + ]}. + +type_check_erl_file() -> [?_assertEqual(ok, gradualizer:type_check_file(?passing)), ?_assertEqual([], gradualizer:type_check_file(?passing, [return_errors])), ?_assertEqual(nok, gradualizer:type_check_file(?failing)), ?_assertMatch([_|_], gradualizer:type_check_file(?failing, [return_errors])) ]. -type_check_erl_files_test_() -> +type_check_erl_files() -> [ ?_assertEqual(ok, gradualizer:type_check_files([?passing, ?passing])), ?_assertEqual(nok, gradualizer:type_check_files([?failing, ?failing])), @@ -24,7 +39,7 @@ type_check_erl_files_test_() -> ?_assertEqual([], gradualizer:type_check_files([?passing, ?passing], [return_errors])) ]. -type_check_forms_test_() -> +type_check_forms() -> {ok, PassingForms} = epp:parse_file(?passing, []), %% Drop the file attribute to check that type_check_forms works without it [{attribute, _, file, _} | PassingFormsNoFile] = PassingForms, @@ -33,19 +48,19 @@ type_check_forms_test_() -> ?_assertEqual(ok, gradualizer:type_check_forms(PassingFormsNoFile, [])) ]. -type_check_beam_file_test() -> +type_check_beam_file() -> Dir = filename:dirname(?FILE), % this differs when /not/ using rebar BeamFile = filename:join(Dir, "any.beam"), ?_assertEqual(ok, gradualizer:type_check_file(BeamFile)). -type_check_module_test() -> +type_check_module() -> {module, Mod} = code:load_file(any), - ?assertEqual(ok, gradualizer:type_check_module(Mod)). + ?_assertEqual(ok, gradualizer:type_check_module(Mod)). -type_check_dir_test() -> - ?assertEqual(nok, gradualizer:type_check_dir("test/dir/")). +type_check_dir() -> + ?_assertEqual(nok, gradualizer:type_check_dir("test/dir/")). -not_found_test_() -> +not_found() -> [ ?_assertThrow({file_not_found, "test/not_found.erl"}, gradualizer:type_check_file("test/not_found.erl")), @@ -61,14 +76,14 @@ not_found_test_() -> gradualizer:type_check_module(erlang)) ]. -bad_content_test_() -> +bad_content() -> {setup, fun() -> file:write_file("test/bad_content.beam", "bad content") end, fun(_) -> file:delete("test/bad_content.beam") end, ?_assertThrow({forms_error,{not_a_beam_file, 'test/bad_content.beam'}}, gradualizer:type_check_file("test/bad_content.beam"))}. -beam_without_forms_test_() -> +beam_without_forms() -> {setup, fun() -> {ok, any} = compile:file("test/should_pass/any.erl", @@ -77,3 +92,12 @@ beam_without_forms_test_() -> fun(_) -> file:delete("test/should_pass/any.beam") end, ?_assertThrow({forms_not_found, "test/should_pass/any.beam"}, gradualizer:type_check_file("test/should_pass/any.beam"))}. + + +setup_app() -> + {ok, Apps} = application:ensure_all_started(gradualizer), + Apps. + + cleanup_app(Apps) -> + [ok = application:stop(App) || App <- Apps], + ok. diff --git a/test/should_pass/module_info_higher_arity.erl b/test/should_pass/module_info_higher_arity.erl new file mode 100644 index 00000000..b117e1fa --- /dev/null +++ b/test/should_pass/module_info_higher_arity.erl @@ -0,0 +1,9 @@ +-module(module_info_higher_arity). + +-export([two_plus_four/0, module_info/2]). + +-spec module_info(number(), number()) -> number(). +module_info(A, B) -> A + B. + +-spec two_plus_four() -> number(). +two_plus_four() -> module_info_higher_arity:module_info(2, 4). \ No newline at end of file