@@ -126,7 +126,7 @@ validate_struct(Atom, _) when is_atom(Atom) -> true;
126126validate_struct (_ , _ ) -> false .
127127
128128assert_struct_info_if_not_function (Meta , Name , Assocs , #{function := nil } = E ) ->
129- case maybe_load_struct_info (Meta , Name , E ) of
129+ case maybe_load_struct_info (Meta , Name , hard , E ) of
130130 {ok , Info } ->
131131 [lists :any (fun (Field ) -> ? key (Field , field ) =:= Key end , Info ) orelse
132132 function_error (Meta , E , ? MODULE , {unknown_key_for_struct , Name , Key })
@@ -140,12 +140,24 @@ assert_struct_info_if_not_function(_Meta, _Name, _Assocs, _E) ->
140140 ok .
141141
142142maybe_load_struct_info (Meta , Name , E ) ->
143+ maybe_load_struct_info (Meta , Name , soft , E ).
144+
145+ maybe_load_struct_info (Meta , Name , Mode , E ) ->
143146 try
144- case is_open (Name , Meta , E ) andalso lookup_struct_info_from_data_tables (Name ) of
147+ InContext = in_context (Name , E ),
148+
149+ case (InContext orelse is_compiling_struct (Meta , Name , Mode , E ))
150+ andalso lookup_struct_info_from_data_tables (Name ) of
145151 % % If I am accessing myself and there is no attribute,
146152 % % don't invoke the fallback to avoid calling loaded code.
147153 false when ? key (E , module ) =:= Name -> nil ;
148- false -> Name :'__info__' (struct );
154+ false ->
155+ % % We already attempted to wait for the struct (unless InContext),
156+ % % so we only invoke '__info__' if already loaded or InContext
157+ case InContext orelse erlang :module_loaded (Name ) of
158+ true -> Name :'__info__' (struct );
159+ false -> nil
160+ end ;
149161 InfoList -> InfoList
150162 end
151163 of
@@ -165,7 +177,8 @@ lookup_struct_info_from_data_tables(Module) ->
165177
166178load_struct (Meta , Name , Assocs , E ) ->
167179 try
168- case is_open (Name , Meta , E ) andalso elixir_def :external_for (Meta , Name , '__struct__' , 1 , [def ]) of
180+ case (in_context (Name , E ) orelse is_compiling_struct (Meta , Name , hard , E )) andalso
181+ elixir_def :external_for (Meta , Name , '__struct__' , 1 , [def ]) of
169182 % % If I am accessing myself and there is no __struct__ function,
170183 % % don't invoke the fallback to avoid calling loaded code.
171184 false when ? key (E , module ) =:= Name ->
@@ -221,17 +234,17 @@ assert_and_trace_struct_assocs(Meta, Name, Assocs, E) ->
221234 elixir_env :trace ({struct_expansion , Meta , Name , Keys }, E ),
222235 Keys .
223236
224- is_open ( Name , Meta , E ) ->
225- in_context ( Name , E ) orelse (( code :ensure_loaded (Name ) /= {module , Name }) andalso wait_for_struct (Name , Meta , E ) ).
237+ is_compiling_struct ( Meta , Name , Mode , E ) ->
238+ ( code :ensure_loaded (Name ) /= {module , Name }) andalso wait_for_struct (Meta , Name , Mode , E ).
226239
227240in_context (Name , E ) ->
228241 % % We also include the current module because it won't be present
229242 % % in context module in case the module name is defined dynamically.
230243 lists :member (Name , [? key (E , module ) | ? key (E , context_modules )]).
231244
232- wait_for_struct (Module , Meta , E ) ->
245+ wait_for_struct (Meta , Module , Mode , E ) ->
233246 (erlang :get (elixir_compiler_info ) /= undefined ) andalso
234- ('Elixir.Kernel.ErrorHandler' :ensure_compiled (Module , struct , hard , elixir_utils :get_line (Meta , E )) =:= found ).
247+ ('Elixir.Kernel.ErrorHandler' :ensure_compiled (Module , struct , Mode , elixir_utils :get_line (Meta , E )) =:= found ).
235248
236249struct_undef (Name , E ) ->
237250 case in_context (Name , E ) andalso (? key (E , function ) == nil ) of
0 commit comments