/// <summary>
        /// Loads all dlls listed as containing rewrite steps to include in the compilation process in the given configuration.
        /// Generates suitable diagnostics if a listed file can't be found or loaded.
        /// Finds all types implementing the IRewriteStep interface and loads the corresponding rewrite steps
        /// according to the specified priority, where a steps with a higher priority will be listed first in the returned array.
        /// If the function onDiagnostic is specified and not null, calls it on all generated diagnostics,
        /// and calls onException on all caught exceptions if it is specified and not null.
        /// Returns an empty array if the rewrite steps in the given configurations are set to null.
        /// </summary>
        internal static ImmutableArray <LoadedStep> Load(CompilationLoader.Configuration config, Action <Diagnostic>?onDiagnostic = null, Action <Exception>?onException = null)
        {
            var loadedSteps = new List <LoadedStep>();
            var instanceRewriteStepLoader = new InstanceRewriteStepsLoader(onDiagnostic, onException);

            loadedSteps.AddRange(instanceRewriteStepLoader.GetLoadedSteps(config.RewriteStepInstances));

            var typeRewriteStepLoader = new TypeRewriteStepsLoader(onDiagnostic, onException);

            loadedSteps.AddRange(typeRewriteStepLoader.GetLoadedSteps(config.RewriteStepTypes));

            var assemblyRewriteStepLoader = new AssemblyRewriteStepsLoader(onDiagnostic, onException);

            // this is for backwards compatibility with RewriteSteps property on the config. Can be removed in the future.
#pragma warning disable CS0618 // Type or member is obsolete
            var unifiedAssemblyRewriteSteps = (config.RewriteStepAssemblies ?? Enumerable.Empty <(string, string?)>()).Union(config.RewriteSteps ?? Enumerable.Empty <(string, string?)>());
#pragma warning restore CS0618 // Type or member is obsolete
            loadedSteps.AddRange(assemblyRewriteStepLoader.GetLoadedSteps(unifiedAssemblyRewriteSteps));

            foreach (var loaded in loadedSteps)
            {
                var assemblyConstants = loaded.AssemblyConstants;
                if (assemblyConstants == null)
                {
                    continue;
                }

                foreach (var kvPair in config.AssemblyConstants ?? Enumerable.Empty <KeyValuePair <string, string> >())
                {
                    assemblyConstants[kvPair.Key] = kvPair.Value;
                }

                // We don't overwrite assembly properties specified by configuration.
                var defaultOutput = assemblyConstants.TryGetValue(AssemblyConstants.OutputPath, out var path) ? path : null;
                assemblyConstants.TryAdd(AssemblyConstants.OutputPath, loaded.OutputFolder ?? defaultOutput ?? config.BuildOutputFolder);
                assemblyConstants.TryAdd(AssemblyConstants.AssemblyName, config.ProjectNameWithoutPathOrExtension);
            }

            CompilationLoader.SortRewriteSteps(loadedSteps, step => step.Priority);
            return(loadedSteps.ToImmutableArray());
        }