fix: skip constructors with inaccessible parameter types - #846
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a source-generator compilation failure when emitting mock constructors whose parameter types are not nameable from the generated public surfaces (e.g., MockExtensionsForX), especially for protected nested types and protected internal nested types across assembly boundaries without InternalsVisibleTo. It centralizes and extends the generator’s “can this type be named from here?” check, and uses it to drop unusable constructors rather than generating uncompilable code.
Changes:
- Promote the generator’s conservative accessibility walk to
Helpers.IsAccessibleFrom(...)and extend it to recurse through array element types and generic type arguments. - Filter generated mock constructors to exclude any constructor whose parameter types are not accessible from the generated assembly.
- Add unit tests covering same-assembly and cross-assembly (
InternalsVisibleTo/ noInternalsVisibleTo) cases, including composed inaccessible types (arrays/generics).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Tests/Mockolate.SourceGenerators.Tests/MockTests.CrossAssemblyTests.cs | Adds cross-assembly regression tests for protected internal nested constructor parameter types with and without InternalsVisibleTo. |
| Tests/Mockolate.SourceGenerators.Tests/MockGeneratorTests.cs | Adds same-assembly regression tests ensuring inaccessible nested parameter types cause constructor omission (and mock omission when no constructors remain). |
| Source/Mockolate.SourceGenerators/Helpers.cs | Introduces Helpers.IsAccessibleFrom and reuses it for attribute emission filtering; adds recursion for arrays/pointers/generic arguments. |
| Source/Mockolate.SourceGenerators/Entities/MockClass.cs | Drops constructors whose parameter types are not accessible/nameable from the generated mock surfaces. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
🚀 Benchmark ResultsDetails
Details
Details
Details
Details
Details
|
3d7dd36 to
fdd6706
Compare
A constructor parameter type is named verbatim in `MockExtensionsForXXX`, which does not derive from the mocked type. When the type is only reachable through inheritance (a `protected` nested type, or a `protected internal` one across assemblies), the generated code failed to compile with CS0122 there and CS0051 on the generated `public` constructor. Such a constructor cannot be driven from the outside at all, so it is now dropped entirely: classes with another accessible constructor still generate a mock, and classes left without one fall into the existing `IsValidMockDeclaration` gate. The conservative accessibility walk that already guarded emitted attribute names is promoted to a shared `Helpers.IsAccessibleFrom`, extended to recurse through array element types and generic type arguments.
`IsAccessibleFrom` only inspected the type arguments of the innermost named type, so `Wrapper<ProtectedNested>.Inner` slipped through and still produced CS0051/CS0122. Both the declaration and the type arguments are now checked per nesting level. Apply the same rule to member signatures (return/member type, parameter types, generic constraint types), which the mock restates verbatim on surfaces that do not derive from the mocked type. A virtual member is dropped from the surface, an abstract one makes the type unmockable, mirroring how an inaccessible member is already handled. `MockabilityAnalyzer` reports Mockolate0002 for the same condition so the user gets a diagnostic instead of a missing mock.
fdd6706 to
65e0f69
Compare
Collapse the `<remarks>` blocks into their summaries and drop the restatements of C# accessibility rules, keeping only the CS-number rationale.
`IsSlotReachable` judged only the base declaration's own accessibility, so
folding `HasAccessibleSignature` into `IsOverridableFromMock` made a concrete
class unmockable whenever a base slot it already overrides names a type the
mock cannot restate:
public abstract class Base
{
protected abstract void Consume(Configuration configuration);
protected class Configuration { }
}
public class Derived : Base
{
protected override void Consume(Configuration configuration) { }
}
`Derived` has no obligation left, yet the generator emitted nothing while
`MockabilityAnalyzer` - which skips filled slots unconditionally - reported no
Mockolate0002, leaving only a bare CS0117 on `CreateMock()`. Teach the slot
check about the signature so both sides agree.
Also guard the analyzer's containing-assembly lookup, matching the
`HasInternalAccess` mirror in the generator.
|
…e parameter types (#846) by Valentin Breuß
…e parameter types (#846) by Valentin Breuß



A constructor parameter type is named verbatim in
MockExtensionsForXXX, which does not derive from the mocked type. When the type is only reachable through inheritance (aprotectednested type, or aprotected internalone across assemblies), the generated code failed to compile with CS0122 there and CS0051 on the generatedpublicconstructor. Such a constructor cannot be driven from the outside at all, so it is now dropped entirely: classes with another accessible constructor still generate a mock, and classes left without one fall into the existingIsValidMockDeclarationgate.The conservative accessibility walk that already guarded emitted attribute names is promoted to a shared
Helpers.IsAccessibleFrom, extended to recurse through array element types and generic type arguments.