erlangerlang-otpbeamerlang-stdlib

How do I get the exported types of an erlang module?


I had cause to check the types exported by a module, and I immediately thought "right, module_info then" but was surprised to run into a few difficulties. I found I can get the exported types from modules I compile, but not from say modules in stdlib.

My (three) questions are, how do I reliably get the exported types of a module, why are the exported types in the attributes bit of the module info on some modules, and why some modules and not others?

I discovered that if I build this module:

-module(foo).
-export([bar/0]).
-export_types([baz/0]).
bar() -> bat .

And then use foo:module_info/0, I get this:

[{exports,[{bar,0},{module_info,0},{module_info,1}]},
 {imports,[]},
 {attributes,[{vsn,[108921085595958308709649797749441408863]},
              {export_types,[{baz,0}]}]},
 {compile,[{options,[{outdir,"/tmp"}]},
           {version,"5.0.1"},
           {time,{2015,10,22,10,38,8}},
           {source,"/tmp/foo.erl"}]}]

Great, hidden away in 'attributes' is 'export_types'. Why this is in attributes I'm not quite sure, but... whatever...

I now know this will work:

4> lists:keyfind(export_types, 1, foo:module_info(attributes)).
{export_types,[{baz,0}]}

Great. So, I now know this will work:

5> lists:keyfind(export_types, 1, ets:module_info(attributes)).
false

Ah... it doesn't.

I know there are exported types of course, if the documentation isn't good enough the ets source shows:

-export_type([tab/0, tid/0, match_spec/0, comp_match_spec/0, match_pattern/0]).

In fact the exported type information for the ets module doesn't seem to be anywhere in the module info:

6> rp(ets:module_info()).                                      
[{exports,[{match_spec_run,2},
       {repair_continuation,2},
       {fun2ms,1},
       {foldl,3},
       {foldr,3},
       {from_dets,2},
       {to_dets,2},
       {test_ms,2},
       {init_table,2},
       {tab2file,2},
       {tab2file,3},
       {file2tab,1},
       {file2tab,2},
       {tabfile_info,1},
       {table,1},
       {table,2},
       {i,0},
       {i,1},
       {i,2},
       {i,3},
       {module_info,0},
       {module_info,1},
       {tab2list,1},
       {match_delete,2},
       {filter,3},
       {setopts,2},
       {give_away,3},
       {update_element,3},
       {match_spec_run_r,3},
       {match_spec_compile,1},
       {select_delete,2},
       {select_reverse,3},
       {select_reverse,2},
       {select_reverse,1},
       {select_count,2},
       {select,3},
       {select,2},
       {select,1},
       {update_counter,3},
       {slot,2},
       {safe_fixtable,2},
       {rename,2},
       {insert_new,2},
       {insert,2},
       {prev,2},
       {next,2},
       {member,2},
       {match_object,3},
       {match_object,2},
       {match_object,1},
       {match,3},
       {match,2},
       {match,1},
       {last,1},
       {info,2},
       {info,1},
       {lookup_element,3},
       {lookup,2},
       {is_compiled_ms,1},
       {first,1},
       {delete_object,2},
       {delete_all_objects,1},
       {delete,2},
       {delete,1},
       {new,2},
       {all,0}]},
 {imports,[]},
 {attributes,[{vsn,[310474638056108355984984900680115120081]}]},
 {compile,[{options,[{outdir,"/tmp/buildd/erlang-17.1-dfsg/lib/stdlib/src/../ebin"},
                 {i,"/tmp/buildd/erlang-17.1-dfsg/lib/stdlib/src/../include"},
                 {i,"/tmp/buildd/erlang-17.1-dfsg/lib/stdlib/src/../../kernel/include"},
                 warnings_as_errors,debug_info]},
       {version,"5.0.1"},
       {time,{2014,7,25,16,54,59}},
       {source,"/tmp/buildd/erlang-17.1-dfsg/lib/stdlib/src/ets.erl"}]}]
ok

I took things to extremes now and ran this, logging the output to a file:

rp(beam_disasm:file("/usr/lib/erlang/lib/stdlib-2.1/ebin/ets.beam")).

Not that I don't consider this absurd... but anyway, it's about 5,000 lines of output, but nowhere do I find an instance of the string "tid".


Solution

  • Up to Erlang 18 this information is not easily available.

    Dialyzer, for example, extracts it from the abstract syntax tree of the core Erlang version of a module (see e.g. dialyzer_utils:get_record_and_type_info/1 used by e.g. dialyzer_analysis_callgraph:compile_byte/5)

    Regarding this part:

    why are the exported types in the attributes bit of the module info on some modules, and why some modules and not others?

    this is due to a bad definition in your module. The attribute should be -export_type, not -export_types. If you use the correct one (and define the baz/0 type and use it somewhere so that the module compiles), the exported types... vanish, as is expected.