Skip to content

Commit 3ac249f

Browse files
Merge pull request #77 from ktsu-dev/claude/sem004-unknown-unit
feat(generator): SEM004 diagnostic — flag dimensions.json units missing from units.json
2 parents d81c5e0 + 2ba315b commit 3ac249f

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ var converted = sourceString.As<SourceType, TargetType>();
150150
- **SEM001** — a relationship in `dimensions.json` references a dimension that does not exist (typo or rename). The operator is silently dropped.
151151
- **SEM002** — schema-level validation issue (missing `name`/`symbol`, empty `availableUnits`, duplicate type names, no vector forms declared).
152152
- **SEM003** — a relationship's explicit `forms` list references a vector form not declared on a participating dimension. Use `forms` to constrain a relationship to specific vector forms (e.g. `crossProducts: [{ "other": "Length", "result": "Torque", "forms": [3] }]`); when omitted, the legacy "emit at every common form" behaviour is preserved.
153+
- **SEM004** — a dimension's `availableUnits` array references a unit name that isn't declared anywhere in `units.json`. Without the diagnostic the generator silently emits an identity-conversion `From{Unit}` factory, which is wrong for any non-base unit; SEM004 catches the typo at build time.
153154
- See `docs/physics-generator.md` for the full schema and an end-to-end "add a dimension" walk-through.
154155

155156
This file is the entry point. For deeper material:

Semantics.SourceGenerators/AnalyzerReleases.Unshipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Rule ID | Category | Severity | Notes
88
SEM001 | Semantics.SourceGenerators | Warning | Reports relationships in dimensions.json that reference unknown dimension names.
99
SEM002 | Semantics.SourceGenerators | Warning | Reports schema-level validation issues in dimensions.json (missing fields, duplicate type names, etc).
1010
SEM003 | Semantics.SourceGenerators | Warning | Reports a relationship whose explicit `forms` list references a vector form not declared on a participating dimension.
11+
SEM004 | Semantics.SourceGenerators | Warning | Reports a `dimensions.json` `availableUnits` entry that doesn't match any unit declared in `units.json`.

Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ public class QuantitiesGenerator : GeneratorBase<DimensionsMetadata>
4545
defaultSeverity: DiagnosticSeverity.Warning,
4646
isEnabledByDefault: true);
4747

48+
private static readonly DiagnosticDescriptor UnknownUnitReference = new(
49+
id: "SEM004",
50+
title: "dimensions.json references a unit not declared in units.json",
51+
messageFormat: "Unit '{0}' (referenced by dimension '{1}'.availableUnits) is not declared in units.json; the generated From{0} factory will use an identity conversion. Add the unit to units.json or fix the spelling.",
52+
category: "Semantics.SourceGenerators",
53+
defaultSeverity: DiagnosticSeverity.Warning,
54+
isEnabledByDefault: true);
55+
4856
public QuantitiesGenerator() : base("dimensions.json") { }
4957

5058
/// <summary>
@@ -140,6 +148,13 @@ private void GenerateInner(SourceProductionContext context, DimensionsMetadata m
140148

141149
Dictionary<string, UnitDefinition> unitMap = BuildUnitMap(units);
142150

151+
// Issue #58/#48 follow-up: surface dimensions.json availableUnits entries that
152+
// don't exist in units.json. The generator's BuildToBaseExpression silently falls
153+
// back to identity conversion in that case, which is wrong for any non-base unit
154+
// — a typo (e.g. "Kilometres" vs "Kilometers") would silently produce a factory
155+
// with no scale factor. SEM004 catches that at build time.
156+
ReportUnknownUnitReferences(context, metadata, unitMap);
157+
143158
// Phase A: Build maps and collect operators
144159
Dictionary<string, PhysicalDimension> dimensionMap = BuildDimensionMap(metadata);
145160
Dictionary<string, int> typeFormMap = BuildTypeFormMap(metadata);
@@ -549,6 +564,51 @@ private static Dictionary<string, UnitDefinition> BuildUnitMap(UnitsMetadata uni
549564
return map;
550565
}
551566

567+
/// <summary>
568+
/// Walks every <c>availableUnits</c> entry across the dimensions metadata and emits
569+
/// <c>SEM004</c> for any unit name that doesn't appear in <paramref name="unitMap"/>.
570+
/// Deduplicates by (unit, dimension) so a typo on a unit shared by many dimensions
571+
/// reports once per offending dimension instead of per-form/overload.
572+
/// </summary>
573+
private static void ReportUnknownUnitReferences(
574+
SourceProductionContext context,
575+
DimensionsMetadata metadata,
576+
Dictionary<string, UnitDefinition> unitMap)
577+
{
578+
// If units.json wasn't loaded the map is empty; treating every unit as "unknown"
579+
// would flood the build log. The CombinedMetadata loader already supplies a
580+
// non-null UnitsMetadata even when units.json is missing — check for that case
581+
// and bail rather than report a useless wall of warnings.
582+
if (unitMap.Count == 0)
583+
{
584+
return;
585+
}
586+
587+
HashSet<string> seen = [];
588+
foreach (PhysicalDimension dim in metadata.PhysicalDimensions)
589+
{
590+
foreach (string unitName in dim.AvailableUnits)
591+
{
592+
if (string.IsNullOrEmpty(unitName) || unitMap.ContainsKey(unitName))
593+
{
594+
continue;
595+
}
596+
597+
string key = $"{dim.Name}::{unitName}";
598+
if (!seen.Add(key))
599+
{
600+
continue;
601+
}
602+
603+
context.ReportDiagnostic(Diagnostic.Create(
604+
UnknownUnitReference,
605+
Location.None,
606+
unitName,
607+
dim.Name));
608+
}
609+
}
610+
}
611+
552612
/// <summary>
553613
/// Emits one <c>From{Unit}</c> static factory per entry in <paramref name="availableUnits"/>.
554614
/// The first unit is treated as the SI base unit (no conversion). Subsequent units use the

0 commit comments

Comments
 (0)