summaryrefslogtreecommitdiff
path: root/lib/dialyzer/src/dialyzer_typegraph.erl
blob: 5818236fa7ad72dca1cb1d6abf3e800f7a52ce0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
%% -*- erlang-indent-level: 2 -*-
-module(dialyzer_typegraph).

-export([module_type_deps/3]).

-export_type([type_mod_deps/0]).

-include("dialyzer.hrl").

%% Maps a module to those modules that depend on it
-type type_mod_deps() :: #{module() => [module()]}.

%% We track type dependecies so that we know which modules we ultimately
%% depend upon for type definitions which, in turn, affect the checking
%% of a module.

%% Any non-local types that are used in a spec (aka contract) / callback, any
%% locally-defined types that may depend on a non-local type, and any
%% implementations of a behaviour, introduce type-level dependencies on other
%% modules such that if the definition of that other module were to change,
%% this module would need to be checked again to account for those changes.

-spec module_type_deps(UseContracts :: boolean(), dialyzer_codeserver:codeserver(), [module()]) -> type_mod_deps().

%% The module type deps of a module are modules that depend on the module to
%% define types, contracts, callbacks or behaviours
module_type_deps(UseContracts, CodeServer, Modules) ->

  Contracts =
    case UseContracts of
      true -> maps:from_list(dict:to_list(dialyzer_codeserver:get_contracts(CodeServer)));
      false -> []
    end,
  Callbacks = maps:from_list(dialyzer_codeserver:get_callbacks(CodeServer)),
  TypeDefinitions =
    maps:from_list(
      [{M, dialyzer_codeserver:lookup_mod_records(M, CodeServer)} || M <- Modules]
    ),
  Behaviours =
    maps:from_list(
      [{M, get_behaviours_for_module(M, CodeServer)} || M <- Modules]
    ),
  collect_module_type_deps(Contracts, Callbacks, TypeDefinitions, Behaviours).

-spec get_behaviours_for_module(module(), dialyzer_codeserver:codeserver()) -> [module()].

get_behaviours_for_module(M, CodeServer) ->
  ModCode = dialyzer_codeserver:lookup_mod_code(M, CodeServer),
  Attrs = cerl:module_attrs(ModCode),
  {Behaviours, _BehaviourLocations} = dialyzer_behaviours:get_behaviours(Attrs),
  Behaviours.

-spec collect_module_type_deps(Specs, Callbacks, TypeDefinitions, Behaviours) -> type_mod_deps() when
    Specs :: #{mfa() => dialyzer_contracts:file_contract()},
    Callbacks :: #{mfa() => dialyzer_contracts:file_contract()},
    TypeDefinitions :: #{module() => erl_types:type_table()},
    Behaviours :: #{module() => [module()]}.

collect_module_type_deps(Specs, Callbacks, TypeDefinitions, Behaviours) ->

  Contracts =
    [{M, Spec} || {{M, _F, _A}, {_FileLine, Spec, _Extra}} <- maps:to_list(Specs)] ++
    [{M, Callback} || {{M, _F, _A}, {_FileLine, Callback, _Extra}} <- maps:to_list(Callbacks)],

  ModulesMentionedInTypeDefinitions =
    [{FromTypeDefM, erl_types:module_type_deps_of_type_defs(TypeTable)}
      || {FromTypeDefM, TypeTable} <- maps:to_list(TypeDefinitions)],

  ModulesMentionedInContracts =
    [{FromContractM, module_type_deps_of_contract(C)}
      || {FromContractM, C} <- Contracts],

  ModulesMentionedAsBehaviours =
    maps:to_list(Behaviours),

  AllDepsRaw =
    ModulesMentionedInContracts ++
    ModulesMentionedInTypeDefinitions ++
    ModulesMentionedAsBehaviours,

  %% Find the union of module dependencies from all sources, removing
  %% self-references, and flipping the direction of the mapping to
  %% match the expectations of Dialyzer elsewhere,
  %% i.e., from:
  %%   module -> those modules it depends on
  %% to
  %%   module -> those modules that depend on it
  S0 = sofs:relation(AllDepsRaw, [{atom,[atom]}]),
  S1 = sofs:relation_to_family(S0),
  S2 = sofs:family_union(S1),
  S3 = sofs:family_to_relation(S2),
  S4 = sofs:converse(S3),
  S5 = sofs:strict_relation(S4),
  S6 = sofs:relation_to_family(S5),
  S7 = sofs:to_external(S6),
  ModuleToThoseModulesThatDependOnIt = maps:from_list(S7),

  ModuleToThoseModulesThatDependOnIt.

-spec module_type_deps_of_contract(#contract{}) -> [module()].

module_type_deps_of_contract(#contract{forms = Forms}) ->
  TypeForms = [TypeForm || {TypeForm, _Constraints} <- Forms],
  ConstraintForms =
    lists:append([Constraints || {_TypeForm, Constraints} <- Forms]),
  lists:usort(
    lists:append(
      erl_types:type_form_to_remote_modules(TypeForms),
      dialyzer_contracts:constraint_form_to_remote_modules(ConstraintForms))).