Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Fixes

- Issue #28: IgnoreCase rejects multiple case-insensitive source matches even when one is exact; nested structural ambiguity retains LITEMAPPER1004. Exact and ExactThenIgnoreCase keep their existing exact-match priority. [#28](https://github.com/PrimordialCode/Mammoth.LiteMapper/issues/28)
- Issue #27: by-name enum exceptions use stable source names and paths instead of applying `nameof` to guarded expressions; nullable enum captures retain source metadata. [#27](https://github.com/PrimordialCode/Mammoth.LiteMapper/issues/27)
- Issue #26: structural collection mapping preserves nullable elements and applies Throw guards before object helpers; nullable nested collection helper signatures retain their annotations. [#26](https://github.com/PrimordialCode/Mammoth.LiteMapper/issues/26)
- Issue #25: cycle detection now forwards one context through acyclic object and collection bridges, retaining complete member paths without tracking the bridge objects. [#25](https://github.com/PrimordialCode/Mammoth.LiteMapper/issues/25)
Expand Down
7 changes: 7 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Mammoth.LiteMapper Decision Log

## Issue #28: apply the selected matching policy before exact priority (2026-10-09)

- Context: MatchSource selected a sole exact candidate even under IgnoreCase. Constructor matching already limits exact priority to Exact and ExactThenIgnoreCase. Structural helper planning also discarded an ambiguous match without reporting its specific member diagnostic.
- Decision: use the same exact-priority gate in member matching and report LITEMAPPER1004 at the nested target member before rejecting the structural plan. Preserve discovery, hidden-member resolution, explicit configuration, constructor matching, and method failure isolation.
- Contract: sections 6.5, 9.1-9.3, 10.3, 20.2-20.3, and 28.2 already require unique IgnoreCase matches and non-configurable ambiguity errors. No public API or specification semantics change is required.
- Evidence: 40 new generator cases on unchanged production yielded 22 failures and 18 controls, zero skips. Cases cover creation, updates, nested mappings, properties/fields, inherited class/interface members, option precedence, exact/unique controls, explicit paths, hiding, diagnostic location, and independent-method generation.

## Issue #27: separate enum exception metadata from executable expressions (2026-10-09)

- Context: configured nullable traversal renders coalescing/null-conditional expressions that are invalid nameof operands. Nullable enum conversion replaces source expressions with generated captures, leaking private names into exception metadata.
Expand Down
8 changes: 8 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Mammoth.LiteMapper Implementation Plan

## Issue #28: IgnoreCase member ambiguity (2026-10-09)

- Scope: apply unique ordinal-ignore-case matching without exact-match priority to automatic source members. Preserve Exact and ExactThenIgnoreCase, constructor matching, hidden-member selection, and explicit configuration precedence.
- Test-first: cover new-object, update, nested creation/update, properties, fields, class/interface inheritance, diagnostics and omitted invalid implementations. Retain constructor and successful exact/unique-match controls.
- Implementation: align the shared member matcher with the existing constructor matcher; preserve the specific ambiguity diagnostic when structural helper planning fails.
- Validation: focused/full generator, runtime/integration, source samples, available package/consumer/API/benchmark checks, independent review, and exact-head ordinary Windows/Linux and Roslyn CI.
- Boundary: issue #28 only, no public API or specification semantics change. No merge, release, tag, package publication, or workflow dispatch. Stop at draft PR/human review. AgentStack CLI is absent; authorized connector fallback adds/readbacks readiness labels without issue comments or claim writes.

## Issue #27: stable enum exception names (2026-10-09)

- Scope: separate by-name enum exception metadata from rendered value expressions. Preserve root parameter/direct source-member names; configured source paths remain identifiable even after null guards or captures.
Expand Down
10 changes: 10 additions & 0 deletions STATUS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Mammoth.LiteMapper Status

## GitHub issue #28 IgnoreCase ambiguity checkpoint (2026-10-09)

- Base: isolated cloud worktree from develop 14eaa974769554884d7f8ca71ff8821a6e19470f after PR #41 merged. Issue #28 was the oldest remaining P2, with no blocking dependencies, sub-issues, or competing PRs. Authorized exec:agent and ready:agent labels were added and read back. AgentStack CLI is absent; connector fallback is used without issue comments or claim writes.
- Red-first: final 40-case generator fixture yielded 22 failures and 18 controls on unchanged production, zero skips. An initial fixture also included two independently unsupported nested-interface cases; these were removed and replaced with constructor exact-policy controls before the final baseline. Four clean-package cases independently demonstrated successful compilation of ambiguous mappings on netstandard2.0/net8.0/net9.0/net10.0. A subsequent red run isolating direct/nested mapper containers was interrupted by automatic approval cancellation and produced no final TRX.
- Implementation: restrict member-matching exact priority to Exact and ExactThenIgnoreCase; preserve LITEMAPPER1004 at the nested target member when structural planning encounters ambiguity. Constructor matching, explicit configuration, hidden-member selection, failure isolation, and public API remain unchanged. Usage, consumer guidance, and changelog are synchronized.
- Verified core evidence: Release solution build has zero warnings/errors. All 799 generator tests pass, including 40 new cases; runtime five and integration one pass, zero failures/skips. The focused new/constructor/inheritance/root-converter set passes 94/94. C# 9 fixtures assert diagnostic severity/location, omitted invalid methods, retained independent methods, deterministic output, and runtime exact/unique-match controls. Independent read-only review, consumer-skill validation, and CRLF-aware diff checks pass.
- Verified package evidence: 224 passes, two infrastructure failures, zero skips in the available packaging suite. All four final isolated package ambiguity cases pass, rejecting invalid direct/nested mappings without generated implementations. All 12 successful framework/language combinations, clean-package samples, source samples/usage, package/API checks, and benchmark smoke pass.
- Local limits: trimming and Native AOT fail at ILLink ComputeManagedAssemblies task-host creation with MSB4216 / Unix-domain SocketException 13, before publish behavior is established. PowerShell and clang are absent; 49 PowerShell-dependent cases were not run locally. Workspace-local temporary files avoid the previous small-temp-filesystem failure. No warning, assertion, dependency, prerequisite, credential, or security setting was weakened. Exact-head ordinary Windows/Linux and Roslyn CI evidence belongs in the draft PR.
- Boundary: issue #28 only, then human review. The previously documented nullable-intermediate enum runtime defect and other issues remain outside scope. No user-computer access, merge, release, tag push, package publication, or workflow dispatch.

## GitHub issue #27 enum diagnostic-name checkpoint (2026-10-09)

- Base: isolated cloud worktree from develop 8d41533957c7b5b27943327c8b6b3eef171b4232 after PR #40 merged. Issue #27 was the oldest remaining P2; no blocked-by dependencies, sub-issues, or competing PRs. Authorized exec:agent and ready:agent labels were added and read back. AgentStack CLI is absent; connector fallback is used without issue comments or claim writes.
Expand Down
4 changes: 3 additions & 1 deletion docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,11 @@ Useful options:

- `NameMatching.Exact`: ordinal exact member names only.
- `NameMatching.ExactThenIgnoreCase`: exact first, then unique ignore-case match.
- `NameMatching.IgnoreCase`: unique ignore-case match.
- `NameMatching.IgnoreCase`: unique ordinal ignore-case match, without exact-match priority.
- `UnmappedMemberPolicy.Ignore`, `Info`, `Warning`, `Error`: controls diagnostics for unmapped source or target members.

If a source exposes both `Value` and `value`, automatic matching to target `Value` under `IgnoreCase` reports `LITEMAPPER1004` and omits the invalid implementation, including in updates and nested structural mappings. `Exact` and `ExactThenIgnoreCase` select the exact `Value` member. An explicit `MapProperty.Source` can select the intended member. Constructor parameters follow the same matching policy.

Member candidates are public instance properties and fields. Hidden members are resolved to the most-derived usable member, with property-over-field preference when needed.

Source paths in `[MapProperty]` may use dotted member paths such as `"Email.Value"`. Source paths must resolve to members and cannot use method-call syntax. Source methods are not discovered automatically; use a converter or root-source `MapProperty.Use` method when a method-derived value is needed.
Expand Down
2 changes: 2 additions & 0 deletions skills/mammoth-litemapper/references/mapping-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Options resolve method `[MappingOptions]`, mapper `[LiteMapper]`, assembly `[Lit

For an opt-in strict source-completeness boundary, set `UnmappedSourceMembers` to `Error` at mapper, method, or assembly scope. This reports unused readable source members as configurable `LITEMAPPER1003` diagnostics naming the member; use `Warning` or `Info` to stage adoption and `IgnoreSource` for intentional exclusions. The default remains `Ignore` because source models can contain fields that are not part of a given destination contract. Source completeness does not weaken target completeness or mandatory target errors.

Name matching uses ordinal comparisons. `Exact` accepts only an exact name; `ExactThenIgnoreCase` prefers exact and otherwise requires a unique case-insensitive match. `IgnoreCase` requires a unique case-insensitive match without exact priority: source `Value` plus `value` targeting `Value` reports non-configurable `LITEMAPPER1004`, including in updates and nested structural mapping. Explicit `MapProperty.Source` can disambiguate. Constructor parameters follow the same policy; hidden members are resolved before matching.

## Nulls and updates

- Nullable-to-non-null values default to a compile-time error. `NullableMismatchPolicy.Throw` requests runtime validation. Nullable source and nullable result preserve root null.
Expand Down
10 changes: 8 additions & 2 deletions src/Mammoth.LiteMapper.Generator/LiteMapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2235,7 +2235,13 @@ private static bool ReportDuplicateVisibleDefaults(IMethodSymbol mappingMethod,
}

var match = MatchSource(targetMember, sourceMembers, options.NameMatching);
if (match.Ambiguous || match.Member == null)
if (match.Ambiguous)
{
diagnostics.Add(Diagnostic.Create(Diagnostics.AmbiguousMemberMatch, targetMember.Locations.FirstOrDefault() ?? rootMethod.Locations.FirstOrDefault(), targetMember.Name));
return null;
}

if (match.Member == null)
{
return null;
}
Expand Down Expand Up @@ -3313,7 +3319,7 @@ private static IEnumerable<ISymbol> GetVisibleMembers(ITypeSymbol type, ICollect
private static MatchResult MatchSource(ISymbol targetMember, ISymbol[] sourceMembers, string nameMatching)
{
var exact = sourceMembers.Where(s => s.Name == targetMember.Name).ToArray();
if (nameMatching == NameMatchingExact || exact.Length == 1)
if (nameMatching == NameMatchingExact || nameMatching != NameMatchingIgnoreCase && exact.Length == 1)
{
return new MatchResult(exact.Length == 1 ? exact[0] : null, exact.Length > 1);
}
Expand Down
Loading
Loading