diff --git a/Directory.Build.props b/Directory.Build.props index ecdb625..5faf412 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ JasperFx.Events, JasperFx.Events.SourceGenerator) set $(JasperFxVersion) so they always release together. Bump this one value to release the whole set. --> - 2.2.0 + 2.2.1 13 1570;1571;1572;1573;1574;1587;1591;1701;1702;1711;1735;0618 Jeremy D. Miller;Jaedyn Tonee diff --git a/src/JasperFx.SourceGenerator/InputParserGenerator.cs b/src/JasperFx.SourceGenerator/InputParserGenerator.cs index f7fa43b..74bbd6b 100644 --- a/src/JasperFx.SourceGenerator/InputParserGenerator.cs +++ b/src/JasperFx.SourceGenerator/InputParserGenerator.cs @@ -408,7 +408,7 @@ private static string GetConverterExpression(MemberParseInfo member) "global::System.Guid" => "s => global::System.Guid.Parse(s)", "global::System.DateTime" => "s => global::JasperFx.CommandLine.Internal.Conversion.DateTimeConverter.GetDateTime(s)", "global::System.TimeSpan" => "s => global::JasperFx.CommandLine.Internal.Conversion.TimeSpanConverter.GetTimeSpan(s)", - _ => $"new global::JasperFx.CommandLine.Internal.Conversion.Conversions().FindConverter(typeof({type}))!", + _ => $"s => ({type})new global::JasperFx.CommandLine.Internal.Conversion.Conversions().FindConverter(typeof({type}))!(s)", }; } diff --git a/src/JasperFx/CodeGeneration/DependencyGatherer.cs b/src/JasperFx/CodeGeneration/DependencyGatherer.cs index 18a76f3..be2668a 100644 --- a/src/JasperFx/CodeGeneration/DependencyGatherer.cs +++ b/src/JasperFx/CodeGeneration/DependencyGatherer.cs @@ -14,39 +14,87 @@ internal class DependencyGatherer public DependencyGatherer(IMethodVariables methodVariables, IList frames) { _methodVariables = methodVariables; - Dependencies.OnMissing = frame => new List(findDependencies(frame).Distinct()); - Variables.OnMissing = v => new List(findDependencies(v).Distinct()); + Dependencies.OnMissing = frame => + { + // Iterative BFS replaces the original co-recursive yield iterators that lacked cycle + // protection and stack-overflowed on deep Frame/Variable graphs (e.g. Lamar/Wolverine's + // resolver code-gen). Variables encountered along the way are pinned into the Variables + // cache via Fill so MethodFrameArranger.findInjectedFields (which reads Variables.Keys()) + // observes the same population the original recursive walk produced as a side effect. + var result = new List(); + var seenFrames = new HashSet { frame }; + var seenVariables = new HashSet(); + Walk(frame, null, result, seenFrames, seenVariables, top: frame); + return result; + }; + Variables.OnMissing = v => + { + var result = new List(); + var seenFrames = new HashSet(); + var seenVariables = new HashSet { v }; + Walk(null, v, result, seenFrames, seenVariables, top: null); + return result; + }; foreach (var frame in frames) Dependencies.FillDefault(frame); } - - private IEnumerable findDependencies(Frame frame) + private void Walk(Frame? startFrame, Variable? startVariable, List result, + HashSet seenFrames, HashSet seenVariables, Frame? top) { - frame.ResolveVariables(_methodVariables); + var frameQueue = new Queue(); + var variableQueue = new Queue(); + if (startFrame != null) frameQueue.Enqueue(startFrame); + if (startVariable != null) variableQueue.Enqueue(startVariable); - foreach (var dependency in frame.Dependencies) + while (frameQueue.Count > 0 || variableQueue.Count > 0) { - yield return dependency; + while (frameQueue.Count > 0) + { + var f = frameQueue.Dequeue(); + f.ResolveVariables(_methodVariables); - foreach (var child in Dependencies[dependency]) yield return child; - } + foreach (var dep in f.Dependencies) + { + if (seenFrames.Add(dep)) + { + if (!ReferenceEquals(dep, top)) result.Add(dep); + frameQueue.Enqueue(dep); + } + } - foreach (var variable in frame.Uses) - foreach (var dependency in Variables[variable]) - yield return dependency; - } + foreach (var v in f.Uses) + { + if (seenVariables.Add(v)) + { + // Pin the key so findInjectedFields' Variables.Keys() lookup sees it. The + // value here is a placeholder; the next caller to read Variables[v] + // through the indexer triggers a fresh BFS via OnMissing. + Variables.Fill(v, EmptyList); + variableQueue.Enqueue(v); + } + } + } - private IEnumerable findDependencies(Variable variable) - { - if (variable.Creator != null) - { - yield return variable.Creator; - foreach (var frame in Dependencies[variable.Creator]) yield return frame; - } + if (variableQueue.Count == 0) break; + var variable = variableQueue.Dequeue(); + + if (variable.Creator != null && seenFrames.Add(variable.Creator)) + { + if (!ReferenceEquals(variable.Creator, top)) result.Add(variable.Creator); + frameQueue.Enqueue(variable.Creator); + } - foreach (var dependency in variable.Dependencies) - foreach (var frame in Variables[dependency]) - yield return frame; + foreach (var d in variable.Dependencies) + { + if (seenVariables.Add(d)) + { + Variables.Fill(d, EmptyList); + variableQueue.Enqueue(d); + } + } + } } -} \ No newline at end of file + + private static readonly List EmptyList = new(0); +}