Skip to content

chore: Bump Imposter from 0.1.11 to 0.2.23 - #883

Merged
mergify[bot] merged 1 commit into
mainfrom
dependabot/nuget/Imposter-0.2.23
Oct 12, 2026
Merged

mergify[bot] merged 1 commit into
mainfrom
dependabot/nuget/Imposter-0.2.23

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Oct 12, 2026

Copy link
Copy Markdown
Contributor

Updated Imposter from 0.1.11 to 0.2.23.

Release notes

Sourced from Imposter's releases.

0.2.23

Fixes

  • Targets nested in generic types (#​362, closes #​337). A target registered open inside a generic type, such as typeof(Outer<>.Inner), got an imposter that referred to the containing type's type parameters without declaring them (CS0246). The imposter now declares them, outermost first, followed by the target's own type parameters, with their constraints, so Outer<T>.Inner<U> gets InnerImposter<T, U>. A closed registration such as typeof(Outer<int>.Inner) keeps its own imposter and can sit beside the open one. The generics docs cover it with an executable example.

0.2.22

Fixes

  • Members named with escaped keywords (#​363, closes #​359). A member declared with an escaped keyword name, such as @​class, @​event or @​object, was written bare in the imposter and failed to compile. Such names are now escaped wherever generated code uses them.
  • Same-signature methods that differ only in allows ref struct (#​365, closes #​356). Two interfaces' methods with the same signature, where only one type parameter allows ref structs, got two implicit implementations (CS0111, CS0425). They're now implemented explicitly, like methods whose constraints differ in other ways.
  • A type parameter named like the imposter class (#​366, closes #​354). A target such as IService<IServiceImposter> generated class IServiceImposter<IServiceImposter> (CS0694). The imposter keeps its name, and the type parameter takes another one in generated code.

Documentation

  • Member types from other source generators (#​367). A member whose type another source generator produces keeps working: the type is written through and binds in the final compilation, as long as it's referenced fully qualified. An event whose delegate type comes from another generator is left out of the imposter. Its workaround is documented, and regression tests cover both cases.

Maintenance

  • The generator dropped some dead code and renamed a check (#​360, #​361). Generated output is unchanged.

0.2.21

Fixes

  • Generic method type parameters named like a namespace (#​364, closes #​348). A generic method whose type parameter had the name of a namespace in its signature or in generated code, such as System, Collections, Generic, Tasks, Imposter, Abstractions or your own namespace, or the name global, got an imposter that failed to compile (CS0234, CS0400, CS0432). The imposter now renames only the type parameter itself in those types, and leaves namespaces and the global:: alias as they are.

0.2.20

Fixes

  • Type parameters named var, _, record or partial (#​357, closes #​319). A target or method type parameter with one of these names made the imposter fail to compile:

    • var and _ turned the generated locals and discards into the type parameter;
    • a generated declaration that starts with record or, from C# 14, partial read as a record or a partial member.

    Such a type parameter now takes another name in generated code, one that no type parameter, parameter or member beside it uses. You still write the imposter and its setups with your own type arguments, as before.

Maintenance

  • Generated code now passes in arguments with in in every place that forwards them (#​352, closes #​322), and the generator dropped some dead code and duplicated logic (#​351, #​353, #​355). The generated imposters behave the same.

0.2.19

Fixes

  • Members that share a name across kinds (#​346, closes #​328). A target that gets a method, a property or an event of one name from different interfaces, such as a Current property and a Current() method, got an imposter that failed with CS0102. These members now take apart names on the imposter, like same-kind members already did. The target's own member names are unchanged.
  • Re-abstracted ref-returning members (#​347, closes #​325). A ref-returning interface member with a default body that a derived interface re-abstracts left the target without an implementation. The imposter then failed with CS0535 and no diagnostic. Such a member is now handled like an abstract one: IMP011 is reported and no imposter is generated.
  • Generic method type parameters named like the method's generated types (#​345, closes #​339). A method type parameter named like one of the types generated for its method made the imposter fail to compile. Examples are Get<GetDelegate>, Get<IGetMethodImposterBuilder> and Get<GetMethodImposterCollection> (CS0694, CS1503), and Get<Builder> or Get<Adapter> (CS0102). The method's generated types and nested classes now take other names in that case.

0.2.18

Fixes

  • Events whose type doesn't bind (#​341, closes #​320). An event whose delegate type couldn't be resolved, as happens in code being edited (for example System.Action shadowed by a type named System), or an event declared with a non-delegate type, made the generator report IMP005 and drop that target's imposter. With a Debug build of the generator, every imposter of the project was dropped. Such an event is now left out of the imposter, the compiler's own error stays the one to fix, and the rest of the imposter is generated.

0.2.17

Fixes

  • Closed generic registrations (#​336, closes #​318 and #​329). A closed registration such as typeof(IRepository<int>) now generates the open type's imposter, IRepositoryImposter<T>, as IMP006 and the docs already said:
    • Under C# 14 it failed to compile (CS9295), because its Imposter() extension was declared for IRepository<int>.
    • Registering one generic type more than once, closed and closed or closed and open, reported IMP007 and generated nothing. The registrations now share the one imposter, and each closed one still reports IMP006.
    • Change: with putInTheSameNamespace: false, a closed registration's imposter now goes into the open type's namespace, such as Imposters.Sample.IRepository_T_ instead of Imposters.Sample.IRepository_int_.
    • A type nested in a generic class keeps the containing type's type arguments: typeof(Outer<int>.Inner) generates as before and no longer reports IMP006.
  • Type parameters named like the imposter's own types (#​338, closes #​330). A target such as IRepo<GetDelegate>, whose type parameter has the name of a type the imposter generates, failed to compile. Those types are now renamed, as they already were for a member of that name.

0.2.16

Fixes

  • Targets whose names differ only in letter case (#​334, closes #​316). Registering two targets such as IFoo and Ifoo made the generator fail with CS8785, and no imposter of the project was generated. Roslyn compares generated file names ignoring case, so the two files clashed. Such targets' files now get a hash of the target's name in their file name, as generic targets' files already do. Every other file keeps its name.

Performance

  • Calls allocate less (#​321, closes #​315). Every call to an impersonated method, property or indexer used to allocate an enumerator to look for callbacks, even when none were registered. It now skips the lookup when there are none:
    • a property getter call no longer allocates;
    • a method call allocates 144 bytes instead of 216;
    • a property setter call 32 instead of 104;
    • an indexer getter call 104 instead of 176;
    • an indexer setter call 64 instead of 144.

0.2.15

Performance

  • Less memory per generation (#​314, closes #​313). The generator worked out every method's setup signature for every target, to keep numbered setups such as Get_1() clear of other setup names. Most targets have no numbered setup, so it now does that only when one needs it. Generating 400 methods allocates 1.3% less (2.78 MB); the generated code is unchanged.

0.2.14

Performance

  • Leaner indexer setters (#​310, closes #​309). Every indexer's default behaviour took a base setter that was always null, and checked it on each set that reached it. The setter already calls the base setter itself, so that parameter and check are gone. Behaviour is unchanged.

0.2.13

Fixes

  • Events whose delegate returns dynamic and takes a ref dynamic (#​308, closes #​307). An event such as delegate dynamic DynHandler(dynamic x, ref dynamic y) failed to compile (CS0019, CS8716). It now compiles.

Performance

  • Imposters of members with dynamic types no longer use the runtime binder (#​308, closes #​307). An imposter kept dynamic arguments, results and values as dynamic, so its own calls with them were dynamic call sites, bound by Microsoft.CSharp's runtime binder on first use. That covered matching a setup and formatting a verification message. It now keeps them as object, and those calls bind when the imposter compiles. What you write against keeps dynamic: Arg<dynamic> in setups and Raised, and the Returns, Callback, Throws and indexer delegates.

0.2.12

Fixes

  • Methods of two interfaces that differ only in object and dynamic (#​303, closes #​301). Two interfaces' Use(object) and Use(dynamic), or Use(List<object>) and Use(List<dynamic>), have the same signature at runtime. The imposter didn't treat them as colliding and failed with CS0111. They're now implemented apart and set up through their interfaces' setup views.

  • Members with dynamic types (#​306, closes #​304). These failed to compile because a dynamic argument binds the call at runtime:

    • a method returning dynamic (CS8716);
    • a class's virtual method, property or indexer with a dynamic parameter, value or key, with its base implementation (CS1971, CS1976, CS1977, CS1972);
    • a class constructor taking dynamic (CS1975);
    • a generic method with a dynamic parameter (CS1962).

    Generated code now passes dynamic values between its own members as object. Setups (Arg<dynamic>, Returns, callbacks) keep dynamic.

Diagnostics

  • IMP013: member with a pointer type (#​305, closes #​302). A member whose signature uses a pointer or function pointer type, such as int*, int*[] or delegate*<int, void>, generated an imposter with dozens of compile errors and no explanation. Such a target now reports IMP013 and gets no imposter.
    • A class constructor that takes a pointer is left out of the imposter's constructors.
    • A class whose accessible constructors all take one reports IMP013.

0.2.11

Features

  • Methods whose type parameter allows ref structs (#​299, closes #​201). A method with where T : allows ref struct (C# 13) no longer reports IMP009. A value of T may be a ref struct, so the imposter passes it through, whatever the type argument:
    • Setups and verification take matchers for the other parameters only, as in imposter.Measure<Gauge>(Arg<int>.Any()).Returns((value, scale) => value.Level * scale).
    • The delegates passed to Returns, Callback and Throws, and the base implementation, get the value. A result of type T comes only from a Returns delegate or the base implementation.
    • A setup applies to calls with the same type arguments only.
    • This needs the Roslyn 4.14 compiler or later (.NET SDK 9.0.300, Visual Studio 17.14). Older compilers still report IMP009 for such a method.

This completes #​201. Still IMP009, and documented in Limitations:

  • a ref struct indexer key taken by in;
  • a class indexer of a ref struct type with an in or ref readonly key and a base getter;
  • a ref struct argument of an async event;
  • a span returned by reference (IMP011 covers ref returns).

Fixes

  • Interface overloads that differ only in their type parameter count (#​300, closes #​298). void Use() next to void Use<T>() got numbered setups, so imposter.Use<int>() didn't compile; it had to be imposter.Use_1<int>(). Such overloads now keep their names.
  • Generic methods that name their type parameters differently (#​300, closes #​298). Two interfaces' Use<T>(T) and Use<U>(U), or a method hiding an inherited one under another type parameter name, failed with CS0111. They're now set up apart, through their interfaces' setup views.

0.2.10

Features

  • Indexer keys of a custom ref struct type (#​297, part of #​201). A key of a ref struct type, passed by value, no longer reports IMP009. An imposter can't keep or match a ref struct key, so it passes the key through:
    • Setups and verification match the other keys only, as in imposter[Arg<int>.Any()].Getter().Returns((row, locator) => row + locator.Offset).
    • The delegates passed to Returns, Callback and Throws, and the base implementation, get the key.
    • The default behaviour keeps a value set by the other keys, whatever the ref struct key.
    • An indexer whose keys are all ref structs, or whose other keys are another indexer's, is set up by a method named after it, such as imposter.Indexer() or imposter.Indexer_1(). Its interface's setup view declares the same method.

Still IMP009 (see Limitations):

  • a ref struct indexer key taken by in;
  • a class indexer of a ref struct type with an in or ref readonly key and a base getter;
  • a ref struct whose type uses a method's type parameters;
  • a ref struct argument of an async event;
  • type parameters that allow ref structs;
  • a span returned by reference.

0.2.9

Features

Members with a custom ref struct type, part of #​201. These no longer report IMP009. An imposter can't keep or match a ref struct, so it only passes one between the caller and your delegates:

  • Methods with ref struct parameters (#​286).
    • Setups and verification take matchers for the other parameters only, and the invocation history leaves the argument out.
    • The delegates passed to Returns, Callback and Throws, and the base implementation, get the argument, by reference for ref and out.
    • Overloads that differ only in their ref struct parameters get numbered setups, such as Read_1.
  • Methods that return a ref struct (#​287).
    • Returns takes only a delegate, as in Returns(size => new Slot(size)).
    • The invocation history doesn't record the result.
    • Without a setup, the method returns default.
  • Events whose delegate takes a ref struct (#​288). Raise passes it on to the callbacks and the handlers, and Raised matches the other arguments.
  • Properties of a ref struct type (#​290, #​292).
    • The getter's Returns takes only a delegate.
    • Setter() takes no criteria, and Called counts every set.
    • Without a setup, a class property reads its base getter each time, and any other property returns default.
  • Indexers of a ref struct type (#​296).
    • The getter's Returns takes only the delegate that gets the keys.
    • The setter's history and Called leave the value out.
    • Without a setup, the getter returns the base getter's value or default.

Still IMP009 (see Limitations):

  • a ref struct indexer key;
  • a class indexer of a ref struct type with an in or ref readonly key and a base getter;
  • a ref struct whose type uses a method's type parameters;
  • a ref struct argument of an async event;
  • type parameters that allow ref structs;
  • a span returned by reference.

Fixes

  • Generated types named like another member (#​293, closes #​289). The imposter declares types named after each member, such as ICurrentPropertyBuilder or GetDelegate. Two cases made the imposter fail with CS0102:

    • a target member named like one of those types;
    • two members whose names derive the same type name, such as Foo and FooCallback.

    The member whose types clash now names them after another unique name. Its setup keeps its name.

  • Event and indexer builder fields named like another member (#​295, closes #​294). A property named _Changed next to an event Changed, or _IndexerIndexer next to an indexer, failed with CS0102. Those fields now avoid the other members' names, as a property's already did.

0.2.8

Features

  • Members with a scoped parameter that return a span or take one by ref or out (#​281, part of #​201). These no longer report IMP009. The same goes for a params span, which is scoped implicitly, and for an indexer with a span value and a scoped or params key. Generated code repeats scoped wherever it declares the parameter again. C# doesn't infer scoped for a lambda that leaves out its parameter types, so a lambda passed to Returns for such a member declares them: .Returns((scoped ReadOnlySpan<char> text) => ...).
  • Event spans by ref or out, and spans of async events (#​285, part of #​201). Raise passes a ref or out span by reference. For an async delegate, RaiseAsync takes the array the span covers, and the handlers get a span over it.

Still IMP009 (see Limitations): custom ref structs, type parameters that allow ref structs, a span returned by reference, and a span an async event's delegate takes by ref, out or ref readonly.

Fixes

  • Events whose delegate takes a ref or out parameter (#​284, closes #​282). Their imposters compile now. A handler's change to a ref argument reaches the raiser, and an out argument starts as the default.

Internal

The generator's code was reorganised without changing what it generates:

  • the property and indexer accessors' EnsureConfigured, verification and constructor builders are shared (#​279, closes #​231);
  • field-storing constructors are built with one helper (#​283, closes #​280).

0.2.7

Features

  • Spans in properties, indexers and events (part of #​201). These no longer report IMP009:

    • Properties of type Span<T> or ReadOnlySpan<T> (#​224). The imposter keeps the value as the array of its elements, and Returns takes that array.
    • Indexers whose keys or value are spans (#​248). Keys are matched with SpanArg<T>/ReadOnlySpanArg<T>.
    • Events whose delegate takes a span by value (#​260). Raising one copies the span's elements into the history, and Raised matches them with SpanArg<T>/ReadOnlySpanArg<T>.

    Still IMP009 (see Limitations):

    • custom ref structs;
    • spans returned by reference;
    • a span an async event's delegate takes, or one an event's delegate takes by ref or out;
    • the scoped cases;
    • type parameters that allow ref structs.

New diagnostics

Each of these targets used to get an imposter that didn't compile, with no diagnostic. Now it reports an error and gets no imposter:

  • IMP011: a method, property or indexer that returns by ref or ref readonly (#​244, closes #​216).
  • IMP012: an interface with a static abstract member that the interface doesn't implement (#​223, closes #​217).
  • IMP009: a method whose type parameter has the C# 13 allows ref struct anti-constraint (#​222, closes #​218).

Fixes

  • Classes with required members (#​219, closes #​215). Their imposters now compile and leave the required members at their defaults.
  • Static members of interface targets (#​221, closes #​220). They're no longer impersonated. A static member is always called through the interface, so its setup never took effect.
  • Event raise methods and Raised keep the delegate's nullable annotations (#​265, closes #​258). For an EventHandler, Raise(null, EventArgs.Empty) no longer warns CS8625.
  • A generic method parameter named it (#​245, closes #​241). Its arguments criteria no longer fail with CS1061.
  • Same-named targets of different arity on C# 14, such as IFoo and IFoo<T> (#​246, closes #​242). Their Imposter() extension classes no longer collide.
  • A target type parameter named TException (#​268, closes #​264). The builders' Throws<TException>() no longer shadows it (CS0693).

Changes to be aware of

  • Arg<object> for an object? event parameter now warns. With nullable reference types enabled, passing Arg<object> where the delegate's parameter is object? (an EventHandler's sender, for example) warns CS8620, as it already did for methods. Use Arg<object?>. The same applies to handler criteria for delegates such as Func<object?, EventArgs, Task>.
  • Setups for an interface's static members are gone (see #​221). They compiled but never had an effect.

Internal

The generator's code was reorganised without changing what it generates. The snapshot tests confirm the output is byte-identical:

  • shared builder helpers and metadata (#​249, #​250, #​251, #​253, #​259, #​263, #​266, #​267, #​269, #​270, #​271, #​274, #​276, #​277);
  • names read from metadata instead of string literals (#​254, #​256, #​261);
  • long builder methods split up (#​257, #​272, #​273, #​275);
  • one vocabulary for the method feature's invocation imposter groups (#​278);
  • dead code removed (#​247, #​255);
  • comments and the IMP009 description brought up to date (#​252).

0.2.6

Features

  • Span parameters and returns (part of #​201). Methods that take Span<T> or ReadOnlySpan<T> (by value, in, ref readonly, ref or out), or return one by value, no longer report IMP009:

    • The imposter copies the elements a span argument arrives with into an array.
    • New matchers in Imposter.Abstractions:
      • SpanArg<T> and ReadOnlySpanArg<T> (Is(elements), Is(elements, comparer), Is(predicate), Any());
      • OutSpanArg<T> and OutReadOnlySpanArg<T> for out spans.
    • Delegates passed to Returns and Callback receive the span itself, by reference for ref and out.
    • For a span result, Returns takes the array the span covers.
    • PRs: #​205, #​208, #​213, #​214.

    Spans in properties, indexers and events, and custom ref structs, still report IMP009 (see Limitations).

Fixes

  • Overloads that differ only in passing a parameter by reference (#​210, closes #​209). Overloads such as Count(int) and Count(in int) got setups with the same signature, and the imposter didn't compile:
    • the overload declared first keeps the name;
    • the others get a numbered setup (Count_1), on the imposter and in the setup views.
  • Awaitable methods with ref, out or in parameters (#​212, closes #​211). ReturnsAsync, ThrowsAsync and the default result of such methods didn't compile (CS1988).

Docs

  • Code links that opened the wrong test or line, and snippets that no longer matched their tests, are fixed (#​207, closes #​206).

0.2.5

Performance

  • Unchanged imposters are reused across incremental runs (#​178, closes #​115; groundwork in #​152, #​175, #​176, #​177). The generator now builds each imposter from value models instead of Roslyn symbols, so an edit that doesn't change a target reuses its imposter. For 20 small imposters, a rerun after an unrelated edit went from 103 ms and 39.6 MB to 5.3 ms and 0.64 MB.

Diagnostics

  • IMP007: two registered targets that would generate the same imposter type, such as same-named interfaces nested in different classes, now report IMP007 instead of over 200 compile errors (#​164, closes #​128).
  • IMP008: a class target with abstract members your project can't override (internal or private protected members of another assembly) now reports IMP008 instead of generating an imposter that can't compile (#​173, closes #​148).
  • IMP009: a target whose impersonated members use Span<T>, ReadOnlySpan<T> or another ref struct in their signature now reports IMP009, naming the member and the type, instead of generating code with compile errors. The limitation is documented on the Limitations page (#​202, closes #​132).
  • IMP003 now stops generation for projects older than C# 9, the help links of all diagnostics point to the new Diagnostics page, and the unreachable IMP001 was retired (#​142, closes #​114).

Fixes

  • .NET Standard 2.0 and .NET Framework projects (#​166, closes #​131). Generated imposters compile for projects targeting .NET Standard 2.0 and later and .NET Framework 4.7.2 and later.
  • Virtual calls during construction (#​156, closes #​126). A target constructor calling a virtual member threw NullReferenceException inside the imposter. Such calls now run the base implementation or return defaults (see Limitations).
  • Init-only properties (#​155, closes #​130). Properties with init accessors generated ordinary setters that didn't compile.
  • Concurrent first calls (#​157, closes #​117). Concurrent callers could miss a configured outcome or see sequenced outcomes out of order, for methods, property getters and indexer getters.
  • Generic class methods:
    • base methods whose type parameters appear only in the return type, or not at all, are called with explicit type arguments (#​158, closes #​123);
    • overrides restate only the constraints C# allows (#​159, closes #​125).
  • Restricted accessors (#​162, closes #​122). Properties and indexers with a protected or otherwise restricted accessor are overridden legally, and accessors the project can't override are left alone.
  • Colliding inherited indexers (#​163, closes #​127). Indexers from different interfaces with the same parameter types are implemented explicitly.
  • Event handler order (#​165, closes #​129). Raising an event calls handlers in the order a C# event does, also after a handler is removed and added again.
  • Parameter modifiers:
    • in indexer parameters with base implementations (#​168, closes #​140);
    • async events whose delegate has in parameters (#​170, closes #​169);
    • ref readonly parameters (#​172, closes #​167).
  • Nullable annotations (#​174, closes #​161). Generated helpers keep T?, string? and nullable constraint annotations, so Returns((string?)null) compiles with nullable warnings as errors.
  • Name collisions in generated code. A target member, parameter or type parameter whose name matched something the generator declares broke the imposter. These names are now chosen around the target's names:
    • the instance's imposter field and the imposter builder fields (#​182, closes #​181; #​185, closes #​183);
    • constructor parameters named invocationBehavior or imposter (#​188, closes #​184);
    • event delegate parameters named like the raise methods' locals (callback, handler, task, pendingTasks) or the event builder's members (#​187, closes #​186; #​191, closes #​189);
    • event delegate parameters named Handler, Item1, Rest and other names C# reserves for tuple elements (#​199, closes #​190);
    • method parameters named like the method imposter's members, such as _callbacks or DefaultResultGenerator (#​194, closes #​192);
    • indexer parameters named like the indexer imposters' members (#​196, closes #​193), other or hash (#​197, closes #​195);
    • parameters named like the arguments classes or their members, such as Equals on an indexer or As on a generic method (#​200, closes #​198).

Internal

  • CI fails when the committed generated-file snapshots are out of date (#​154, closes #​153), and pull requests must be up to date with master before merging (#​203, closes #​160).
  • Code scanning ignores source-generator output, so it reports only checked-in code (#​204, closes #​171).
  • The property builder field is described by the builder type (#​180, closes #​179).

0.2.4

Fixes

  • Classes from other assemblies, including HttpMessageHandler (#​145, closes #​118). Overrides copied the member's declared accessibility, so a protected internal member from another assembly was overridden as protected internal (CS0507), and internal or private protected virtual members were overridden although the project cannot see them. Overrides now use protected in that case, and members and constructors the project cannot access are skipped. HttpMessageHandler can now be impersonated. A class whose constructors are all inaccessible reports IMP004.
  • Non-sealed overrides are impersonated (#​143, closes #​121). A method a class overrides without sealed had no setup or verification API, and calls always ran the real override. Behaviour change: such methods now behave like the class's other virtual methods. ToString, Equals and GetHashCode overrides keep their real behaviour.
  • Constructor parameter defaults (#​150, closes #​149). Defaults of nullable value types (decimal? d = 1.5m, E? e = E.B), NaN and infinities were copied into the imposter's constructors as code that did not compile.
  • Name collisions in generated code:
    • indexer parameters named like generated locals, such as arguments (#​137, closes #​105);
    • method parameters and type parameters named like generated members, such as methodDisplayName or Called (#​144, closes #​107);
    • types in the target's namespace named like types the imposter uses, such as Environment, Count or Exception (#​136, closes #​108).
  • Indexers with in parameters (#​134, closes #​106). The generated imposter failed with CS1615.

Internal

  • Verification builds keep the generated files in tests/Imposter.Tests/GeneratedFiles/<Roslyn version>/ up to date, so generator changes show up as diffs in pull requests (#​139, closes #​116).
  • The benchmarks project compiles again and is built in CI (#​141, #​147).
  • Parameters and their types are described by value models, the first step towards incremental caching (#​151, part of #​115). Generated code is unchanged.

0.2.3

Fixes

  • IMPOSTER_LOG now works from the NuGet package (#​135, closes #​113). The package did not make the property visible to the generator, so setting it had no effect. The log is also more useful now:

    • once per compilation, the language version and whether the static Imposter() extensions are generated;
    • one line per imposter, with its generated file and target.

    Set IMPOSTER_LOG=true and build with -v:d to see the IMPLOG001 lines (see Getting Started).

  • Keyword-named type parameters (#​119, closes #​109). Type parameters such as @​class or @​in were written without the @, so the generated file did not compile.

  • Async event callbacks run before handlers (#​120, closes #​110). Behaviour change: RaiseAsync now runs the Callback(...) callbacks before the subscribed handlers, the same order as Raise. Before, it awaited the handlers first.

  • Event verification messages name the event once (#​124, closes #​111). A failed subscription verification printed the event name twice. Message text change: update tests that assert on the exact text.

New diagnostic

  • IMP006 (warning): closed generic target (#​133, closes #​112). Registering a closed generic type such as typeof(IRepository<User>) generates the same generic imposter as typeof(IRepository<>); the type arguments have no effect. The generator now warns and suggests the open registration. With TreatWarningsAsErrors, switch to the open registration or suppress IMP006.

Package

  • 37% smaller (#​86, closes #​85). The Roslyn 4.5, 4.7 and 4.11 generator builds were dropped because they compiled to the same code as the 4.4 build. Compilers 4.5–4.13 now load the 4.4 build. Generated code is unchanged.
  • The Roslyn 5.0 build uses the stable Microsoft.CodeAnalysis 5.0.0 (#​84, closes #​83) instead of a prerelease.

Internal

  • Generator code cleanup, with byte-identical generated output (#​89, #​90, #​92, #​95, #​96, #​98, #​100, #​102, #​104).

0.2.2

Fixes

  • Attributes applied through an alias are now found on older compilers (#​82, closes #​81). The generator build used by Roslyn 4.0–4.3 hosts (for example the .NET 6 SDK and Visual Studio 2022 up to 17.3) only recognised [GenerateImposter] by the name as written. When every attribute in a project went through an alias (using Generate = Imposter.Abstractions.GenerateImposterAttribute;, or a global using alias), no imposter was generated. Newer compilers were not affected.

Performance

  • Faster attribute discovery on Roslyn 4.0–4.3 hosts (#​82). Discovery no longer re-reads every assembly attribute for each [GenerateImposter], so its cost grows linearly instead of with the square of the attribute count. With 800 attributes, it drops from about 480 ms and 138 MB per run to about 3 ms and 2 MB.

0.2.1

Fixes

  • Generated file names no longer collide (#​80, closes #​79). This is a regression in 0.2.0. A generic target and a type named like its sanitized form got the same generated file name, e.g. IFoo<int> and IFoo_int_ both got IFoo_int_Imposter.g.cs. Roslyn then dropped every file of the generator, so no imposter was generated for the project (warning CS8785).
    • Plain names such as Sample.IFooImposter.g.cs are unchanged.
    • Names of generic targets, or names that contain _, now end with a short stable hash, e.g. Sample.IRepository_TEntity_Imposter.6b1618b2.g.cs.
    • Generated namespaces and type names are unchanged.

0.2.0

Fixes

  • Generic method calls no longer slow down over time (#​61, closes #​60). An unconfigured call to a generic method stored a new imposter that could never match, and every later call scanned all of them. After a few thousand calls, each call cost about 0.5 ms. A miss now returns an imposter that is not stored, and the lookup is a plain loop instead of a LINQ chain.

  • Indexer keys are compared with EqualityComparer<T>.Default (#​63, closes #​62). The generated indexer arguments compared keys with ==. That caused two problems:

    • It did not compile for unconstrained generic keys or for structs without == (CS0019).
    • It used reference equality for classes that override Equals, so the default indexer behaviour could not read back a value stored under an equal key.
  • Async methods are recognised on netstandard2.0 and .NET Framework (#​65, closes #​64). Task, Task<T> and ValueTask<T> were only recognised when they came from System.Runtime, System.Private.CoreLib or mscorlib. On netstandard2.0, and for ValueTask<T> on .NET Framework, the builders lacked ReturnsAsync/ThrowsAsync. Task types are now detected by name and namespace.

  • Cancelled builds no longer report a generator crash (#​75, closes #​74). A build cancelled while an imposter was being generated reported IMP005. The IDE then kept a result without the imposter until the next edit. Cancellation now propagates.

  • Generated file names no longer depend on attribute order (#​77, closes #​76). Two targets with the same name in different namespaces got IFooImposter.g.cs and IFooImposter_1.g.cs in processing order, so reordering the attributes swapped the files. Each file is now named after its fully qualified target, e.g. Sample.IFooImposter.g.cs, or Imposters.Sample.IFooImposter.g.cs for the dedicated namespace.

Performance

  • Faster generator with lower memory use (#​67, #​69, #​71):
    • Symbol-derived syntax is cached in metadata.
    • Method metadata is no longer boxed.
    • Generated code is written in a single pass over its tokens instead of through NormalizeWhitespace.
    • For the 21 imposters in our sample set, generation went from about 1.2 s and 452 MB allocated to about 0.33 s and 168 MB.
  • Smaller generated code (#​73). Members that were never used are no longer emitted, including:
    • stored imposter references in method builders;
    • an unused invocation behaviour field;
    • indexer getter invocation counters;
    • accessor members that the indexer or property does not have.

Changes to generated output

  • Generated files use tab indentation and LF line endings (#​71). The layout is otherwise unchanged.
  • Generated file names include the target's namespace (#​77). If you inspect the files through EmitCompilerGeneratedFiles, they now appear under the new names.

Commits viewable in compare view.

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

---
updated-dependencies:
- dependency-name: Imposter
  dependency-version: 0.2.23
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Oct 12, 2026
@github-actions

Copy link
Copy Markdown

Test Results

    24 files  ±0      24 suites  ±0   12m 11s ⏱️ +10s
 4 529 tests ±0   4 527 ✅ ±0  2 💤 ±0  0 ❌ ±0 
28 684 runs  ±0  28 680 ✅ ±0  4 💤 ±0  0 ❌ ±0 

Results for commit 69f5d0a. ± Comparison against base commit 7dc398a.

@mergify
mergify Bot merged commit c4bcdeb into main Oct 12, 2026
16 checks passed
@mergify
mergify Bot deleted the dependabot/nuget/Imposter-0.2.23 branch October 12, 2026 22:11
@github-actions

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 371.4 ns 14.42 ns 13.49 ns 0.77 1.93 KB 1.00
Mockolate 1 483.6 ns 11.52 ns 10.78 ns 1.00 1.93 KB 1.00
Imposter 1 532.0 ns 9.49 ns 8.88 ns 1.10 3.96 KB 2.05
TUnitMocks 1 479.3 ns 14.48 ns 13.54 ns 0.99 2.02 KB 1.05
Moq 1 184,397.0 ns 696.83 ns 617.72 ns 381.52 14.58 KB 7.55
NSubstitute 1 6,414.3 ns 40.43 ns 35.84 ns 13.27 9.12 KB 4.72
FakeItEasy 1 5,979.5 ns 41.06 ns 36.40 ns 12.37 8.05 KB 4.17
baseline* 10 699.7 ns 14.38 ns 13.45 ns 1.05 2.14 KB 1.00
Mockolate 10 664.8 ns 4.23 ns 3.96 ns 1.00 2.14 KB 1.00
Imposter 10 1,216.3 ns 24.20 ns 22.63 ns 1.83 4.8 KB 2.24
TUnitMocks 10 1,361.3 ns 4.70 ns 3.67 ns 2.05 3.73 KB 1.74
Moq 10 189,744.0 ns 689.56 ns 645.02 ns 285.41 18.48 KB 8.63
NSubstitute 10 9,405.1 ns 139.15 ns 130.16 ns 14.15 12.07 KB 5.64
FakeItEasy 10 9,411.3 ns 122.41 ns 114.50 ns 14.16 15.42 KB 7.20
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
INTEL XEON PLATINUM 8573C 2.30GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 354.5 ns 10.44 ns 9.77 ns 0.93 1.7 KB 1.00
Mockolate 382.7 ns 1.55 ns 1.45 ns 1.00 1.7 KB 1.00
Imposter 1,106.2 ns 8.85 ns 7.84 ns 2.89 7.18 KB 4.22
TUnitMocks 252.3 ns 2.44 ns 2.17 ns 0.66 1.34 KB 0.79
Moq 14,490.2 ns 36.49 ns 30.47 ns 37.87 12.51 KB 7.34
NSubstitute 5,455.9 ns 41.05 ns 38.40 ns 14.26 9.05 KB 5.31
FakeItEasy 127,040.9 ns 1,338.15 ns 1,251.71 ns 331.98 15.22 KB 8.94
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 200.8 ns 3.73 ns 3.11 ns 0.59 1.57 KB 1.00
Mockolate 342.7 ns 11.81 ns 11.05 ns 1.00 1.57 KB 1.00
Imposter 539.1 ns 14.98 ns 14.02 ns 1.57 2.37 KB 1.51
TUnitMocks 567.3 ns 10.15 ns 9.00 ns 1.66 2 KB 1.27
Moq 98,754.6 ns 654.22 ns 611.96 ns 288.41 8.88 KB 5.66
NSubstitute 4,872.7 ns 33.39 ns 27.89 ns 14.23 7.71 KB 4.91
FakeItEasy 4,865.2 ns 65.62 ns 61.38 ns 14.21 6.81 KB 4.33
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
AMD EPYC 9V45 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Indexer N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 534.1 ns 13.69 ns 12.14 ns 1.13 3.77 KB 1.00
Mockolate 1 473.5 ns 4.15 ns 3.68 ns 1.00 3.77 KB 1.00
Imposter 1 414.7 ns 3.92 ns 3.28 ns 0.88 4.99 KB 1.33
Moq 1 64,793.7 ns 849.40 ns 794.53 ns 136.83 20.39 KB 5.41
NSubstitute 1 5,056.7 ns 88.94 ns 78.84 ns 10.68 12.84 KB 3.41
FakeItEasy 1 5,127.1 ns 105.46 ns 88.06 ns 10.83 13.63 KB 3.62
baseline* 10 1,756.3 ns 43.32 ns 40.53 ns 1.14 4.82 KB 1.00
Mockolate 10 1,546.6 ns 18.63 ns 17.42 ns 1.00 4.82 KB 1.00
Imposter 10 740.1 ns 9.32 ns 8.72 ns 0.48 6.47 KB 1.34
Moq 10 67,028.3 ns 915.14 ns 811.25 ns 43.34 29.89 KB 6.20
NSubstitute 10 12,394.3 ns 349.63 ns 309.94 ns 8.01 25.63 KB 5.32
FakeItEasy 10 10,888.1 ns 148.02 ns 131.21 ns 7.04 32.97 KB 6.84
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
AMD EPYC 9V74 3.65GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 18.54 ns 0.372 ns 0.348 ns 1.32 160 B 1.00
Mockolate 14.00 ns 0.333 ns 0.311 ns 1.00 160 B 1.00
Imposter 232.71 ns 4.347 ns 3.853 ns 16.63 2240 B 14.00
TUnitMocks 29.82 ns 0.365 ns 0.341 ns 2.13 200 B 1.25
Moq 1,104.98 ns 7.402 ns 6.924 ns 78.95 2096 B 13.10
NSubstitute 1,574.00 ns 13.242 ns 12.387 ns 112.46 5048 B 31.55
FakeItEasy 1,497.99 ns 47.638 ns 44.561 ns 107.02 2763 B 17.27
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.5 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.401
[Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Property N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 336.8 ns 8.11 ns 7.58 ns 0.82 2.41 KB 1.00
Mockolate 1 411.3 ns 8.43 ns 7.89 ns 1.00 2.41 KB 1.00
Imposter 1 320.2 ns 6.71 ns 6.28 ns 0.78 2.98 KB 1.23
TUnitMocks 1 465.6 ns 15.65 ns 14.64 ns 1.13 1.64 KB 0.68
Moq 1 8,038.4 ns 191.05 ns 178.71 ns 19.55 10.39 KB 4.30
NSubstitute 1 5,535.9 ns 119.54 ns 111.82 ns 13.46 11.45 KB 4.74
FakeItEasy 1 5,944.5 ns 226.16 ns 211.55 ns 14.46 11.24 KB 4.66
baseline* 10 737.5 ns 6.49 ns 5.42 ns 0.89 2.91 KB 1.00
Mockolate 10 825.9 ns 15.08 ns 13.37 ns 1.00 2.91 KB 1.00
Imposter 10 509.1 ns 13.68 ns 12.80 ns 0.62 3.26 KB 1.12
TUnitMocks 10 1,287.9 ns 31.94 ns 29.88 ns 1.56 3.94 KB 1.35
Moq 10 13,482.8 ns 292.89 ns 273.97 ns 16.33 18.28 KB 6.29
NSubstitute 10 12,573.7 ns 317.62 ns 297.10 ns 15.23 21.08 KB 7.25
FakeItEasy 10 13,876.2 ns 472.46 ns 441.94 ns 16.80 30.81 KB 10.60

baseline* rows show the corresponding Mockolate benchmark from the most recent successful main branch build with results, for regression comparison.

github-actions Bot added a commit that referenced this pull request Oct 12, 2026
github-actions Bot added a commit that referenced this pull request Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants