From 80a0e12d6ea291ec11fd9a5a98bae3c286afecad Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Sun, 21 Jul 2024 16:57:29 -0400 Subject: [PATCH 1/3] Use imported symbol identifiers rather than representation in memory as dictionary key in ImportClosure representation. --- .../CompileTimeImportTests.cs | 34 ++ .../CompileTimeImports/ImportClosureInfo.cs | 469 ++++++++++++------ src/Bicep.Core/Semantics/SemanticModel.cs | 1 + .../BuildCommandTests.cs | 87 ++++ 4 files changed, 445 insertions(+), 146 deletions(-) diff --git a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs index 06be07c6298..3f64e6286ef 100644 --- a/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs +++ b/src/Bicep.Core.IntegrationTests/CompileTimeImportTests.cs @@ -2361,4 +2361,38 @@ public void Bicepparam_can_import_non_literal_variables_from_json() result.Parameters.Should().NotBeNull(); result.Parameters.Should().HaveValueAtPath("parameters.aParam.value", 4); } + + [TestMethod] + public void Symbols_entering_the_import_closure_via_multiple_paths_are_supported() + { + var result = CompilationHelper.Compile( + ("main.bicep", """ + import * as typesB from 'moduleB.bicep' + import * as typesC from 'moduleC.bicep' + """), + ("moduleA.bicep", """ + @export() + type typeA = { + propA: string + } + """), + ("moduleB.bicep", """ + import * as typesA from 'moduleA.bicep' + @export() + type typeB = { + optionsA: typesA.typeA + propB: string + } + """), + ("moduleC.bicep", """ + import * as typesA from 'moduleA.bicep' + @export() + type typeC = { + optionsA: typesA.typeA + propC: string + } + """)); + + result.ExcludingLinterDiagnostics().Should().NotHaveAnyDiagnostics(); + } } diff --git a/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs b/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs index 9c8897c19a3..3a5ef7d409d 100644 --- a/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs +++ b/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs @@ -38,27 +38,27 @@ public record ImportClosureInfo(ImmutableArray ImportedT public static ImportClosureInfo Calculate(SemanticModel model) { - var closure = CalculateImportClosure(model); + SymbolFromForeignTemplateFactory referenceFactory = new(model.SourceFile.FileUri); + var closure = CalculateImportClosure(model, referenceFactory); var closureMetadata = CalculateImportedSymbolMetadata(model, closure); - var importedBicepSymbolNames = closureMetadata.Keys.OfType() - .ToImmutableDictionary(@ref => @ref.Symbol, @ref => closureMetadata[@ref].UniqueNameWithinClosure); - var importedSymbolNames = closure.ImportedSymbolsToIntraTemplateSymbols - .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value].UniqueNameWithinClosure); + var importedBicepSymbolNames = closureMetadata.Keys.OfType() + .ToImmutableDictionary(@ref => @ref.Symbol, @ref => closureMetadata[@ref]); + var importedSymbolNames = closure.ImportedSymbolsToForeignTemplateSymbols + .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value]); var wildcardImportPropertyNames = closure.WildcardImportPropertiesToIntraTemplateSymbols - .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value].UniqueNameWithinClosure); - var synthesizedVariableNames = closureMetadata.Keys.OfType() + .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value]); + var synthesizedVariableNames = closureMetadata.Keys.OfType() .ToLookup(@ref => @ref.SourceBicepModel) - .ToImmutableDictionary(g => g.Key, g => g.ToImmutableDictionary(@ref => @ref.Name, @ref => closureMetadata[@ref].UniqueNameWithinClosure)); + .ToImmutableDictionary(g => g.Key, g => g.ToImmutableDictionary(@ref => @ref.Name, @ref => closureMetadata[@ref])); - ArmIdentifierEqualityComparer armIdentifierEqualityComparer = new(); - var importedArmSymbolNamesByFile = closureMetadata.Keys.OfType() + var importedArmSymbolNamesByFile = closureMetadata.Keys.OfType() .ToLookup(@ref => @ref.ArmTemplateFile) .ToImmutableDictionary(grouping => grouping.Key, grouping => grouping.ToImmutableDictionary( @ref => new ArmIdentifier(@ref.Type, @ref.Identifier), - @ref => closureMetadata[@ref].UniqueNameWithinClosure, - armIdentifierEqualityComparer)); - var armDeclarationToExpressionConverters = closureMetadata.Keys.OfType() + @ref => closureMetadata[@ref], + ArmIdentifierEqualityComparer.Instance)); + var armDeclarationToExpressionConverters = closureMetadata.Keys.OfType() .Select(@ref => @ref.ArmTemplateFile) .Distinct() .ToImmutableDictionary(templateFile => templateFile, @@ -71,12 +71,12 @@ public static ImportClosureInfo Calculate(SemanticModel model) Dictionary importedFunctions = new(); var importedSymbolMetadata = ImmutableDictionary.CreateBuilder(); - foreach (var (symbol, (originMetadata, name)) in closureMetadata) + foreach (var (symbol, name) in closureMetadata) { - importedSymbolMetadata.Add(name, originMetadata); + importedSymbolMetadata.Add(name, new(symbol.SourceTemplateIdentifier, symbol.OriginalSymbolName)); switch (symbol) { - case ArmSymbolicReference armRef: + case SymbolFromForeignArmTemplate armRef: var converter = armDeclarationToExpressionConverters[armRef.ArmTemplateFile].WithSourceSyntax(closure.SymbolsInImportClosure[armRef]); switch (armRef.Type) { @@ -93,7 +93,7 @@ public static ImportClosureInfo Calculate(SemanticModel model) throw new UnreachableException($"Unknown ARM symbol type: {armRef.Type}"); } break; - case BicepSymbolicReference bicepRef: + case SymbolFromForeignBicepTemplate bicepRef: var migrator = new ImportedSymbolDeclarationMigrator(bicepRef.SourceBicepModel, importedBicepSymbolNames, synthesizedVariableNames.TryGetValue(bicepRef.SourceBicepModel, out var dict) ? dict : ImmutableDictionary.Empty, @@ -114,11 +114,11 @@ public static ImportClosureInfo Calculate(SemanticModel model) throw new UnreachableException($"Cannot import Bicep symbols of type {bicepRef.Symbol.GetType().Name}"); } break; - case BicepSynthesizedVariableReference synthesizedVariableRef: + case SynthesizedVariableFromForeignBicepTemplate synthesizedVariableRef: importedVariables.Add(name, new(closure.SymbolsInImportClosure[synthesizedVariableRef], name, synthesizedVariableRef.Value)); break; default: - throw new UnreachableException($"This switch was expected to exhaustively process all kinds of {nameof(IntraTemplateSymbolicReference)} but did not handle an instance of type {symbol.GetType().Name}"); + throw new UnreachableException($"This switch was expected to exhaustively process all kinds of {nameof(SymbolImportedFromForeignTemplate)} but did not handle an instance of type {symbol.GetType().Name}"); } } @@ -130,95 +130,117 @@ public static ImportClosureInfo Calculate(SemanticModel model) importedSymbolMetadata.ToImmutable()); } - private static ImportClosure CalculateImportClosure(SemanticModel model) + private static ImportClosure CalculateImportClosure( + SemanticModel model, + SymbolFromForeignTemplateFactory referenceFactory) { - Dictionary importedModuleReferences = new(); - Dictionary symbolsInImportClosure = new(); - Dictionary importedSymbolsToIntraTemplateSymbols = new(); - Dictionary wildcardImportPropertiesToIntraTemplateSymbols = new(); + Dictionary symbolsInImportClosure = new(SymbolImportedFromForeignTemplateComparer.Instance); + Dictionary importedSymbolsToIntraTemplateSymbols = new(); + Dictionary wildcardImportPropertiesToIntraTemplateSymbols = new(); ConcurrentDictionary armReferenceCollectors = new(); ConcurrentDictionary bicepEmitterContexts = new(); Queue searchQueue = new(model.Root.ImportedSymbols - .Select(importedSymbol => new SearchQueueItem(importedSymbol.DeclaringSyntax, new BicepImportedSymbolReference(importedSymbol, model, GetImportReference(importedSymbol)))) + .Select(importedSymbol => new SearchQueueItem(importedSymbol.DeclaringSyntax, new BicepImportedSymbol(importedSymbol, model, GetImportReference(importedSymbol)))) .Concat(model.Root.WildcardImports - .Select(wildcardImport => new SearchQueueItem(wildcardImport.DeclaringSyntax, new BicepWildcardImportSymbolicReference(wildcardImport, model, GetImportReference(wildcardImport)))))); + .Select(wildcardImport => new SearchQueueItem(wildcardImport.DeclaringSyntax, new BicepWildcardImportSymbol(wildcardImport, model, GetImportReference(wildcardImport)))))); while (searchQueue.Count > 0) { var item = searchQueue.Dequeue(); - if (item.SymbolicReference is BicepSymbolicReference bicepSymbolicReference) + if (item.Symbol is SymbolFromForeignBicepTemplate bicepSymbol) { - if (symbolsInImportClosure.TryAdd(bicepSymbolicReference, item.InitiallyDeclaringSyntax)) + if (symbolsInImportClosure.TryAdd(bicepSymbol, item.InitiallyDeclaringSyntax)) { - foreach (var reference in CollectReferences(bicepSymbolicReference)) + foreach (var reference in CollectReferences(bicepSymbol)) { searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, reference)); } - foreach (var synthesizedVariableReference in CollectSynthesizedVariableReferences(bicepSymbolicReference, bicepEmitterContexts.GetOrAdd(bicepSymbolicReference.SourceBicepModel, m => new(m)))) + foreach (var synthesizedVariableReference in CollectSynthesizedVariableReferences( + bicepSymbol, + bicepEmitterContexts.GetOrAdd(bicepSymbol.SourceBicepModel, m => new(m)))) { symbolsInImportClosure.TryAdd(synthesizedVariableReference, item.InitiallyDeclaringSyntax); } } } - else if (item.SymbolicReference is ArmSymbolicReference armSymbolicReference) + else if (item.Symbol is SymbolFromForeignArmTemplate armSymbol) { - if (symbolsInImportClosure.TryAdd(armSymbolicReference, item.InitiallyDeclaringSyntax)) + if (symbolsInImportClosure.TryAdd(armSymbol, item.InitiallyDeclaringSyntax)) { - ArmIdentifier refTarget = new(armSymbolicReference.Type, armSymbolicReference.Identifier); - foreach (var reference in armReferenceCollectors.GetOrAdd(armSymbolicReference.ArmTemplateFile, f => new(f)).EnumerateReferencesUsedInDefinitionOf(refTarget)) + ArmIdentifier refTarget = new(armSymbol.Type, armSymbol.Identifier); + foreach (var reference in armReferenceCollectors.GetOrAdd( + armSymbol.ArmTemplateFile, + f => new(f)).EnumerateReferencesUsedInDefinitionOf(refTarget)) { - searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, new ArmSymbolicReference(reference.SymbolType, reference.Identifier, armSymbolicReference.ArmTemplateFile, armSymbolicReference.SourceModel))); + searchQueue.Enqueue(new( + item.InitiallyDeclaringSyntax, + SymbolFromForeignTemplateFactory.SymbolFor( + reference.SymbolType, + reference.Identifier, + armSymbol))); } } } - else if (item.SymbolicReference is BicepWildcardImportSymbolicReference wildcardImportSymbolicReference) + else if (item.Symbol is BicepWildcardImportSymbol wildcardImportSymbol) { - var targetModel = wildcardImportSymbolicReference.Symbol.SourceModel; - importedModuleReferences[targetModel] = wildcardImportSymbolicReference.ImportTarget; - - foreach (var (propertyName, exportedSymbol) in EnumerateExportedSymbolsAsIntraTemplateSymbols(targetModel)) + foreach (var (propertyName, exportedSymbol) in EnumerateExportedSymbolsAsIntraTemplateSymbols( + wildcardImportSymbol.Symbol.SourceModel, + wildcardImportSymbol, + referenceFactory)) { - wildcardImportPropertiesToIntraTemplateSymbols[new(wildcardImportSymbolicReference.Symbol, propertyName)] = exportedSymbol; + wildcardImportPropertiesToIntraTemplateSymbols[new(wildcardImportSymbol.Symbol, propertyName)] = exportedSymbol; searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, exportedSymbol)); } } - else if (item.SymbolicReference is BicepImportedSymbolReference importedSymbolReference) + else if (item.Symbol is BicepImportedSymbol importedSymbol) { - var targetModel = importedSymbolReference.Symbol.SourceModel; - importedModuleReferences[targetModel] = importedSymbolReference.ImportTarget; + var targetModel = importedSymbol.Symbol.SourceModel; - var name = importedSymbolReference.Symbol.OriginalSymbolName - ?? throw new InvalidOperationException($"The import symbol {importedSymbolReference.Symbol.Name} did not specify what symbol to import"); + var name = importedSymbol.Symbol.OriginalSymbolName + ?? throw new InvalidOperationException($"The import symbol {importedSymbol.Symbol.Name} did not specify what symbol to import"); if (!targetModel.Exports.TryGetValue(name, out var exportMetadata)) { - throw new InvalidOperationException($"No export named {name} found in {TemplateIdentifier(model, targetModel, importedSymbolReference.ImportTarget)}"); + throw new InvalidOperationException($"No export named {name} found in {TemplateIdentifier( + model.SourceFile.FileUri, + targetModel, + importedSymbol.ImportTarget)}"); } - IntraTemplateSymbolicReference target = targetModel switch + SymbolImportedFromForeignTemplate target = targetModel switch { - SemanticModel targetBicepModel - => new BicepSymbolicReference(FindSymbolNamed(name, targetBicepModel), targetBicepModel), - ArmTemplateSemanticModel targetArmModel - => ReferenceForArmTarget(exportMetadata, targetArmModel.SourceFile, targetModel), - TemplateSpecSemanticModel targetTemplateSpecModel - => ReferenceForArmTarget(exportMetadata, targetTemplateSpecModel.SourceFile.MainTemplateFile, targetModel), + SemanticModel targetBicepModel => referenceFactory.SymbolFor( + FindSymbolNamed(name, targetBicepModel), + targetBicepModel, + importedSymbol), + ArmTemplateSemanticModel targetArmModel => ReferenceForArmTarget( + exportMetadata, + targetArmModel.SourceFile, + targetModel, + importedSymbol, + referenceFactory), + TemplateSpecSemanticModel targetTemplateSpecModel => ReferenceForArmTarget( + exportMetadata, + targetTemplateSpecModel.SourceFile.MainTemplateFile, + targetModel, + importedSymbol, + referenceFactory), _ => throw new InvalidOperationException($"Unrecognized module type {targetModel.GetType().Name} encountered"), }; - importedSymbolsToIntraTemplateSymbols[importedSymbolReference.Symbol] = target; + importedSymbolsToIntraTemplateSymbols[importedSymbol.Symbol] = target; searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, target)); } else { - throw new InvalidOperationException($"Unexpected symbolic reference type of {item.SymbolicReference.GetType().Name} encountered."); + throw new InvalidOperationException($"Unexpected symbolic reference type of {item.Symbol.GetType().Name} encountered."); } } - return new(importedModuleReferences, + return new( symbolsInImportClosure, importedSymbolsToIntraTemplateSymbols, wildcardImportPropertiesToIntraTemplateSymbols, @@ -245,113 +267,175 @@ private static ArtifactReference GetImportReference(WildcardImportSymbol symbol) throw new InvalidOperationException("Unable to load module reference for import statement"); } - private static IEnumerable CollectReferences(BicepSymbolicReference typeReference) + private static IEnumerable CollectReferences(SymbolFromForeignBicepTemplate typeReference) => typeReference.SourceBicepModel.Binder.GetSymbolsReferencedInDeclarationOf(typeReference.Symbol) - .Select(symbol => symbol switch + .Select(symbol => symbol switch { ProviderNamespaceSymbol => null, // this was the base expression of a fully qualified ambient type reference (e.g., sys.string) LocalVariableSymbol => null, // local variables are contained within the expression and are not external references - TypeAliasSymbol typeAlias => new BicepSymbolicReference(typeAlias, typeReference.SourceBicepModel), - VariableSymbol variable => new BicepSymbolicReference(variable, typeReference.SourceBicepModel), - DeclaredFunctionSymbol declaredFunction => new BicepSymbolicReference(declaredFunction, typeReference.SourceBicepModel), - ImportedSymbol imported => new BicepImportedSymbolReference(imported, typeReference.SourceBicepModel, GetImportReference(imported)), - WildcardImportSymbol wildcardImport => new BicepWildcardImportSymbolicReference(wildcardImport, typeReference.SourceBicepModel, GetImportReference(wildcardImport)), - _ => throw new InvalidOperationException($"Invalid symbol {symbol.Name} of type {symbol.GetType().Name} encountered within a export expression"), + TypeAliasSymbol typeAlias => SymbolFromForeignTemplateFactory.SymbolFor(typeAlias, typeReference), + VariableSymbol variable => SymbolFromForeignTemplateFactory.SymbolFor(variable, typeReference), + DeclaredFunctionSymbol func => SymbolFromForeignTemplateFactory.SymbolFor(func, typeReference), + ImportedSymbol imported => new BicepImportedSymbol( + imported, + typeReference.SourceBicepModel, + GetImportReference(imported)), + WildcardImportSymbol wildcardImport => new BicepWildcardImportSymbol( + wildcardImport, + typeReference.SourceBicepModel, + GetImportReference(wildcardImport)), + _ => throw new InvalidOperationException( + $"Invalid symbol {symbol.Name} of type {symbol.GetType().Name} encountered within a export expression"), }) .WhereNotNull(); - private static IEnumerable CollectSynthesizedVariableReferences(BicepSymbolicReference @ref, EmitterContext emitterContext) - => SyntaxAggregator.AggregateByType(@ref.Symbol.DeclaringSyntax) - .Select(functionCallSyntax => emitterContext.FunctionVariables.TryGetValue(functionCallSyntax, out var result) - ? new BicepSynthesizedVariableReference(result.Name, result.Value, @ref.SourceBicepModel) - : null) - .WhereNotNull(); - - private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols(ISemanticModel model) => model switch + private static IEnumerable CollectSynthesizedVariableReferences( + SymbolFromForeignBicepTemplate @ref, + EmitterContext emitterContext + ) => SyntaxAggregator.AggregateByType(@ref.Symbol.DeclaringSyntax) + .Select(functionCallSyntax => emitterContext.FunctionVariables.TryGetValue(functionCallSyntax, out var result) + ? SymbolFromForeignTemplateFactory.SymbolFor(result.Name, result.Value, @ref) + : null) + .WhereNotNull(); + + private static IEnumerable< + (string symbolName, SymbolImportedFromForeignTemplate reference) + > EnumerateExportedSymbolsAsIntraTemplateSymbols( + ISemanticModel model, + BicepWildcardImportSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => model switch { - SemanticModel bicepModel => EnumerateExportedSymbolsAsIntraTemplateSymbols(bicepModel), - ArmTemplateSemanticModel armModel => EnumerateExportedSymbolsAsIntraTemplateSymbols(armModel), - TemplateSpecSemanticModel templateSpecModel => EnumerateExportedSymbolsAsIntraTemplateSymbols(templateSpecModel), + SemanticModel bicepModel => EnumerateExportedSymbolsAsIntraTemplateSymbols( + bicepModel, + referrer, + referenceFactory), + ArmTemplateSemanticModel armModel => EnumerateExportedSymbolsAsIntraTemplateSymbols( + armModel, + referrer, + referenceFactory), + TemplateSpecSemanticModel templateSpecModel => EnumerateExportedSymbolsAsIntraTemplateSymbols( + templateSpecModel, + referrer, + referenceFactory), _ => throw new InvalidOperationException($"Unrecognized module type {model.GetType().Name} encountered"), }; - private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols(SemanticModel model) - => model.Root.TypeDeclarations - .Where(s => model.Exports.ContainsKey(s.Name)) - .Select(t => (t.Name, new BicepSymbolicReference(t, model))) - .Concat(model.Root.VariableDeclarations - .Where(s => model.Exports.ContainsKey(s.Name)) - .Select(v => (v.Name, new BicepSymbolicReference(v, model)))) - .Concat(model.Root.FunctionDeclarations - .Where(s => model.Exports.ContainsKey(s.Name)) - .Select(f => (f.Name, new BicepSymbolicReference(f, model)))); - - private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols(ArmTemplateSemanticModel model) - => EnumerateExportedSymbolsAsIntraTemplateSymbols(model, model.SourceFile); - - private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols(TemplateSpecSemanticModel model) - => EnumerateExportedSymbolsAsIntraTemplateSymbols(model, model.SourceFile.MainTemplateFile); - - private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols(ISemanticModel model, ArmTemplateFile templateFile) - => model.Exports.Values.Select(md => (md.Name, ReferenceForArmTarget(md, templateFile, model))); + private static IEnumerable< + (string symbolName, SymbolImportedFromForeignTemplate reference) + > EnumerateExportedSymbolsAsIntraTemplateSymbols( + SemanticModel model, + BicepWildcardImportSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => model.Root.TypeDeclarations + .Concat(model.Root.VariableDeclarations) + .Concat(model.Root.FunctionDeclarations) + .Where(t => t.IsExported()) + .Select(s => (s.Name, referenceFactory.SymbolFor(s, model, referrer))); + + private static IEnumerable< + (string symbolName, SymbolImportedFromForeignTemplate reference) + > EnumerateExportedSymbolsAsIntraTemplateSymbols( + ArmTemplateSemanticModel model, + BicepWildcardImportSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => EnumerateExportedSymbolsAsIntraTemplateSymbols(model, model.SourceFile, referrer, referenceFactory); + + private static IEnumerable< + (string symbolName, SymbolImportedFromForeignTemplate reference) + > EnumerateExportedSymbolsAsIntraTemplateSymbols( + TemplateSpecSemanticModel model, + BicepWildcardImportSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => EnumerateExportedSymbolsAsIntraTemplateSymbols( + model, + model.SourceFile.MainTemplateFile, + referrer, + referenceFactory); + + private static IEnumerable<(string symbolName, SymbolImportedFromForeignTemplate reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols( + ISemanticModel model, + ArmTemplateFile templateFile, + BicepWildcardImportSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => model.Exports.Values.Select( + md => (md.Name, ReferenceForArmTarget(md, templateFile, model, referrer, referenceFactory))); private static DeclaredSymbol FindSymbolNamed(string nameOfSymbolSought, SemanticModel model) => model.Root.Declarations.Where(t => LanguageConstants.IdentifierComparer.Equals(t.Name, nameOfSymbolSought)).Single(); - private static IntraTemplateSymbolicReference ReferenceForArmTarget(ExportMetadata targetMetadata, ArmTemplateFile sourceTemplateFile, ISemanticModel sourceModel) + private static SymbolImportedFromForeignTemplate ReferenceForArmTarget( + ExportMetadata targetMetadata, + ArmTemplateFile sourceTemplateFile, + ISemanticModel sourceModel, + InterTemplateSymbol referrer, + SymbolFromForeignTemplateFactory referenceFactory + ) => targetMetadata switch { - ExportedTypeMetadata => new ArmSymbolicReference(ArmSymbolType.Type, $"{ArmTypeRefPrefix}{targetMetadata.Name}", sourceTemplateFile, sourceModel), - ExportedVariableMetadata => new ArmSymbolicReference(ArmSymbolType.Variable, targetMetadata.Name, sourceTemplateFile, sourceModel), - ExportedFunctionMetadata => new ArmSymbolicReference(ArmSymbolType.Function, + ExportedTypeMetadata => referenceFactory.SymbolFor( + ArmSymbolType.Type, + $"{ArmTypeRefPrefix}{targetMetadata.Name}", + sourceTemplateFile, + sourceModel, + referrer), + ExportedVariableMetadata => referenceFactory.SymbolFor( + ArmSymbolType.Variable, + targetMetadata.Name, + sourceTemplateFile, + sourceModel, + referrer), + ExportedFunctionMetadata => referenceFactory.SymbolFor( + ArmSymbolType.Function, !targetMetadata.Name.Contains('.') ? $"{EmitConstants.UserDefinedFunctionsNamespace}.{targetMetadata.Name}" : targetMetadata.Name, sourceTemplateFile, - sourceModel), + sourceModel, + referrer), _ => throw new InvalidOperationException($"Unrecognized export metadata type: {targetMetadata.GetType().Name}"), }; - private static ImmutableDictionary - CalculateImportedSymbolMetadata(SemanticModel model, ImportClosure closure) + private static ImmutableDictionary CalculateImportedSymbolMetadata( + SemanticModel model, + ImportClosure closure) { - var importedSymbolMetadata = ImmutableDictionary.CreateBuilder(); + var importedSymbolNames = ImmutableDictionary.CreateBuilder( + SymbolImportedFromForeignTemplateComparer.Instance); // Every symbol explicitly imported into the model by name should keep that name in the compiled template. foreach (var importedSymbol in model.Root.ImportedSymbols) { - var importTarget = closure.ImportedSymbolsToIntraTemplateSymbols[importedSymbol]; - importedSymbolMetadata[closure.ImportedSymbolsToIntraTemplateSymbols[importedSymbol]] = ( - new(TemplateIdentifier(model, importTarget.SourceModel, closure.ImportedModuleReferences[importTarget.SourceModel]), SymbolIdentifier(importTarget)), - importedSymbol.Name); + importedSymbolNames[closure.ImportedSymbolsToForeignTemplateSymbols[importedSymbol]] = importedSymbol.Name; } int uniqueIdentifier = 1; ConcurrentDictionary templateIds = new(); // Every other symbol in the closure should be assigned a stable identifier that won't conflict with any valid Bicep identifier - foreach (var (symbolInfo, sourceTemplateIdentifier, originalSymbolName) in closure.SymbolsInImportClosure.Keys - .Select(symbolInfo => (symbolInfo, TemplateIdentifier(model, symbolInfo.SourceModel, closure.ImportedModuleReferences[symbolInfo.SourceModel]), SymbolIdentifier(symbolInfo))) - .OrderBy(t => $"{t.Item2}_{t.Item3}")) + foreach (var symbol in closure.SymbolsInImportClosure.Keys + .OrderBy(s => $"{s.SourceTemplateIdentifier}_{s.OriginalSymbolName}")) { // This symbol was imported by name and should appear in the template using the assigned identifier - if (importedSymbolMetadata.ContainsKey(symbolInfo)) + if (importedSymbolNames.ContainsKey(symbol)) { continue; } - var templateId = templateIds.GetOrAdd(sourceTemplateIdentifier, _ => uniqueIdentifier++); - var symbolId = Lexer.IsValidIdentifier(originalSymbolName) ? originalSymbolName : $"_{uniqueIdentifier++}"; + var templateId = templateIds.GetOrAdd(symbol.SourceTemplateIdentifier, _ => uniqueIdentifier++); + var symbolId = Lexer.IsValidIdentifier(symbol.OriginalSymbolName) + ? symbol.OriginalSymbolName + : $"_{uniqueIdentifier++}"; - importedSymbolMetadata.Add(symbolInfo, (new(sourceTemplateIdentifier, originalSymbolName), $"_{templateId}.{symbolId}")); + importedSymbolNames.Add(symbol, $"_{templateId}.{symbolId}"); } - return importedSymbolMetadata.ToImmutable(); + return importedSymbolNames.ToImmutable(); } - private static string TemplateIdentifier(SemanticModel entryPointModel, ISemanticModel modelToIdentify, ArtifactReference reference) + private static string TemplateIdentifier(Uri entryPointUri, ISemanticModel modelToIdentify, ArtifactReference reference) => reference switch { // for local modules, use the path on disk relative to the entry point template - LocalModuleReference => entryPointModel.SourceFile.FileUri.MakeRelativeUri(GetSourceFileUri(modelToIdentify)).ToString(), + LocalModuleReference => entryPointUri.MakeRelativeUri(GetSourceFileUri(modelToIdentify)).ToString(), ArtifactReference otherwise => otherwise.FullyQualifiedReference, }; @@ -363,45 +447,138 @@ private static string TemplateIdentifier(SemanticModel entryPointModel, ISemanti _ => throw new InvalidOperationException($"Unrecognized module type {model.GetType().Name} encountered"), }; - private static string SymbolIdentifier(IntraTemplateSymbolicReference reference) => reference switch + private abstract record EnclosedSymbol(ISemanticModel SourceModel); + private abstract record InterTemplateSymbol(SemanticModel SourceBicepModule, ArtifactReference ImportTarget) + : EnclosedSymbol(SourceBicepModule); + private record BicepWildcardImportSymbol(WildcardImportSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) + : InterTemplateSymbol(SourceBicepModel, ImportTarget); + private record BicepImportedSymbol(ImportedSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) + : InterTemplateSymbol(SourceBicepModel, ImportTarget); + + private abstract record SymbolImportedFromForeignTemplate(ISemanticModel SourceModel, string SourceTemplateIdentifier) + : EnclosedSymbol(SourceModel) { - BicepSymbolicReference bicepSymbolicReference => bicepSymbolicReference.Symbol.Name, - BicepSynthesizedVariableReference synthesizedVariableReference => synthesizedVariableReference.Name, - // For ARM JSON type references, the name is a JSON pointer - // If the pointer starts with "#/definitions/" and everything after that is a valid idenfitier (this will be the case for anything compiled from Bicep), use the last path segment - ArmSymbolicReference armRef when armRef.Type == ArmSymbolType.Type && armRef.Identifier.StartsWith(ArmTypeRefPrefix) - => armRef.Identifier[ArmTypeRefPrefix.Length..], - // For ARM user-defined function references, the name will be of the format "." - // If the namespace is "__bicep" (this will be the case for anything compiled from Bicep), use everything after the '.' - ArmSymbolicReference armRef when armRef.Type == ArmSymbolType.Function && armRef.Identifier.StartsWith(BicepDefinedFunctionNamePrefix) - => armRef.Identifier[BicepDefinedFunctionNamePrefix.Length..], - ArmSymbolicReference armRef => armRef.Identifier, - _ => throw new InvalidOperationException($"Unexpected symbolic reference type of {reference.GetType().Name} encountered."), - }; + public abstract string OriginalSymbolName { get; } + } + + private class SymbolImportedFromForeignTemplateComparer : IEqualityComparer + { + internal static readonly SymbolImportedFromForeignTemplateComparer Instance = new(); + + public bool Equals(SymbolImportedFromForeignTemplate? x, SymbolImportedFromForeignTemplate? y) + { + if (x is null) + { + return y is null; + } + + return x.SourceTemplateIdentifier.Equals(y?.SourceTemplateIdentifier) && + x.OriginalSymbolName.Equals(y.OriginalSymbolName); + } + + public int GetHashCode(SymbolImportedFromForeignTemplate obj) + => HashCode.Combine(obj.SourceTemplateIdentifier, obj.OriginalSymbolName); + } + + private record SymbolFromForeignBicepTemplate(DeclaredSymbol Symbol, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) + : SymbolImportedFromForeignTemplate(SourceBicepModel, SourceTemplateIdentifier) + { + public override string OriginalSymbolName => Symbol.Name; + } + + private record SynthesizedVariableFromForeignBicepTemplate(string Name, Expression Value, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) + : SymbolImportedFromForeignTemplate(SourceBicepModel, SourceTemplateIdentifier) + { + public override string OriginalSymbolName => Name; + } + + private record SymbolFromForeignArmTemplate(ArmSymbolType Type, string Identifier, ArmTemplateFile ArmTemplateFile, ISemanticModel SourceModel, string SourceTemplateIdentifier) + : SymbolImportedFromForeignTemplate(SourceModel, SourceTemplateIdentifier) + { + public override string OriginalSymbolName => Type switch + { + // For ARM JSON type references, the name is a JSON pointer + // If the pointer starts with "#/definitions/" and everything after that is a valid idenfitier + // (this will be the case for anything compiled from Bicep), use the last path segment + ArmSymbolType.Type when Identifier.StartsWith(ArmTypeRefPrefix) && + Lexer.IsValidIdentifier(Identifier[ArmTypeRefPrefix.Length..]) + => Identifier[ArmTypeRefPrefix.Length..], + // For ARM user-defined function references, the name will be of the format "." + // If the namespace is "__bicep" (this will be the case for anything compiled from Bicep), + // use everything after the '.' + ArmSymbolType.Function when Identifier.StartsWith(BicepDefinedFunctionNamePrefix) && + Lexer.IsValidIdentifier(Identifier[BicepDefinedFunctionNamePrefix.Length..]) + => Identifier[BicepDefinedFunctionNamePrefix.Length..], + _ => Identifier, + }; + } + + private class SymbolFromForeignTemplateFactory + { + private readonly Uri entryPointUri; - private record SymbolicReference(ISemanticModel SourceModel); - private record InterTemplateSymbolicReference(SemanticModel SourceBicepModule) : SymbolicReference(SourceBicepModule); - private record BicepWildcardImportSymbolicReference(WildcardImportSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) - : InterTemplateSymbolicReference(SourceBicepModel); - private record BicepImportedSymbolReference(ImportedSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) - : InterTemplateSymbolicReference(SourceBicepModel); + internal SymbolFromForeignTemplateFactory(Uri entryPointUri) + { + this.entryPointUri = entryPointUri; + } - private record IntraTemplateSymbolicReference(ISemanticModel SourceModel) : SymbolicReference(SourceModel); - private record BicepSymbolicReference(DeclaredSymbol Symbol, SemanticModel SourceBicepModel) : IntraTemplateSymbolicReference(SourceBicepModel); - private record BicepSynthesizedVariableReference(string Name, Expression Value, SemanticModel SourceBicepModel) : IntraTemplateSymbolicReference(SourceBicepModel); - private record ArmSymbolicReference(ArmSymbolType Type, string Identifier, ArmTemplateFile ArmTemplateFile, ISemanticModel SourceModel) : IntraTemplateSymbolicReference(SourceModel); + internal SymbolImportedFromForeignTemplate SymbolFor( + DeclaredSymbol symbol, + SemanticModel sourceBicepModel, + InterTemplateSymbol referrer + ) => new SymbolFromForeignBicepTemplate( + symbol, + sourceBicepModel, + TemplateIdentifier(entryPointUri, sourceBicepModel, referrer.ImportTarget)); + + internal static SymbolFromForeignBicepTemplate SymbolFor( + DeclaredSymbol symbol, + SymbolFromForeignBicepTemplate enclosedBy + ) => new(symbol, enclosedBy.SourceBicepModel, enclosedBy.SourceTemplateIdentifier); + + internal static SynthesizedVariableFromForeignBicepTemplate SymbolFor( + string name, + Expression value, + SymbolFromForeignBicepTemplate enclosedBy + ) => new(name, value, enclosedBy.SourceBicepModel, enclosedBy.SourceTemplateIdentifier); + + internal SymbolImportedFromForeignTemplate SymbolFor( + ArmSymbolType type, + string identifier, + ArmTemplateFile armTemplateFile, + ISemanticModel sourceModel, + InterTemplateSymbol referrer + ) => new SymbolFromForeignArmTemplate( + type, + identifier, + armTemplateFile, + sourceModel, + TemplateIdentifier(entryPointUri, sourceModel, referrer.ImportTarget)); + + internal static SymbolFromForeignArmTemplate SymbolFor( + ArmSymbolType type, + string identifier, + SymbolFromForeignArmTemplate enclosedBy + ) => new( + type, + identifier, + enclosedBy.ArmTemplateFile, + enclosedBy.SourceModel, + enclosedBy.SourceTemplateIdentifier); + } private record ImportClosure( - IReadOnlyDictionary ImportedModuleReferences, - IReadOnlyDictionary SymbolsInImportClosure, - IReadOnlyDictionary ImportedSymbolsToIntraTemplateSymbols, - IReadOnlyDictionary WildcardImportPropertiesToIntraTemplateSymbols, + IReadOnlyDictionary SymbolsInImportClosure, + IReadOnlyDictionary ImportedSymbolsToForeignTemplateSymbols, + IReadOnlyDictionary WildcardImportPropertiesToIntraTemplateSymbols, ConcurrentDictionary EmitterContexts); - private record SearchQueueItem(SyntaxBase InitiallyDeclaringSyntax, SymbolicReference SymbolicReference); + private record SearchQueueItem(SyntaxBase InitiallyDeclaringSyntax, EnclosedSymbol Symbol); private class ArmIdentifierEqualityComparer : IEqualityComparer { + internal static readonly ArmIdentifierEqualityComparer Instance = new(); + public bool Equals(ArmIdentifier? x, ArmIdentifier? y) { if (x is null) diff --git a/src/Bicep.Core/Semantics/SemanticModel.cs b/src/Bicep.Core/Semantics/SemanticModel.cs index f3155fd4f99..dab573daab7 100644 --- a/src/Bicep.Core/Semantics/SemanticModel.cs +++ b/src/Bicep.Core/Semantics/SemanticModel.cs @@ -205,6 +205,7 @@ private static void TraceBuildOperation(BicepSourceFile sourceFile, IFeatureProv } public BicepSourceFile SourceFile { get; } + public IEnvironment Environment { get; } public BicepSourceFileKind SourceFileKind => this.SourceFile.FileKind; diff --git a/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs b/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs index 147064cdbc7..db5ee6dcdb2 100644 --- a/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs @@ -91,5 +91,92 @@ await client.Workspace.ExecuteCommand(new Command buildCommandOutput.Should().OnlyContainLFNewline(); buildCommandOutput.Should().BeEquivalentToIgnoringNewlines(expectedJson); } + + [TestMethod] + public async Task Build_command_with_transitive_imports_should_succeed_even_if_multiple_compilations_are_combined_unexpectedly() + { + var files = new Dictionary + { + { + "main.bicep", + """ + import * as typesB from 'moduleB.bicep' + import * as typesC from 'moduleC.bicep' + """ + }, + { + "moduleA.bicep", + """ + @export() + type typeA = { + propA: string + } + """ + }, + { + "moduleB.bicep", + """ + import * as typesA from 'moduleA.bicep' + @export() + type typeB = { + optionsA: typesA.typeA + propB: string + } + """ + }, + { + "moduleC.bicep", + """ + import * as typesA from 'moduleA.bicep' + @export() + type typeC = { + optionsA: typesA.typeA + propC: string + } + """ + }, + }; + + var outputDirectory = FileHelper.GetUniqueTestOutputPath(TestContext); + Directory.CreateDirectory(outputDirectory); + + foreach (var kvp in files) + { + using var file = File.CreateText(Path.Combine(outputDirectory, kvp.Key)); + await file.WriteAsync(kvp.Value); + } + + var diagnosticsListener = new MultipleMessageListener(); + + using var helper = await LanguageServerHelper.StartServer( + this.TestContext, + options => options.OnPublishDiagnostics(diagnosticsListener.AddMessage), + services => services + .WithNamespaceProvider(BuiltInTestTypes.Create()) + .WithFeatureOverrides(new(TestContext))); + var client = helper.Client; + + var mainPath = Path.Combine(outputDirectory, "main.bicep"); + var moduleCPath = Path.Combine(outputDirectory, "moduleC.bicep"); + + client.TextDocument.DidOpenTextDocument( + TextDocumentParamHelper.CreateDidOpenDocumentParamsFromFile(mainPath, 1)); + await diagnosticsListener.WaitNext(); + + client.TextDocument.DidOpenTextDocument( + TextDocumentParamHelper.CreateDidOpenDocumentParamsFromFile(moduleCPath, 1)); + await diagnosticsListener.WaitNext(); + + await client.Workspace.ExecuteCommand(new Command + { + Name = "build", + Arguments = new JArray { + mainPath, + } + }); + + var buildCommandOutput = File.ReadAllText(Path.ChangeExtension(mainPath, ".json")); + buildCommandOutput.Length.Should().BeGreaterThan(0); + } } } From e63fc92e57d84eeecdcc6cd2e0d7834d0b388c22 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Mon, 22 Jul 2024 11:53:30 -0400 Subject: [PATCH 2/3] Add some explanatory comments to the regression test --- .../BuildCommandTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs b/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs index db5ee6dcdb2..9a3e633a17d 100644 --- a/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/BuildCommandTests.cs @@ -92,6 +92,7 @@ await client.Workspace.ExecuteCommand(new Command buildCommandOutput.Should().BeEquivalentToIgnoringNewlines(expectedJson); } + // https://github.com/Azure/bicep/issues/14196 [TestMethod] public async Task Build_command_with_transitive_imports_should_succeed_even_if_multiple_compilations_are_combined_unexpectedly() { @@ -159,20 +160,26 @@ public async Task Build_command_with_transitive_imports_should_succeed_even_if_m var mainPath = Path.Combine(outputDirectory, "main.bicep"); var moduleCPath = Path.Combine(outputDirectory, "moduleC.bicep"); + // First, load main.bicep (and compile the transitive closure) client.TextDocument.DidOpenTextDocument( TextDocumentParamHelper.CreateDidOpenDocumentParamsFromFile(mainPath, 1)); await diagnosticsListener.WaitNext(); + // Second, load moduleC.bicep (and compile its transitive closure) + // Because main.bicep and moduleC.bicep exist in separate "active contexts" in the language server, this + // sequence of actions will result in the compilation manager having two separate SemanticModels for + // moduleA.bicep: one in the transitive closure of main.bicep (still reachable via the import of + // moduleB.bicep) and another in the transitive closure of moduleC.bicep. client.TextDocument.DidOpenTextDocument( TextDocumentParamHelper.CreateDidOpenDocumentParamsFromFile(moduleCPath, 1)); await diagnosticsListener.WaitNext(); + // Make sure we can still build main.bicep event though the `typeA` symbol from moduleA.bicep has two + // instantiations (and two different memory addresses) within the compilation manager. await client.Workspace.ExecuteCommand(new Command { Name = "build", - Arguments = new JArray { - mainPath, - } + Arguments = [mainPath], }); var buildCommandOutput = File.ReadAllText(Path.ChangeExtension(mainPath, ".json")); From cd9a48df6c663d2fa3c526a88aecfa114c92cb31 Mon Sep 17 00:00:00 2001 From: Jonathan Eskew Date: Mon, 22 Jul 2024 11:59:06 -0400 Subject: [PATCH 3/3] Revert private record renamings Renaming some of the records that were private to ImportClosureInfo created too much noise in the PR and obscured the changes that were being made. Names can be improved in a follow-up PR that does not alter behavior. --- .../CompileTimeImports/ImportClosureInfo.cs | 226 +++++++++--------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs b/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs index 3a5ef7d409d..3305f97db25 100644 --- a/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs +++ b/src/Bicep.Core/Emit/CompileTimeImports/ImportClosureInfo.cs @@ -38,27 +38,27 @@ public record ImportClosureInfo(ImmutableArray ImportedT public static ImportClosureInfo Calculate(SemanticModel model) { - SymbolFromForeignTemplateFactory referenceFactory = new(model.SourceFile.FileUri); + IntraTemplateSymbolicReferenceFactory referenceFactory = new(model.SourceFile.FileUri); var closure = CalculateImportClosure(model, referenceFactory); - var closureMetadata = CalculateImportedSymbolMetadata(model, closure); + var closureMetadata = CalculateImportedSymbolNames(model, closure); - var importedBicepSymbolNames = closureMetadata.Keys.OfType() + var importedBicepSymbolNames = closureMetadata.Keys.OfType() .ToImmutableDictionary(@ref => @ref.Symbol, @ref => closureMetadata[@ref]); - var importedSymbolNames = closure.ImportedSymbolsToForeignTemplateSymbols + var importedSymbolNames = closure.ImportedSymbolsToIntraTemplateSymbols .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value]); var wildcardImportPropertyNames = closure.WildcardImportPropertiesToIntraTemplateSymbols .ToImmutableDictionary(kvp => kvp.Key, kvp => closureMetadata[kvp.Value]); - var synthesizedVariableNames = closureMetadata.Keys.OfType() + var synthesizedVariableNames = closureMetadata.Keys.OfType() .ToLookup(@ref => @ref.SourceBicepModel) .ToImmutableDictionary(g => g.Key, g => g.ToImmutableDictionary(@ref => @ref.Name, @ref => closureMetadata[@ref])); - var importedArmSymbolNamesByFile = closureMetadata.Keys.OfType() + var importedArmSymbolNamesByFile = closureMetadata.Keys.OfType() .ToLookup(@ref => @ref.ArmTemplateFile) .ToImmutableDictionary(grouping => grouping.Key, grouping => grouping.ToImmutableDictionary( @ref => new ArmIdentifier(@ref.Type, @ref.Identifier), @ref => closureMetadata[@ref], ArmIdentifierEqualityComparer.Instance)); - var armDeclarationToExpressionConverters = closureMetadata.Keys.OfType() + var armDeclarationToExpressionConverters = closureMetadata.Keys.OfType() .Select(@ref => @ref.ArmTemplateFile) .Distinct() .ToImmutableDictionary(templateFile => templateFile, @@ -76,7 +76,7 @@ public static ImportClosureInfo Calculate(SemanticModel model) importedSymbolMetadata.Add(name, new(symbol.SourceTemplateIdentifier, symbol.OriginalSymbolName)); switch (symbol) { - case SymbolFromForeignArmTemplate armRef: + case ArmSymbolicReference armRef: var converter = armDeclarationToExpressionConverters[armRef.ArmTemplateFile].WithSourceSyntax(closure.SymbolsInImportClosure[armRef]); switch (armRef.Type) { @@ -93,7 +93,7 @@ public static ImportClosureInfo Calculate(SemanticModel model) throw new UnreachableException($"Unknown ARM symbol type: {armRef.Type}"); } break; - case SymbolFromForeignBicepTemplate bicepRef: + case BicepSymbolicReference bicepRef: var migrator = new ImportedSymbolDeclarationMigrator(bicepRef.SourceBicepModel, importedBicepSymbolNames, synthesizedVariableNames.TryGetValue(bicepRef.SourceBicepModel, out var dict) ? dict : ImmutableDictionary.Empty, @@ -114,11 +114,11 @@ public static ImportClosureInfo Calculate(SemanticModel model) throw new UnreachableException($"Cannot import Bicep symbols of type {bicepRef.Symbol.GetType().Name}"); } break; - case SynthesizedVariableFromForeignBicepTemplate synthesizedVariableRef: + case BicepSynthesizedVariableReference synthesizedVariableRef: importedVariables.Add(name, new(closure.SymbolsInImportClosure[synthesizedVariableRef], name, synthesizedVariableRef.Value)); break; default: - throw new UnreachableException($"This switch was expected to exhaustively process all kinds of {nameof(SymbolImportedFromForeignTemplate)} but did not handle an instance of type {symbol.GetType().Name}"); + throw new UnreachableException($"This switch was expected to exhaustively process all kinds of {nameof(IntraTemplateSymbolicReference)} but did not handle an instance of type {symbol.GetType().Name}"); } } @@ -132,111 +132,111 @@ public static ImportClosureInfo Calculate(SemanticModel model) private static ImportClosure CalculateImportClosure( SemanticModel model, - SymbolFromForeignTemplateFactory referenceFactory) + IntraTemplateSymbolicReferenceFactory referenceFactory) { - Dictionary symbolsInImportClosure = new(SymbolImportedFromForeignTemplateComparer.Instance); - Dictionary importedSymbolsToIntraTemplateSymbols = new(); - Dictionary wildcardImportPropertiesToIntraTemplateSymbols = new(); + Dictionary symbolsInImportClosure = new(IntraTemplateSymbolicReferenceComparer.Instance); + Dictionary importedSymbolsToIntraTemplateSymbols = new(); + Dictionary wildcardImportPropertiesToIntraTemplateSymbols = new(); ConcurrentDictionary armReferenceCollectors = new(); ConcurrentDictionary bicepEmitterContexts = new(); Queue searchQueue = new(model.Root.ImportedSymbols - .Select(importedSymbol => new SearchQueueItem(importedSymbol.DeclaringSyntax, new BicepImportedSymbol(importedSymbol, model, GetImportReference(importedSymbol)))) + .Select(importedSymbol => new SearchQueueItem(importedSymbol.DeclaringSyntax, new BicepImportedSymbolReference(importedSymbol, model, GetImportReference(importedSymbol)))) .Concat(model.Root.WildcardImports - .Select(wildcardImport => new SearchQueueItem(wildcardImport.DeclaringSyntax, new BicepWildcardImportSymbol(wildcardImport, model, GetImportReference(wildcardImport)))))); + .Select(wildcardImport => new SearchQueueItem(wildcardImport.DeclaringSyntax, new BicepWildcardImportSymbolicReference(wildcardImport, model, GetImportReference(wildcardImport)))))); while (searchQueue.Count > 0) { var item = searchQueue.Dequeue(); - if (item.Symbol is SymbolFromForeignBicepTemplate bicepSymbol) + if (item.SymbolicReference is BicepSymbolicReference bicepSymbolicReference) { - if (symbolsInImportClosure.TryAdd(bicepSymbol, item.InitiallyDeclaringSyntax)) + if (symbolsInImportClosure.TryAdd(bicepSymbolicReference, item.InitiallyDeclaringSyntax)) { - foreach (var reference in CollectReferences(bicepSymbol)) + foreach (var reference in CollectReferences(bicepSymbolicReference)) { searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, reference)); } foreach (var synthesizedVariableReference in CollectSynthesizedVariableReferences( - bicepSymbol, - bicepEmitterContexts.GetOrAdd(bicepSymbol.SourceBicepModel, m => new(m)))) + bicepSymbolicReference, + bicepEmitterContexts.GetOrAdd(bicepSymbolicReference.SourceBicepModel, m => new(m)))) { symbolsInImportClosure.TryAdd(synthesizedVariableReference, item.InitiallyDeclaringSyntax); } } } - else if (item.Symbol is SymbolFromForeignArmTemplate armSymbol) + else if (item.SymbolicReference is ArmSymbolicReference armSymbolicReference) { - if (symbolsInImportClosure.TryAdd(armSymbol, item.InitiallyDeclaringSyntax)) + if (symbolsInImportClosure.TryAdd(armSymbolicReference, item.InitiallyDeclaringSyntax)) { - ArmIdentifier refTarget = new(armSymbol.Type, armSymbol.Identifier); + ArmIdentifier refTarget = new(armSymbolicReference.Type, armSymbolicReference.Identifier); foreach (var reference in armReferenceCollectors.GetOrAdd( - armSymbol.ArmTemplateFile, + armSymbolicReference.ArmTemplateFile, f => new(f)).EnumerateReferencesUsedInDefinitionOf(refTarget)) { searchQueue.Enqueue(new( item.InitiallyDeclaringSyntax, - SymbolFromForeignTemplateFactory.SymbolFor( + IntraTemplateSymbolicReferenceFactory.SymbolFor( reference.SymbolType, reference.Identifier, - armSymbol))); + armSymbolicReference))); } } } - else if (item.Symbol is BicepWildcardImportSymbol wildcardImportSymbol) + else if (item.SymbolicReference is BicepWildcardImportSymbolicReference wildcardImportSymbolicReference) { foreach (var (propertyName, exportedSymbol) in EnumerateExportedSymbolsAsIntraTemplateSymbols( - wildcardImportSymbol.Symbol.SourceModel, - wildcardImportSymbol, + wildcardImportSymbolicReference.Symbol.SourceModel, + wildcardImportSymbolicReference, referenceFactory)) { - wildcardImportPropertiesToIntraTemplateSymbols[new(wildcardImportSymbol.Symbol, propertyName)] = exportedSymbol; + wildcardImportPropertiesToIntraTemplateSymbols[new(wildcardImportSymbolicReference.Symbol, propertyName)] = exportedSymbol; searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, exportedSymbol)); } } - else if (item.Symbol is BicepImportedSymbol importedSymbol) + else if (item.SymbolicReference is BicepImportedSymbolReference importedSymbolReference) { - var targetModel = importedSymbol.Symbol.SourceModel; + var targetModel = importedSymbolReference.Symbol.SourceModel; - var name = importedSymbol.Symbol.OriginalSymbolName - ?? throw new InvalidOperationException($"The import symbol {importedSymbol.Symbol.Name} did not specify what symbol to import"); + var name = importedSymbolReference.Symbol.OriginalSymbolName + ?? throw new InvalidOperationException($"The import symbol {importedSymbolReference.Symbol.Name} did not specify what symbol to import"); if (!targetModel.Exports.TryGetValue(name, out var exportMetadata)) { throw new InvalidOperationException($"No export named {name} found in {TemplateIdentifier( model.SourceFile.FileUri, targetModel, - importedSymbol.ImportTarget)}"); + importedSymbolReference.ImportTarget)}"); } - SymbolImportedFromForeignTemplate target = targetModel switch + IntraTemplateSymbolicReference target = targetModel switch { SemanticModel targetBicepModel => referenceFactory.SymbolFor( FindSymbolNamed(name, targetBicepModel), targetBicepModel, - importedSymbol), + importedSymbolReference), ArmTemplateSemanticModel targetArmModel => ReferenceForArmTarget( exportMetadata, targetArmModel.SourceFile, targetModel, - importedSymbol, + importedSymbolReference, referenceFactory), TemplateSpecSemanticModel targetTemplateSpecModel => ReferenceForArmTarget( exportMetadata, targetTemplateSpecModel.SourceFile.MainTemplateFile, targetModel, - importedSymbol, + importedSymbolReference, referenceFactory), _ => throw new InvalidOperationException($"Unrecognized module type {targetModel.GetType().Name} encountered"), }; - importedSymbolsToIntraTemplateSymbols[importedSymbol.Symbol] = target; + importedSymbolsToIntraTemplateSymbols[importedSymbolReference.Symbol] = target; searchQueue.Enqueue(new(item.InitiallyDeclaringSyntax, target)); } else { - throw new InvalidOperationException($"Unexpected symbolic reference type of {item.Symbol.GetType().Name} encountered."); + throw new InvalidOperationException($"Unexpected symbolic reference type of {item.SymbolicReference.GetType().Name} encountered."); } } @@ -267,20 +267,20 @@ private static ArtifactReference GetImportReference(WildcardImportSymbol symbol) throw new InvalidOperationException("Unable to load module reference for import statement"); } - private static IEnumerable CollectReferences(SymbolFromForeignBicepTemplate typeReference) + private static IEnumerable CollectReferences(BicepSymbolicReference typeReference) => typeReference.SourceBicepModel.Binder.GetSymbolsReferencedInDeclarationOf(typeReference.Symbol) - .Select(symbol => symbol switch + .Select(symbol => symbol switch { ProviderNamespaceSymbol => null, // this was the base expression of a fully qualified ambient type reference (e.g., sys.string) LocalVariableSymbol => null, // local variables are contained within the expression and are not external references - TypeAliasSymbol typeAlias => SymbolFromForeignTemplateFactory.SymbolFor(typeAlias, typeReference), - VariableSymbol variable => SymbolFromForeignTemplateFactory.SymbolFor(variable, typeReference), - DeclaredFunctionSymbol func => SymbolFromForeignTemplateFactory.SymbolFor(func, typeReference), - ImportedSymbol imported => new BicepImportedSymbol( + TypeAliasSymbol typeAlias => IntraTemplateSymbolicReferenceFactory.SymbolFor(typeAlias, typeReference), + VariableSymbol variable => IntraTemplateSymbolicReferenceFactory.SymbolFor(variable, typeReference), + DeclaredFunctionSymbol func => IntraTemplateSymbolicReferenceFactory.SymbolFor(func, typeReference), + ImportedSymbol imported => new BicepImportedSymbolReference( imported, typeReference.SourceBicepModel, GetImportReference(imported)), - WildcardImportSymbol wildcardImport => new BicepWildcardImportSymbol( + WildcardImportSymbol wildcardImport => new BicepWildcardImportSymbolicReference( wildcardImport, typeReference.SourceBicepModel, GetImportReference(wildcardImport)), @@ -289,21 +289,21 @@ private static IEnumerable CollectReferences(SymbolFromForeignBi }) .WhereNotNull(); - private static IEnumerable CollectSynthesizedVariableReferences( - SymbolFromForeignBicepTemplate @ref, + private static IEnumerable CollectSynthesizedVariableReferences( + BicepSymbolicReference @ref, EmitterContext emitterContext ) => SyntaxAggregator.AggregateByType(@ref.Symbol.DeclaringSyntax) .Select(functionCallSyntax => emitterContext.FunctionVariables.TryGetValue(functionCallSyntax, out var result) - ? SymbolFromForeignTemplateFactory.SymbolFor(result.Name, result.Value, @ref) + ? IntraTemplateSymbolicReferenceFactory.SymbolFor(result.Name, result.Value, @ref) : null) .WhereNotNull(); private static IEnumerable< - (string symbolName, SymbolImportedFromForeignTemplate reference) + (string symbolName, IntraTemplateSymbolicReference reference) > EnumerateExportedSymbolsAsIntraTemplateSymbols( ISemanticModel model, - BicepWildcardImportSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory + BicepWildcardImportSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory ) => model switch { SemanticModel bicepModel => EnumerateExportedSymbolsAsIntraTemplateSymbols( @@ -322,11 +322,11 @@ SymbolFromForeignTemplateFactory referenceFactory }; private static IEnumerable< - (string symbolName, SymbolImportedFromForeignTemplate reference) + (string symbolName, IntraTemplateSymbolicReference reference) > EnumerateExportedSymbolsAsIntraTemplateSymbols( SemanticModel model, - BicepWildcardImportSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory + BicepWildcardImportSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory ) => model.Root.TypeDeclarations .Concat(model.Root.VariableDeclarations) .Concat(model.Root.FunctionDeclarations) @@ -334,42 +334,42 @@ SymbolFromForeignTemplateFactory referenceFactory .Select(s => (s.Name, referenceFactory.SymbolFor(s, model, referrer))); private static IEnumerable< - (string symbolName, SymbolImportedFromForeignTemplate reference) + (string symbolName, IntraTemplateSymbolicReference reference) > EnumerateExportedSymbolsAsIntraTemplateSymbols( ArmTemplateSemanticModel model, - BicepWildcardImportSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory + BicepWildcardImportSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory ) => EnumerateExportedSymbolsAsIntraTemplateSymbols(model, model.SourceFile, referrer, referenceFactory); private static IEnumerable< - (string symbolName, SymbolImportedFromForeignTemplate reference) + (string symbolName, IntraTemplateSymbolicReference reference) > EnumerateExportedSymbolsAsIntraTemplateSymbols( TemplateSpecSemanticModel model, - BicepWildcardImportSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory + BicepWildcardImportSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory ) => EnumerateExportedSymbolsAsIntraTemplateSymbols( model, model.SourceFile.MainTemplateFile, referrer, referenceFactory); - private static IEnumerable<(string symbolName, SymbolImportedFromForeignTemplate reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols( + private static IEnumerable<(string symbolName, IntraTemplateSymbolicReference reference)> EnumerateExportedSymbolsAsIntraTemplateSymbols( ISemanticModel model, ArmTemplateFile templateFile, - BicepWildcardImportSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory - ) => model.Exports.Values.Select( + BicepWildcardImportSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory + ) => model.Exports.Values.Select( md => (md.Name, ReferenceForArmTarget(md, templateFile, model, referrer, referenceFactory))); private static DeclaredSymbol FindSymbolNamed(string nameOfSymbolSought, SemanticModel model) => model.Root.Declarations.Where(t => LanguageConstants.IdentifierComparer.Equals(t.Name, nameOfSymbolSought)).Single(); - private static SymbolImportedFromForeignTemplate ReferenceForArmTarget( + private static IntraTemplateSymbolicReference ReferenceForArmTarget( ExportMetadata targetMetadata, ArmTemplateFile sourceTemplateFile, ISemanticModel sourceModel, - InterTemplateSymbol referrer, - SymbolFromForeignTemplateFactory referenceFactory + InterTemplateSymbolicReference referrer, + IntraTemplateSymbolicReferenceFactory referenceFactory ) => targetMetadata switch { @@ -394,17 +394,17 @@ SymbolFromForeignTemplateFactory referenceFactory _ => throw new InvalidOperationException($"Unrecognized export metadata type: {targetMetadata.GetType().Name}"), }; - private static ImmutableDictionary CalculateImportedSymbolMetadata( + private static ImmutableDictionary CalculateImportedSymbolNames( SemanticModel model, ImportClosure closure) { - var importedSymbolNames = ImmutableDictionary.CreateBuilder( - SymbolImportedFromForeignTemplateComparer.Instance); + var importedSymbolNames = ImmutableDictionary.CreateBuilder( + IntraTemplateSymbolicReferenceComparer.Instance); // Every symbol explicitly imported into the model by name should keep that name in the compiled template. foreach (var importedSymbol in model.Root.ImportedSymbols) { - importedSymbolNames[closure.ImportedSymbolsToForeignTemplateSymbols[importedSymbol]] = importedSymbol.Name; + importedSymbolNames[closure.ImportedSymbolsToIntraTemplateSymbols[importedSymbol]] = importedSymbol.Name; } int uniqueIdentifier = 1; @@ -447,25 +447,25 @@ private static string TemplateIdentifier(Uri entryPointUri, ISemanticModel model _ => throw new InvalidOperationException($"Unrecognized module type {model.GetType().Name} encountered"), }; - private abstract record EnclosedSymbol(ISemanticModel SourceModel); - private abstract record InterTemplateSymbol(SemanticModel SourceBicepModule, ArtifactReference ImportTarget) - : EnclosedSymbol(SourceBicepModule); - private record BicepWildcardImportSymbol(WildcardImportSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) - : InterTemplateSymbol(SourceBicepModel, ImportTarget); - private record BicepImportedSymbol(ImportedSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) - : InterTemplateSymbol(SourceBicepModel, ImportTarget); + private abstract record SymbolicReference(ISemanticModel SourceModel); + private abstract record InterTemplateSymbolicReference(SemanticModel SourceBicepModule, ArtifactReference ImportTarget) + : SymbolicReference(SourceBicepModule); + private record BicepWildcardImportSymbolicReference(WildcardImportSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) + : InterTemplateSymbolicReference(SourceBicepModel, ImportTarget); + private record BicepImportedSymbolReference(ImportedSymbol Symbol, SemanticModel SourceBicepModel, ArtifactReference ImportTarget) + : InterTemplateSymbolicReference(SourceBicepModel, ImportTarget); - private abstract record SymbolImportedFromForeignTemplate(ISemanticModel SourceModel, string SourceTemplateIdentifier) - : EnclosedSymbol(SourceModel) + private abstract record IntraTemplateSymbolicReference(ISemanticModel SourceModel, string SourceTemplateIdentifier) + : SymbolicReference(SourceModel) { public abstract string OriginalSymbolName { get; } } - private class SymbolImportedFromForeignTemplateComparer : IEqualityComparer + private class IntraTemplateSymbolicReferenceComparer : IEqualityComparer { - internal static readonly SymbolImportedFromForeignTemplateComparer Instance = new(); + internal static readonly IntraTemplateSymbolicReferenceComparer Instance = new(); - public bool Equals(SymbolImportedFromForeignTemplate? x, SymbolImportedFromForeignTemplate? y) + public bool Equals(IntraTemplateSymbolicReference? x, IntraTemplateSymbolicReference? y) { if (x is null) { @@ -476,24 +476,24 @@ public bool Equals(SymbolImportedFromForeignTemplate? x, SymbolImportedFromForei x.OriginalSymbolName.Equals(y.OriginalSymbolName); } - public int GetHashCode(SymbolImportedFromForeignTemplate obj) + public int GetHashCode(IntraTemplateSymbolicReference obj) => HashCode.Combine(obj.SourceTemplateIdentifier, obj.OriginalSymbolName); } - private record SymbolFromForeignBicepTemplate(DeclaredSymbol Symbol, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) - : SymbolImportedFromForeignTemplate(SourceBicepModel, SourceTemplateIdentifier) + private record BicepSymbolicReference(DeclaredSymbol Symbol, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) + : IntraTemplateSymbolicReference(SourceBicepModel, SourceTemplateIdentifier) { public override string OriginalSymbolName => Symbol.Name; } - private record SynthesizedVariableFromForeignBicepTemplate(string Name, Expression Value, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) - : SymbolImportedFromForeignTemplate(SourceBicepModel, SourceTemplateIdentifier) + private record BicepSynthesizedVariableReference(string Name, Expression Value, SemanticModel SourceBicepModel, string SourceTemplateIdentifier) + : IntraTemplateSymbolicReference(SourceBicepModel, SourceTemplateIdentifier) { public override string OriginalSymbolName => Name; } - private record SymbolFromForeignArmTemplate(ArmSymbolType Type, string Identifier, ArmTemplateFile ArmTemplateFile, ISemanticModel SourceModel, string SourceTemplateIdentifier) - : SymbolImportedFromForeignTemplate(SourceModel, SourceTemplateIdentifier) + private record ArmSymbolicReference(ArmSymbolType Type, string Identifier, ArmTemplateFile ArmTemplateFile, ISemanticModel SourceModel, string SourceTemplateIdentifier) + : IntraTemplateSymbolicReference(SourceModel, SourceTemplateIdentifier) { public override string OriginalSymbolName => Type switch { @@ -513,52 +513,52 @@ ArmSymbolType.Function when Identifier.StartsWith(BicepDefinedFunctionNamePrefix }; } - private class SymbolFromForeignTemplateFactory + private class IntraTemplateSymbolicReferenceFactory { private readonly Uri entryPointUri; - internal SymbolFromForeignTemplateFactory(Uri entryPointUri) + internal IntraTemplateSymbolicReferenceFactory(Uri entryPointUri) { this.entryPointUri = entryPointUri; } - internal SymbolImportedFromForeignTemplate SymbolFor( + internal IntraTemplateSymbolicReference SymbolFor( DeclaredSymbol symbol, SemanticModel sourceBicepModel, - InterTemplateSymbol referrer - ) => new SymbolFromForeignBicepTemplate( + InterTemplateSymbolicReference referrer + ) => new BicepSymbolicReference( symbol, sourceBicepModel, TemplateIdentifier(entryPointUri, sourceBicepModel, referrer.ImportTarget)); - internal static SymbolFromForeignBicepTemplate SymbolFor( + internal static BicepSymbolicReference SymbolFor( DeclaredSymbol symbol, - SymbolFromForeignBicepTemplate enclosedBy + BicepSymbolicReference enclosedBy ) => new(symbol, enclosedBy.SourceBicepModel, enclosedBy.SourceTemplateIdentifier); - internal static SynthesizedVariableFromForeignBicepTemplate SymbolFor( + internal static BicepSynthesizedVariableReference SymbolFor( string name, Expression value, - SymbolFromForeignBicepTemplate enclosedBy + BicepSymbolicReference enclosedBy ) => new(name, value, enclosedBy.SourceBicepModel, enclosedBy.SourceTemplateIdentifier); - internal SymbolImportedFromForeignTemplate SymbolFor( + internal IntraTemplateSymbolicReference SymbolFor( ArmSymbolType type, string identifier, ArmTemplateFile armTemplateFile, ISemanticModel sourceModel, - InterTemplateSymbol referrer - ) => new SymbolFromForeignArmTemplate( + InterTemplateSymbolicReference referrer + ) => new ArmSymbolicReference( type, identifier, armTemplateFile, sourceModel, TemplateIdentifier(entryPointUri, sourceModel, referrer.ImportTarget)); - internal static SymbolFromForeignArmTemplate SymbolFor( + internal static ArmSymbolicReference SymbolFor( ArmSymbolType type, string identifier, - SymbolFromForeignArmTemplate enclosedBy + ArmSymbolicReference enclosedBy ) => new( type, identifier, @@ -568,12 +568,12 @@ SymbolFromForeignArmTemplate enclosedBy } private record ImportClosure( - IReadOnlyDictionary SymbolsInImportClosure, - IReadOnlyDictionary ImportedSymbolsToForeignTemplateSymbols, - IReadOnlyDictionary WildcardImportPropertiesToIntraTemplateSymbols, + IReadOnlyDictionary SymbolsInImportClosure, + IReadOnlyDictionary ImportedSymbolsToIntraTemplateSymbols, + IReadOnlyDictionary WildcardImportPropertiesToIntraTemplateSymbols, ConcurrentDictionary EmitterContexts); - private record SearchQueueItem(SyntaxBase InitiallyDeclaringSyntax, EnclosedSymbol Symbol); + private record SearchQueueItem(SyntaxBase InitiallyDeclaringSyntax, SymbolicReference SymbolicReference); private class ArmIdentifierEqualityComparer : IEqualityComparer {