From c74f567565488d04ce93b3a1934104febb0f425f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 9 May 2026 11:19:20 +0000 Subject: [PATCH] feat(generator): emit SEM001 when relationship references unknown dimension When dimensions.json declares an integral / derivative / dotProduct / crossProduct that targets a dimension that doesn't exist (typo, stale name after a rename), QuantitiesGenerator silently dropped the operator. Typos were invisible until someone noticed the missing operator at the call site. Now: emit a Warning-severity SEM001 diagnostic naming the owning dimension, the unresolved reference, and the metadata path, e.g. warning SEM001: Dimension 'Force' references unknown dimension 'Lengt' in integrals[Lengt -> Energy].other; the operator will not be generated. Check spelling and that the referenced dimension exists in dimensions.json. The operator is still skipped (build doesn't fail) so existing valid trees keep working, but typos surface in build output where they're noticeable. Closes #56. --- .../Generators/QuantitiesGenerator.cs | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs b/Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs index c322296..541c1d8 100644 --- a/Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs +++ b/Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs @@ -20,6 +20,14 @@ namespace Semantics.SourceGenerators; [Generator] public class QuantitiesGenerator : GeneratorBase { + private static readonly DiagnosticDescriptor UnknownDimensionReference = new( + id: "SEM001", + title: "Unknown dimension reference in physics relationship", + messageFormat: "Dimension '{0}' references unknown dimension '{1}' in {2}; the operator will not be generated. Check spelling and that the referenced dimension exists in dimensions.json.", + category: "Semantics.SourceGenerators", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + public QuantitiesGenerator() : base("dimensions.json") { } protected override void Generate(SourceProductionContext context, DimensionsMetadata metadata, CodeBlocker codeBlocker) @@ -32,8 +40,8 @@ protected override void Generate(SourceProductionContext context, DimensionsMeta // Phase A: Build maps and collect operators Dictionary dimensionMap = BuildDimensionMap(metadata); Dictionary typeFormMap = BuildTypeFormMap(metadata); - List allOperators = CollectAllOperators(metadata, dimensionMap); - List allProducts = CollectAllProducts(metadata, dimensionMap); + List allOperators = CollectAllOperators(context, metadata, dimensionMap); + List allProducts = CollectAllProducts(context, metadata, dimensionMap); Dictionary> operatorsByOwner = GroupBy(allOperators, o => o.OwnerTypeName); Dictionary> productsByOwner = GroupBy(allProducts, p => p.SelfTypeName); @@ -112,7 +120,7 @@ private static Dictionary BuildTypeFormMap(DimensionsMetadata metad return map; } - private static List CollectAllOperators(DimensionsMetadata metadata, Dictionary dimMap) + private static List CollectAllOperators(SourceProductionContext context, DimensionsMetadata metadata, Dictionary dimMap) { HashSet seen = []; List result = []; @@ -124,11 +132,13 @@ private static List CollectAllOperators(DimensionsMetadata metadat { if (!dimMap.TryGetValue(integral.Other, out PhysicalDimension? otherDim)) { + ReportUnknownReference(context, dim.Name, integral.Other, $"integrals[{integral.Other} -> {integral.Result}].other"); continue; } if (!dimMap.TryGetValue(integral.Result, out PhysicalDimension? resultDim)) { + ReportUnknownReference(context, dim.Name, integral.Result, $"integrals[{integral.Other} -> {integral.Result}].result"); continue; } @@ -168,11 +178,13 @@ private static List CollectAllOperators(DimensionsMetadata metadat { if (!dimMap.TryGetValue(derivative.Other, out PhysicalDimension? otherDim)) { + ReportUnknownReference(context, dim.Name, derivative.Other, $"derivatives[{derivative.Other} -> {derivative.Result}].other"); continue; } if (!dimMap.TryGetValue(derivative.Result, out PhysicalDimension? resultDim)) { + ReportUnknownReference(context, dim.Name, derivative.Result, $"derivatives[{derivative.Other} -> {derivative.Result}].result"); continue; } @@ -205,7 +217,7 @@ private static List CollectAllOperators(DimensionsMetadata metadat return result; } - private static List CollectAllProducts(DimensionsMetadata metadata, Dictionary dimMap) + private static List CollectAllProducts(SourceProductionContext context, DimensionsMetadata metadata, Dictionary dimMap) { HashSet seen = []; List result = []; @@ -217,11 +229,13 @@ private static List CollectAllProducts(DimensionsMetadata metadata, { if (!dimMap.TryGetValue(dot.Other, out PhysicalDimension? otherDim)) { + ReportUnknownReference(context, dim.Name, dot.Other, $"dotProducts[{dot.Other} -> {dot.Result}].other"); continue; } if (!dimMap.TryGetValue(dot.Result, out PhysicalDimension? resultDim)) { + ReportUnknownReference(context, dim.Name, dot.Result, $"dotProducts[{dot.Other} -> {dot.Result}].result"); continue; } @@ -255,11 +269,13 @@ private static List CollectAllProducts(DimensionsMetadata metadata, { if (!dimMap.TryGetValue(cross.Other, out PhysicalDimension? otherDim)) { + ReportUnknownReference(context, dim.Name, cross.Other, $"crossProducts[{cross.Other} -> {cross.Result}].other"); continue; } if (!dimMap.TryGetValue(cross.Result, out PhysicalDimension? resultDim)) { + ReportUnknownReference(context, dim.Name, cross.Result, $"crossProducts[{cross.Other} -> {cross.Result}].result"); continue; } @@ -297,6 +313,16 @@ private static void AddOp(List list, HashSet seen, string } } + private static void ReportUnknownReference(SourceProductionContext context, string owningDimension, string unknownReference, string fieldPath) + { + context.ReportDiagnostic(Diagnostic.Create( + UnknownDimensionReference, + Location.None, + owningDimension, + unknownReference, + fieldPath)); + } + private static Dictionary> GroupBy(List items, Func keySelector) { Dictionary> groups = [];