diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index f13abf88..b9c7dcf5 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -1201,16 +1201,15 @@ property.IndexerParameters is not null sb.Append("\t\t\t\tif (").Append(mockRegistry).Append(".Wraps is not ").Append(className).Append(" wraps)").AppendLine(); sb.Append("\t\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t\treturn ").Append(mockRegistry).Append(".GetIndexer<").AppendTypeOrWrapper(property.Type) - .Append(">(").Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .Append(").GetResult(() => ") + sb.Append("\t\t\t\t\treturn ").Append(mockRegistry); + AppendGetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.Append(".GetResult(() => ") .AppendDefaultValueGeneratorFor(property.Type, $"this.{mockRegistryName}.Behavior.DefaultValue") .Append(");").AppendLine(); sb.Append("\t\t\t\t}").AppendLine(); - sb.Append("\t\t\t\tvar ").Append(indexerResultVarName).Append(" = this.").Append(mockRegistryName).Append(".GetIndexer<") - .AppendTypeOrWrapper(property.Type).Append(">(") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .AppendLine(");"); + sb.Append("\t\t\t\tvar ").Append(indexerResultVarName).Append(" = this.").Append(mockRegistryName); + AppendGetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.AppendLine(";"); sb.Append("\t\t\t\tvar ").Append(baseResultVarName).Append(" = wraps[") .Append(FormatIndexerParametersAsNames(property.IndexerParameters.Value)).Append("];") .AppendLine(); @@ -1243,10 +1242,9 @@ property.IndexerParameters is not null Helpers.GetUniqueLocalVariableName("indexerResult", property.IndexerParameters.Value); string baseResultVarName = Helpers.GetUniqueLocalVariableName("baseResult", property.IndexerParameters.Value); - sb.Append("\t\t\t\tvar ").Append(indexerResultVarName).Append(" = this.").Append(mockRegistryName).Append(".GetIndexer<") - .AppendTypeOrWrapper(property.Type).Append(">(") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .AppendLine(");"); + sb.Append("\t\t\t\tvar ").Append(indexerResultVarName).Append(" = this.").Append(mockRegistryName); + AppendGetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.AppendLine(";"); sb.Append("\t\t\t\tif (!").Append(indexerResultVarName).Append(".SkipBaseClass)").AppendLine(); sb.Append("\t\t\t\t{").AppendLine(); if (property.Getter?.IsProtected != true) @@ -1292,10 +1290,9 @@ property.IndexerParameters is not null } else if (property is { IsIndexer: true, IndexerParameters: not null, }) { - sb.Append("\t\t\t\treturn this.").Append(mockRegistryName).Append(".GetIndexer<") - .AppendTypeOrWrapper(property.Type).Append(">(") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .Append(").GetResult(() => ") + sb.Append("\t\t\t\treturn this.").Append(mockRegistryName); + AppendGetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.Append(".GetResult(() => ") .AppendDefaultValueGeneratorFor(property.Type, $"this.{mockRegistryName}.Behavior.DefaultValue") .Append(");").AppendLine(); } @@ -1326,11 +1323,9 @@ property.IndexerParameters is not null { if (property is { IsIndexer: true, IndexerParameters: not null, }) { - sb.Append("\t\t\t\tthis.").Append(mockRegistryName).Append(".SetIndexer<") - .Append(property.Type.Fullname) - .Append(">(value, ") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .Append(");").AppendLine(); + sb.Append("\t\t\t\tthis.").Append(mockRegistryName); + AppendSetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.Append(";").AppendLine(); sb.Append("\t\t\t\tif (").Append(mockRegistry).Append(".Wraps is ").Append(className).Append(" wraps)").AppendLine(); sb.Append("\t\t\t\t{").AppendLine(); @@ -1356,10 +1351,9 @@ property.IndexerParameters is not null { if (!isClassInterface && !property.IsAbstract) { - sb.Append("\t\t\t\tif (!this.").Append(mockRegistryName).Append(".SetIndexer<").Append(property.Type.Fullname) - .Append(">(value, ") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)).Append("))") - .AppendLine(); + sb.Append("\t\t\t\tif (!this.").Append(mockRegistryName); + AppendSetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.Append(")").AppendLine(); sb.Append("\t\t\t\t{").AppendLine(); if (property.Setter?.IsProtected != true) { @@ -1387,11 +1381,9 @@ property.IndexerParameters is not null } else { - sb.Append("\t\t\t\tthis.").Append(mockRegistryName).Append(".SetIndexer<") - .Append(property.Type.Fullname) - .Append(">(value, ") - .Append(FormatIndexerParametersAsNameOrWrapper(property.IndexerParameters.Value)) - .AppendLine(");"); + sb.Append("\t\t\t\tthis.").Append(mockRegistryName); + AppendSetIndexerCall(sb, property.Type, property.IndexerParameters.Value); + sb.AppendLine(";"); } } else diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.cs index 6449ed60..5e14d596 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis; using Mockolate.SourceGenerators.Entities; using Mockolate.SourceGenerators.Internals; +using Type = Mockolate.SourceGenerators.Entities.Type; namespace Mockolate.SourceGenerators.Sources; @@ -82,6 +83,86 @@ private static string FormatIndexerParametersAsNameOrWrapper(EquatableArray $"new global::Mockolate.Parameters.NamedParameterValue<{p.ToTypeOrWrapper()}>(\"{p.Name}\", {p.ToNameOrWrapper()})")); + /// + /// Appends a typed GetIndexer call, using the typed overload for 1–4 parameters + /// and falling back to the params INamedParameterValue[] overload otherwise. + /// + private static void AppendGetIndexerCall( + StringBuilder sb, Type propertyType, EquatableArray parameters) + { + bool useTypedOverload = parameters.Count is >= 1 and <= MaxExplicitParameters; + sb.Append(".GetIndexer<").AppendTypeOrWrapper(propertyType); + if (useTypedOverload) + { + foreach (Type? type in parameters.Select(p => p.Type)) + { + sb.Append(", ").AppendTypeOrWrapper(type); + } + } + + sb.Append(">("); + if (useTypedOverload) + { + bool first = true; + foreach (MethodParameter p in parameters) + { + if (!first) + { + sb.Append(", "); + } + + sb.Append('"').Append(p.Name).Append("\", ").Append(p.ToNameOrWrapper()); + first = false; + } + } + else + { + sb.Append(FormatIndexerParametersAsNameOrWrapper(parameters)); + } + + sb.Append(')'); + } + + /// + /// Appends a typed SetIndexer call, using the typed overload for 1–4 parameters + /// and falling back to the params INamedParameterValue[] overload otherwise. + /// + private static void AppendSetIndexerCall( + StringBuilder sb, Type propertyType, EquatableArray parameters) + { + bool useTypedOverload = parameters.Count is >= 1 and <= MaxExplicitParameters; + sb.Append(".SetIndexer<").Append(propertyType.Fullname); + if (useTypedOverload) + { + foreach (Type? type in parameters.Select(p => p.Type)) + { + sb.Append(", ").AppendTypeOrWrapper(type); + } + } + + sb.Append(">(value, "); + if (useTypedOverload) + { + bool first = true; + foreach (MethodParameter p in parameters) + { + if (!first) + { + sb.Append(", "); + } + + sb.Append('"').Append(p.Name).Append("\", ").Append(p.ToNameOrWrapper()); + first = false; + } + } + else + { + sb.Append(FormatIndexerParametersAsNameOrWrapper(parameters)); + } + + sb.Append(')'); + } + /// /// Formats indexer parameters as comma-separated names. /// @@ -147,9 +228,13 @@ private static void AppendNamedValueParameter(StringBuilder sb, MethodParameter { sb.Append(paramRef).Append(" is null ? \"null\" : "); if (parameter.Type.IsFormattable) + { sb.Append("((global::System.IFormattable)").Append(paramRef).Append(").ToString(null, global::System.Globalization.CultureInfo.InvariantCulture)"); + } else + { sb.Append(paramRef).Append(".ToString()"); + } } else if (parameter.Type.IsFormattable) { diff --git a/Source/Mockolate/MockRegistry.Interactions.cs b/Source/Mockolate/MockRegistry.Interactions.cs index 00c60e2e..fee19af1 100644 --- a/Source/Mockolate/MockRegistry.Interactions.cs +++ b/Source/Mockolate/MockRegistry.Interactions.cs @@ -433,6 +433,80 @@ private PropertySetup ResolvePropertySetup( return existingSetup; } + /// + /// Gets the value from the indexer with one typed parameter. + /// Matching runs against the typed value directly; is only + /// allocated for recording. + /// + public IndexerSetupResult GetIndexer(string p1Name, T1 p1) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1); + INamedParameterValue[] parameters = [new NamedParameterValue(p1Name, p1)]; + IndexerGetterAccess interaction = new(parameters); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + return new IndexerSetupResult(matchingSetup, interaction, Behavior, GetIndexerValue, + Setup.Indexers.UpdateValue); + } + + /// + /// Gets the value from the indexer with two typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + public IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2) + ]; + IndexerGetterAccess interaction = new(parameters); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + return new IndexerSetupResult(matchingSetup, interaction, Behavior, GetIndexerValue, + Setup.Indexers.UpdateValue); + } + + /// + /// Gets the value from the indexer with three typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + public IndexerSetupResult GetIndexer( + string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2, p3Name, p3); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2), + new NamedParameterValue(p3Name, p3) + ]; + IndexerGetterAccess interaction = new(parameters); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + return new IndexerSetupResult(matchingSetup, interaction, Behavior, GetIndexerValue, + Setup.Indexers.UpdateValue); + } + + /// + /// Gets the value from the indexer with four typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + public IndexerSetupResult GetIndexer( + string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2, p3Name, p3, p4Name, p4); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2), + new NamedParameterValue(p3Name, p3), + new NamedParameterValue(p4Name, p4) + ]; + IndexerGetterAccess interaction = new(parameters); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + return new IndexerSetupResult(matchingSetup, interaction, Behavior, GetIndexerValue, + Setup.Indexers.UpdateValue); + } + /// /// Gets the value from the indexer with the given parameters. /// @@ -446,6 +520,100 @@ public IndexerSetupResult GetIndexer(params INamedParameterVal Setup.Indexers.UpdateValue); } + /// + /// Sets the value of the indexer with one typed parameter. + /// Matching runs against the typed value directly; is only + /// allocated for recording. + /// + /// + /// Returns a flag, indicating whether the base class implementation should be skipped. + /// + public bool SetIndexer(TResult value, string p1Name, T1 p1) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1); + INamedParameterValue[] parameters = [new NamedParameterValue(p1Name, p1)]; + IndexerSetterAccess interaction = new(parameters, new NamedParameterValue("value", value)); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + + Setup.Indexers.UpdateValue(parameters, value); + matchingSetup?.InvokeSetter(interaction, value, Behavior); + return (matchingSetup as IInteractiveIndexerSetup)?.SkipBaseClass() ?? Behavior.SkipBaseClass; + } + + /// + /// Sets the value of the indexer with two typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + /// + /// Returns a flag, indicating whether the base class implementation should be skipped. + /// + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2) + ]; + IndexerSetterAccess interaction = new(parameters, new NamedParameterValue("value", value)); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + + Setup.Indexers.UpdateValue(parameters, value); + matchingSetup?.InvokeSetter(interaction, value, Behavior); + return (matchingSetup as IInteractiveIndexerSetup)?.SkipBaseClass() ?? Behavior.SkipBaseClass; + } + + /// + /// Sets the value of the indexer with three typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + /// + /// Returns a flag, indicating whether the base class implementation should be skipped. + /// + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, + string p3Name, T3 p3) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2, p3Name, p3); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2), + new NamedParameterValue(p3Name, p3) + ]; + IndexerSetterAccess interaction = new(parameters, new NamedParameterValue("value", value)); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + + Setup.Indexers.UpdateValue(parameters, value); + matchingSetup?.InvokeSetter(interaction, value, Behavior); + return (matchingSetup as IInteractiveIndexerSetup)?.SkipBaseClass() ?? Behavior.SkipBaseClass; + } + + /// + /// Sets the value of the indexer with four typed parameters. + /// Matching runs against the typed values directly; is only + /// allocated for recording. + /// + /// + /// Returns a flag, indicating whether the base class implementation should be skipped. + /// + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, + string p3Name, T3 p3, string p4Name, T4 p4) + { + IndexerSetup? matchingSetup = GetIndexerSetupTyped(p1Name, p1, p2Name, p2, p3Name, p3, p4Name, p4); + INamedParameterValue[] parameters = [ + new NamedParameterValue(p1Name, p1), + new NamedParameterValue(p2Name, p2), + new NamedParameterValue(p3Name, p3), + new NamedParameterValue(p4Name, p4) + ]; + IndexerSetterAccess interaction = new(parameters, new NamedParameterValue("value", value)); + ((IMockInteractions)Interactions).RegisterInteraction(interaction); + + Setup.Indexers.UpdateValue(parameters, value); + matchingSetup?.InvokeSetter(interaction, value, Behavior); + return (matchingSetup as IInteractiveIndexerSetup)?.SkipBaseClass() ?? Behavior.SkipBaseClass; + } + /// /// Sets the value of the indexer with the given parameters. /// diff --git a/Source/Mockolate/MockRegistry.Setup.cs b/Source/Mockolate/MockRegistry.Setup.cs index 33934486..c14902b2 100644 --- a/Source/Mockolate/MockRegistry.Setup.cs +++ b/Source/Mockolate/MockRegistry.Setup.cs @@ -132,6 +132,104 @@ bool Predicate(MethodSetup setup) } } + /// + /// Retrieves the latest indexer setup that matches the single typed parameter, + /// or returns if no matching setup is found. + /// + private IndexerSetup? GetIndexerSetupTyped(string n1, T1 v1) + { + IndexerGetterAccess? fallback = null; + return Setup.Indexers.GetLatestOrDefault(Predicate); + + [DebuggerNonUserCode] + bool Predicate(IndexerSetup setup) + { + if (setup is ITypedIndexerMatch typed) + { + return typed.MatchesTyped(n1, v1); + } + + fallback ??= new IndexerGetterAccess([new NamedParameterValue(n1, v1)]); + return ((IInteractiveIndexerSetup)setup).Matches(fallback); + } + } + + /// + /// Retrieves the latest indexer setup that matches the two typed parameters, + /// or returns if no matching setup is found. + /// + private IndexerSetup? GetIndexerSetupTyped(string n1, T1 v1, string n2, T2 v2) + { + IndexerGetterAccess? fallback = null; + return Setup.Indexers.GetLatestOrDefault(Predicate); + + [DebuggerNonUserCode] + bool Predicate(IndexerSetup setup) + { + if (setup is ITypedIndexerMatch typed) + { + return typed.MatchesTyped(n1, v1, n2, v2); + } + + fallback ??= new IndexerGetterAccess([ + new NamedParameterValue(n1, v1), + new NamedParameterValue(n2, v2)]); + return ((IInteractiveIndexerSetup)setup).Matches(fallback); + } + } + + /// + /// Retrieves the latest indexer setup that matches the three typed parameters, + /// or returns if no matching setup is found. + /// + private IndexerSetup? GetIndexerSetupTyped(string n1, T1 v1, string n2, T2 v2, string n3, T3 v3) + { + IndexerGetterAccess? fallback = null; + return Setup.Indexers.GetLatestOrDefault(Predicate); + + [DebuggerNonUserCode] + bool Predicate(IndexerSetup setup) + { + if (setup is ITypedIndexerMatch typed) + { + return typed.MatchesTyped(n1, v1, n2, v2, n3, v3); + } + + fallback ??= new IndexerGetterAccess([ + new NamedParameterValue(n1, v1), + new NamedParameterValue(n2, v2), + new NamedParameterValue(n3, v3)]); + return ((IInteractiveIndexerSetup)setup).Matches(fallback); + } + } + + /// + /// Retrieves the latest indexer setup that matches the four typed parameters, + /// or returns if no matching setup is found. + /// + private IndexerSetup? GetIndexerSetupTyped( + string n1, T1 v1, string n2, T2 v2, string n3, T3 v3, string n4, T4 v4) + { + IndexerGetterAccess? fallback = null; + return Setup.Indexers.GetLatestOrDefault(Predicate); + + [DebuggerNonUserCode] + bool Predicate(IndexerSetup setup) + { + if (setup is ITypedIndexerMatch typed) + { + return typed.MatchesTyped(n1, v1, n2, v2, n3, v3, n4, v4); + } + + fallback ??= new IndexerGetterAccess([ + new NamedParameterValue(n1, v1), + new NamedParameterValue(n2, v2), + new NamedParameterValue(n3, v3), + new NamedParameterValue(n4, v4)]); + return ((IInteractiveIndexerSetup)setup).Matches(fallback); + } + } + /// /// Gets the indexer value for the given . /// diff --git a/Source/Mockolate/Setup/ITypedIndexerMatch.cs b/Source/Mockolate/Setup/ITypedIndexerMatch.cs new file mode 100644 index 00000000..1e6e31a9 --- /dev/null +++ b/Source/Mockolate/Setup/ITypedIndexerMatch.cs @@ -0,0 +1,31 @@ +namespace Mockolate.Setup; + +/// +/// Matches an indexer access by typed parameters, without boxing through +/// . +/// +internal interface ITypedIndexerMatch +{ + /// + /// Checks if the single parameter value matches. + /// + bool MatchesTyped(string n1, T1 v1); + + /// + /// Checks if the two parameter values match. + /// + bool MatchesTyped(string n1, T1 v1, string n2, T2 v2); + + /// + /// Checks if the three parameter values match. + /// + bool MatchesTyped(string n1, T1 v1, string n2, T2 v2, string n3, T3 v3); + +#pragma warning disable S107 // Methods should not have too many parameters + /// + /// Checks if the four parameter values match. + /// + bool MatchesTyped(string n1, T1 v1, string n2, T2 v2, string n3, T3 v3, + string n4, T4 v4); +#pragma warning restore S107 // Methods should not have too many parameters +} diff --git a/Source/Mockolate/Setup/IndexerSetup.cs b/Source/Mockolate/Setup/IndexerSetup.cs index f3d23983..9ea14a60 100644 --- a/Source/Mockolate/Setup/IndexerSetup.cs +++ b/Source/Mockolate/Setup/IndexerSetup.cs @@ -125,6 +125,32 @@ protected abstract void GetInitialValue(MockBehavior behavior, Func defaul /// protected static string FormatType(Type type) => type.FormatType(); + + /// + /// Checks if the given matches the typed , + /// using when available to avoid boxing. + /// + protected static bool MatchesParameter(NamedParameter namedParameter, string name, T value) + { + if (!string.IsNullOrEmpty(name) && + !namedParameter.Name.Equals(name, StringComparison.Ordinal)) + { + return false; + } + + if (namedParameter.Parameter is ITypedParameter typed) + { + return typed.MatchesValue(namedParameter.Name, value); + } + + return namedParameter.Parameter.Matches(new NamedParameterValue(name, value)); + } + + /// + /// Invokes the callbacks of the given with the typed . + /// + protected static void InvokeCallbacksParameter(NamedParameter namedParameter, string name, T value) + => namedParameter.Parameter.InvokeCallbacks(new NamedParameterValue(name, value)); } /// @@ -135,7 +161,7 @@ protected static string FormatType(Type type) #endif public class IndexerSetup(NamedParameter match1) : IndexerSetup, IIndexerSetupCallbackBuilder, IIndexerSetupReturnBuilder, - IIndexerGetterSetup, IIndexerSetterSetup + IIndexerGetterSetup, IIndexerSetterSetup, ITypedIndexerMatch { private readonly List>> _getterCallbacks = []; private readonly List>> _returnCallbacks = []; @@ -553,6 +579,29 @@ protected override void ExecuteSetterCallback(IndexerSetterAccess indexerSett protected override bool IsMatch(INamedParameterValue[] parameters) => Matches([match1,], parameters); + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1) + { + if (!MatchesParameter(match1, n1, v1)) + { + return false; + } + + InvokeCallbacksParameter(match1, n1, v1); + return true; + } + + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1, string n2, TActual2 v2) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3, string n4, TActual4 v4) => false; + /// protected override void GetInitialValue(MockBehavior behavior, Func defaultValueGenerator, INamedParameterValue[] parameters, @@ -579,7 +628,7 @@ protected override void GetInitialValue(MockBehavior behavior, Func defaul #endif public class IndexerSetup(NamedParameter match1, NamedParameter match2) : IndexerSetup , IIndexerSetupCallbackBuilder, IIndexerSetupReturnBuilder, - IIndexerGetterSetup, IIndexerSetterSetup + IIndexerGetterSetup, IIndexerSetterSetup, ITypedIndexerMatch { private readonly List>> _getterCallbacks = []; private readonly List>> _returnCallbacks = []; @@ -1002,6 +1051,30 @@ protected override void ExecuteSetterCallback(IndexerSetterAccess indexerSett protected override bool IsMatch(INamedParameterValue[] parameters) => Matches([match1, match2,], parameters); + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1, string n2, TActual2 v2) + { + if (!MatchesParameter(match1, n1, v1) || !MatchesParameter(match2, n2, v2)) + { + return false; + } + + InvokeCallbacksParameter(match1, n1, v1); + InvokeCallbacksParameter(match2, n2, v2); + return true; + } + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3, string n4, TActual4 v4) => false; + /// protected override void GetInitialValue(MockBehavior behavior, Func defaultValueGenerator, INamedParameterValue[] parameters, @@ -1033,7 +1106,7 @@ public class IndexerSetup( NamedParameter match2, NamedParameter match3) : IndexerSetup, IIndexerSetupCallbackBuilder, IIndexerSetupReturnBuilder, - IIndexerGetterSetup, IIndexerSetterSetup + IIndexerGetterSetup, IIndexerSetterSetup, ITypedIndexerMatch { private readonly List>> _getterCallbacks = []; private readonly List>> _returnCallbacks = []; @@ -1462,6 +1535,32 @@ protected override void ExecuteSetterCallback(IndexerSetterAccess indexerSett protected override bool IsMatch(INamedParameterValue[] parameters) => Matches([match1, match2, match3,], parameters); + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1, string n2, TActual2 v2) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3) + { + if (!MatchesParameter(match1, n1, v1) || !MatchesParameter(match2, n2, v2) || + !MatchesParameter(match3, n3, v3)) + { + return false; + } + + InvokeCallbacksParameter(match1, n1, v1); + InvokeCallbacksParameter(match2, n2, v2); + InvokeCallbacksParameter(match3, n3, v3); + return true; + } + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3, string n4, TActual4 v4) => false; + /// protected override void GetInitialValue(MockBehavior behavior, Func defaultValueGenerator, INamedParameterValue[] parameters, @@ -1497,7 +1596,7 @@ public class IndexerSetup( NamedParameter match4) : IndexerSetup, IIndexerSetupCallbackBuilder, IIndexerSetupReturnBuilder, - IIndexerGetterSetup, IIndexerSetterSetup + IIndexerGetterSetup, IIndexerSetterSetup, ITypedIndexerMatch { private readonly List>> _getterCallbacks = []; private readonly List>> _returnCallbacks = []; @@ -1931,6 +2030,33 @@ protected override void ExecuteSetterCallback(IndexerSetterAccess indexerSett protected override bool IsMatch(INamedParameterValue[] parameters) => Matches([match1, match2, match3, match4,], parameters); + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped(string n1, TActual1 v1, string n2, TActual2 v2) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3) => false; + + /// + bool ITypedIndexerMatch.MatchesTyped( + string n1, TActual1 v1, string n2, TActual2 v2, string n3, TActual3 v3, string n4, TActual4 v4) + { + if (!MatchesParameter(match1, n1, v1) || !MatchesParameter(match2, n2, v2) || + !MatchesParameter(match3, n3, v3) || !MatchesParameter(match4, n4, v4)) + { + return false; + } + + InvokeCallbacksParameter(match1, n1, v1); + InvokeCallbacksParameter(match2, n2, v2); + InvokeCallbacksParameter(match3, n3, v3); + InvokeCallbacksParameter(match4, n4, v4); + return true; + } + /// protected override void GetInitialValue(MockBehavior behavior, Func defaultValueGenerator, INamedParameterValue[] parameters, diff --git a/Source/Mockolate/Setup/MockSetups.Indexers.cs b/Source/Mockolate/Setup/MockSetups.Indexers.cs index 711355fc..88a752fa 100644 --- a/Source/Mockolate/Setup/MockSetups.Indexers.cs +++ b/Source/Mockolate/Setup/MockSetups.Indexers.cs @@ -72,6 +72,28 @@ private List GetOrCreateStorage() return null; } + public IndexerSetup? GetLatestOrDefault(Predicate predicate) + { + List? storage = _storage; + if (storage is null) + { + return null; + } + + lock (storage) + { + for (int i = storage.Count - 1; i >= 0; i--) + { + if (predicate(storage[i])) + { + return storage[i]; + } + } + } + + return null; + } + /// [ExcludeFromCodeCoverage] public override string ToString() diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt index 0c68d368..93af2330 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt @@ -185,6 +185,10 @@ namespace Mockolate public void AddEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public void ClearAllInteractions() { } public Mockolate.Setup.IndexerSetupResult GetIndexer(params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public TResult GetProperty(string propertyName, System.Func defaultValueGenerator, System.Func? baseValueAccessor) { } public System.Collections.Generic.IReadOnlyCollection GetUnusedSetups(Mockolate.Interactions.MockInteractions interactions) { } public Mockolate.Verify.VerificationResult Indexer(T subject, params Mockolate.Parameters.NamedParameter[] parameters) { } @@ -208,6 +212,10 @@ namespace Mockolate public Mockolate.Verify.VerificationResult Property(T subject, string propertyName, Mockolate.Parameters.IParameter value) { } public void RemoveEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public bool SetIndexer(TResult value, params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public bool SetProperty(string propertyName, T value) { } public void SetupEvent(Mockolate.Setup.EventSetup eventSetup) { } public void SetupIndexer(Mockolate.Setup.IndexerSetup indexerSetup) { } @@ -1406,7 +1414,9 @@ namespace Mockolate.Setup protected abstract bool? GetSkipBaseClass(); protected abstract bool IsMatch(Mockolate.Parameters.INamedParameterValue[] parameters); protected static string FormatType(System.Type type) { } + protected static void InvokeCallbacksParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool Matches(Mockolate.Parameters.NamedParameter[] namedParameters, Mockolate.Parameters.INamedParameterValue[] values) { } + protected static bool MatchesParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool TryCast([System.Diagnostics.CodeAnalysis.NotNullWhen(false)] object? value, out T result, Mockolate.MockBehavior behavior) { } } public class IndexerSetupResult diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt index 2cc89ff6..94ae8083 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt @@ -184,6 +184,10 @@ namespace Mockolate public void AddEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public void ClearAllInteractions() { } public Mockolate.Setup.IndexerSetupResult GetIndexer(params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public TResult GetProperty(string propertyName, System.Func defaultValueGenerator, System.Func? baseValueAccessor) { } public System.Collections.Generic.IReadOnlyCollection GetUnusedSetups(Mockolate.Interactions.MockInteractions interactions) { } public Mockolate.Verify.VerificationResult Indexer(T subject, params Mockolate.Parameters.NamedParameter[] parameters) { } @@ -207,6 +211,10 @@ namespace Mockolate public Mockolate.Verify.VerificationResult Property(T subject, string propertyName, Mockolate.Parameters.IParameter value) { } public void RemoveEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public bool SetIndexer(TResult value, params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public bool SetProperty(string propertyName, T value) { } public void SetupEvent(Mockolate.Setup.EventSetup eventSetup) { } public void SetupIndexer(Mockolate.Setup.IndexerSetup indexerSetup) { } @@ -1405,7 +1413,9 @@ namespace Mockolate.Setup protected abstract bool? GetSkipBaseClass(); protected abstract bool IsMatch(Mockolate.Parameters.INamedParameterValue[] parameters); protected static string FormatType(System.Type type) { } + protected static void InvokeCallbacksParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool Matches(Mockolate.Parameters.NamedParameter[] namedParameters, Mockolate.Parameters.INamedParameterValue[] values) { } + protected static bool MatchesParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool TryCast([System.Diagnostics.CodeAnalysis.NotNullWhen(false)] object? value, out T result, Mockolate.MockBehavior behavior) { } } public class IndexerSetupResult diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt index cf2cddf4..20c77081 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt @@ -171,6 +171,10 @@ namespace Mockolate public void AddEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public void ClearAllInteractions() { } public Mockolate.Setup.IndexerSetupResult GetIndexer(params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public Mockolate.Setup.IndexerSetupResult GetIndexer(string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public TResult GetProperty(string propertyName, System.Func defaultValueGenerator, System.Func? baseValueAccessor) { } public System.Collections.Generic.IReadOnlyCollection GetUnusedSetups(Mockolate.Interactions.MockInteractions interactions) { } public Mockolate.Verify.VerificationResult Indexer(T subject, params Mockolate.Parameters.NamedParameter[] parameters) { } @@ -194,6 +198,10 @@ namespace Mockolate public Mockolate.Verify.VerificationResult Property(T subject, string propertyName, Mockolate.Parameters.IParameter value) { } public void RemoveEvent(string name, object? target, System.Reflection.MethodInfo? method) { } public bool SetIndexer(TResult value, params Mockolate.Parameters.INamedParameterValue[] parameters) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3) { } + public bool SetIndexer(TResult value, string p1Name, T1 p1, string p2Name, T2 p2, string p3Name, T3 p3, string p4Name, T4 p4) { } public bool SetProperty(string propertyName, T value) { } public void SetupEvent(Mockolate.Setup.EventSetup eventSetup) { } public void SetupIndexer(Mockolate.Setup.IndexerSetup indexerSetup) { } @@ -1360,7 +1368,9 @@ namespace Mockolate.Setup protected abstract bool? GetSkipBaseClass(); protected abstract bool IsMatch(Mockolate.Parameters.INamedParameterValue[] parameters); protected static string FormatType(System.Type type) { } + protected static void InvokeCallbacksParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool Matches(Mockolate.Parameters.NamedParameter[] namedParameters, Mockolate.Parameters.INamedParameterValue[] values) { } + protected static bool MatchesParameter(Mockolate.Parameters.NamedParameter namedParameter, string name, T value) { } protected static bool TryCast([System.Diagnostics.CodeAnalysis.NotNullWhen(false)] object? value, out T result, Mockolate.MockBehavior behavior) { } } public class IndexerSetupResult diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs index 4fca9424..fc7dfc80 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs @@ -31,9 +31,9 @@ public interface IMyService await That(result.Sources).ContainsKey("Mock.IMyService.g.cs").WhoseValue .Contains( - "return this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue(\"indexerResult\", indexerResult)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!));") + "return this.MockRegistry.GetIndexer(\"indexerResult\", indexerResult).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!));") .IgnoringNewlineStyle().And - .Contains("this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue(\"indexerResult\", indexerResult));") + .Contains("this.MockRegistry.SetIndexer(value, \"indexerResult\", indexerResult);") .IgnoringNewlineStyle(); } @@ -71,15 +71,15 @@ public int this[int index] { if (this.MockRegistry.Wraps is not global::MyCode.IMyService wraps) { - return this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); + return this.MockRegistry.GetIndexer("index", index).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); } - var indexerResult = this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index)); + var indexerResult = this.MockRegistry.GetIndexer("index", index); var baseResult = wraps[index]; return indexerResult.GetResult(baseResult); } set { - this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue("index", index)); + this.MockRegistry.SetIndexer(value, "index", index); if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) { wraps[index] = value; @@ -95,9 +95,9 @@ public int this[int index] { if (this.MockRegistry.Wraps is not global::MyCode.IMyService wraps) { - return this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index), new global::Mockolate.Parameters.NamedParameterValue("isReadOnly", isReadOnly)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); + return this.MockRegistry.GetIndexer("index", index, "isReadOnly", isReadOnly).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); } - var indexerResult = this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index), new global::Mockolate.Parameters.NamedParameterValue("isReadOnly", isReadOnly)); + var indexerResult = this.MockRegistry.GetIndexer("index", index, "isReadOnly", isReadOnly); var baseResult = wraps[index, isReadOnly]; return indexerResult.GetResult(baseResult); } @@ -109,7 +109,7 @@ public int this[int index] { set { - this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue("index", index), new global::Mockolate.Parameters.NamedParameterValue("isWriteOnly", isWriteOnly)); + this.MockRegistry.SetIndexer(value, "index", index, "isWriteOnly", isWriteOnly); if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) { wraps[index, isWriteOnly] = value; @@ -163,7 +163,7 @@ public override int this[int index] { get { - var indexerResult = this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index)); + var indexerResult = this.MockRegistry.GetIndexer("index", index); if (!indexerResult.SkipBaseClass) { var baseResult = this.MockRegistry.Wraps is global::MyCode.MyService wraps ? wraps[index] : base[index]; @@ -173,7 +173,7 @@ public override int this[int index] } set { - if (!this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue("index", index))) + if (!this.MockRegistry.SetIndexer(value, "index", index)) { if (this.MockRegistry.Wraps is global::MyCode.MyService wraps) { @@ -193,7 +193,7 @@ public override int this[int index] { get { - var indexerResult = this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("index", index), new global::Mockolate.Parameters.NamedParameterValue("isReadOnly", isReadOnly)); + var indexerResult = this.MockRegistry.GetIndexer("index", index, "isReadOnly", isReadOnly); if (!indexerResult.SkipBaseClass) { var baseResult = base[index, isReadOnly]; @@ -209,7 +209,7 @@ public override int this[int index] { set { - if (!this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue("index", index), new global::Mockolate.Parameters.NamedParameterValue("isWriteOnly", isWriteOnly))) + if (!this.MockRegistry.SetIndexer(value, "index", index, "isWriteOnly", isWriteOnly)) { base[index, isWriteOnly] = value; } @@ -223,11 +223,86 @@ public override int this[int index] { get { - return this.MockRegistry.GetIndexer(new global::Mockolate.Parameters.NamedParameterValue("someAdditionalIndex", someAdditionalIndex)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); + return this.MockRegistry.GetIndexer("someAdditionalIndex", someAdditionalIndex).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); } set { - this.MockRegistry.SetIndexer(value, new global::Mockolate.Parameters.NamedParameterValue("someAdditionalIndex", someAdditionalIndex)); + this.MockRegistry.SetIndexer(value, "someAdditionalIndex", someAdditionalIndex); + } + } + """).IgnoringNewlineStyle(); + } + + [Fact] + public async Task ShouldSupportSpanAndReadOnlySpanIndexerParameters() + { + GeneratorResult result = Generator + .Run(""" + using System; + using Mockolate; + + namespace MyCode; + public class Program + { + public static void Main(string[] args) + { + _ = IMyService.CreateMock(); + } + } + + public interface IMyService + { + int this[Span buffer] { get; set; } + int this[ReadOnlySpan values] { get; set; } + } + """); + + await That(result.Sources).ContainsKey("Mock.IMyService.g.cs").WhoseValue + .Contains(""" + /// + public int this[global::System.Span buffer] + { + get + { + if (this.MockRegistry.Wraps is not global::MyCode.IMyService wraps) + { + return this.MockRegistry.GetIndexer>("buffer", new global::Mockolate.Setup.SpanWrapper(buffer)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); + } + var indexerResult = this.MockRegistry.GetIndexer>("buffer", new global::Mockolate.Setup.SpanWrapper(buffer)); + var baseResult = wraps[buffer]; + return indexerResult.GetResult(baseResult); + } + set + { + this.MockRegistry.SetIndexer>(value, "buffer", new global::Mockolate.Setup.SpanWrapper(buffer)); + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps[buffer] = value; + } + } + } + """).IgnoringNewlineStyle().And + .Contains(""" + /// + public int this[global::System.ReadOnlySpan values] + { + get + { + if (this.MockRegistry.Wraps is not global::MyCode.IMyService wraps) + { + return this.MockRegistry.GetIndexer>("values", new global::Mockolate.Setup.ReadOnlySpanWrapper(values)).GetResult(() => this.MockRegistry.Behavior.DefaultValue.Generate(default(int)!)); + } + var indexerResult = this.MockRegistry.GetIndexer>("values", new global::Mockolate.Setup.ReadOnlySpanWrapper(values)); + var baseResult = wraps[values]; + return indexerResult.GetResult(baseResult); + } + set + { + this.MockRegistry.SetIndexer>(value, "values", new global::Mockolate.Setup.ReadOnlySpanWrapper(values)); + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps[values] = value; + } } } """).IgnoringNewlineStyle(); diff --git a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.cs b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.cs index 45bb527b..5614f326 100644 --- a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.cs @@ -286,6 +286,52 @@ await That(Act).Throws() .WithMessage("The indexer [null, 1, 2] was accessed without prior setup."); } +#if NET8_0_OR_GREATER + [Fact] + public async Task WithReadOnlySpanIndexerParameters_ShouldCompile() + { + ReadOnlySpan readOnlySpan123 = ((int[])[1, 2, 3,]).AsSpan(); + ReadOnlySpan readOnlySpan456 = ((int[])[4, 5, 6,]).AsSpan(); + IMyServiceWithSpanIndexerParameters sut = IMyServiceWithSpanIndexerParameters.CreateMock(); + sut.Mock.Setup[It.IsReadOnlySpan(x => x[0] == 1)].InitializeWith(42); + + int result1 = sut[readOnlySpan123]; + int result2 = sut[readOnlySpan456]; + + await That(result1).IsEqualTo(42); + await That(result2).IsEqualTo(0); + await That(sut.Mock.Verify[It.IsReadOnlySpan(x => x[0] == 1)].Got()).Once(); + await That(sut.Mock.Verify[It.IsReadOnlySpan(x => x[0] == 4)].Got()).Once(); + await That(sut.Mock.Verify[It.IsReadOnlySpan(x => x[0] == 9)].Got()).Never(); + } +#endif + +#if NET8_0_OR_GREATER + [Fact] + public async Task WithSpanIndexerParameters_ShouldCompile() + { + Span fooSpan = "foo".ToCharArray().AsSpan(); + Span barSpan = "bar".ToCharArray().AsSpan(); + IMyServiceWithSpanIndexerParameters sut = IMyServiceWithSpanIndexerParameters.CreateMock(); + sut.Mock.Setup[It.IsSpan(x => x[0] == 'f')].InitializeWith(42); + + int result1 = sut[fooSpan]; + int result2 = sut[barSpan]; + + await That(result1).IsEqualTo(42); + await That(result2).IsEqualTo(0); + await That(sut.Mock.Verify[It.IsSpan(x => x[0] == 'f')].Got()).Once(); + await That(sut.Mock.Verify[It.IsSpan(x => x[0] == 'b')].Got()).Once(); + await That(sut.Mock.Verify[It.IsSpan(x => x[0] == 'x')].Got()).Never(); + } +#endif + + public interface IMyServiceWithSpanIndexerParameters + { + int this[Span buffer] { get; set; } + int this[ReadOnlySpan values] { get; set; } + } + public class IndexerWith1Parameter { [Fact]