Skip to content

Commit

Permalink
Code reorganization in preparation for ##691 (#969)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin authored Nov 25, 2020
1 parent cc37691 commit 7e07f45
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 194 deletions.
6 changes: 3 additions & 3 deletions src/Bicep.Core.UnitTests/Semantics/SymbolContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void LockedModeShouldBlockAccess()
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(""));
var bindings = new Dictionary<SyntaxBase, Symbol>();
var cyclesBySymbol = new Dictionary<DeclaredSymbol, ImmutableArray<DeclaredSymbol>>();
var context = new SymbolContext(new TypeManager(TestResourceTypeProvider.Create(), bindings, cyclesBySymbol, new SyntaxHierarchy(), ResourceScopeType.ResourceGroupScope), bindings, compilation);
var context = new SymbolContext(compilation, compilation.GetEntrypointSemanticModel());

Action byName = () =>
{
Expand All @@ -33,13 +33,13 @@ public void LockedModeShouldBlockAccess()

Action byNode = () =>
{
var b = context.Bindings;
var b = context.Compilation;
};
byNode.Should().Throw<InvalidOperationException>().WithMessage(expectedMessage);

context.Unlock();
context.TypeManager.Should().NotBeNull();
context.Bindings.Should().NotBeNull();
context.Compilation.Should().NotBeNull();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Bicep.Core.UnitTests.Utils;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Bicep.Core.UnitTests.TypeSystem
{
Expand Down Expand Up @@ -1113,6 +1114,13 @@ private TypeSymbol CreateDummyResourceType()
return new ResourceType(typeReference, new NamedObjectType(typeReference.FormatName(), TypeSymbolValidationFlags.Default, LanguageConstants.CreateResourceProperties(typeReference), null));
}

private TypeManager CreateTypeManager(SyntaxHierarchy hierarchy) => new TypeManager(TestResourceTypeProvider.Create(), new Dictionary<SyntaxBase, Symbol>(), new Dictionary<DeclaredSymbol, ImmutableArray<DeclaredSymbol>>(), hierarchy, ResourceScopeType.ResourceGroupScope);
private static TypeManager CreateTypeManager(SyntaxHierarchy hierarchy)
{
var binderMock = new Mock<IBinder>();
binderMock.Setup(x => x.GetParent(It.IsAny<SyntaxBase>()))
.Returns<SyntaxBase>(x => hierarchy.GetParent(x));

return new TypeManager(TestResourceTypeProvider.Create(), binderMock.Object);
}
}
}
111 changes: 111 additions & 0 deletions src/Bicep.Core/Semantics/Binder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Bicep.Core.Extensions;
using Bicep.Core.Semantics.Namespaces;
using Bicep.Core.Syntax;
using Bicep.Core.TypeSystem;

namespace Bicep.Core.Semantics
{
public class Binder : IBinder
{
private readonly SyntaxTree syntaxTree;
private readonly ImmutableDictionary<SyntaxBase, Symbol> bindings;
private readonly ImmutableDictionary<DeclaredSymbol, ImmutableArray<DeclaredSymbol>> cyclesBySymbol;

public Binder(SyntaxTree syntaxTree, ISymbolContext symbolContext)
{
// TODO use lazy or some other pattern for init
this.syntaxTree = syntaxTree;
this.TargetScope = SyntaxHelper.GetTargetScope(syntaxTree);
var allDeclarations = GetAllDeclarations(syntaxTree, symbolContext);
var uniqueDeclarations = GetUniqueDeclarations(allDeclarations);
var builtInNamespacs = GetBuiltInNamespaces(this.TargetScope);
this.bindings = GetBindings(syntaxTree, uniqueDeclarations, builtInNamespacs);
this.cyclesBySymbol = GetCyclesBySymbol(syntaxTree, uniqueDeclarations, this.bindings);

// TODO: Avoid looping 5 times?
this.FileSymbol = new FileSymbol(
syntaxTree.FileUri.LocalPath,
syntaxTree.ProgramSyntax,
builtInNamespacs,
allDeclarations.OfType<ParameterSymbol>(),
allDeclarations.OfType<VariableSymbol>(),
allDeclarations.OfType<ResourceSymbol>(),
allDeclarations.OfType<ModuleSymbol>(),
allDeclarations.OfType<OutputSymbol>());
}

public ResourceScopeType TargetScope { get; }

public FileSymbol FileSymbol { get; }

public SyntaxBase? GetParent(SyntaxBase syntax)
=> syntaxTree.Hierarchy.GetParent(syntax);

/// <summary>
/// Returns the symbol that was bound to the specified syntax node. Will return null for syntax nodes that never get bound to symbols. Otherwise,
/// a symbol will always be returned. Binding failures are represented with a non-null error symbol.
/// </summary>
/// <param name="syntax">the syntax node</param>
public Symbol? GetSymbolInfo(SyntaxBase syntax) => this.bindings.TryGetValue(syntax);

/// <summary>
/// Returns all syntax nodes that represent a reference to the specified symbol. This includes the definitions of the symbol as well.
/// Unusued declarations will return 1 result. Unused and undeclared symbols (functions, namespaces, for example) may return an empty list.
/// </summary>
/// <param name="symbol">The symbol</param>
public IEnumerable<SyntaxBase> FindReferences(Symbol symbol) => this.bindings
.Where(binding => ReferenceEquals(binding.Value, symbol))
.Select(binding => binding.Key);

public ImmutableArray<DeclaredSymbol>? TryGetCycle(DeclaredSymbol declaredSymbol)
=> this.cyclesBySymbol.TryGetValue(declaredSymbol, out var cycle) ? cycle : null;

private static ImmutableArray<DeclaredSymbol> GetAllDeclarations(SyntaxTree syntaxTree, ISymbolContext symbolContext)
{
// collect declarations
var declarations = new List<DeclaredSymbol>();
var declarationVisitor = new DeclarationVisitor(symbolContext, declarations);
declarationVisitor.Visit(syntaxTree.ProgramSyntax);

return declarations.ToImmutableArray();
}

private static ImmutableDictionary<string, DeclaredSymbol> GetUniqueDeclarations(IEnumerable<DeclaredSymbol> allDeclarations)
{
// in cases of duplicate declarations we will see multiple declaration symbols in the result list
// for simplicitly we will bind to the first one
// it may cause follow-on type errors, but there will also be errors about duplicate identifiers as well
return allDeclarations
.ToLookup(x => x.Name, LanguageConstants.IdentifierComparer)
.ToImmutableDictionary(x => x.Key, x => x.First(), LanguageConstants.IdentifierComparer);
}

private static ImmutableDictionary<string, NamespaceSymbol> GetBuiltInNamespaces(ResourceScopeType targetScope)
{
var namespaces = new NamespaceSymbol[] { new SystemNamespaceSymbol(), new AzNamespaceSymbol(targetScope) };

return namespaces.ToImmutableDictionary(property => property.Name, property => property, LanguageConstants.IdentifierComparer);
}

private static ImmutableDictionary<SyntaxBase, Symbol> GetBindings(SyntaxTree syntaxTree, IReadOnlyDictionary<string, DeclaredSymbol> uniqueDeclarations, ImmutableDictionary<string, NamespaceSymbol> builtInNamespaces)
{
// bind identifiers to declarations
var bindings = new Dictionary<SyntaxBase, Symbol>();
var binder = new NameBindingVisitor(uniqueDeclarations, bindings, builtInNamespaces);
binder.Visit(syntaxTree.ProgramSyntax);

return bindings.ToImmutableDictionary();
}

private static ImmutableDictionary<DeclaredSymbol, ImmutableArray<DeclaredSymbol>> GetCyclesBySymbol(SyntaxTree syntaxTree, IReadOnlyDictionary<string, DeclaredSymbol> uniqueDeclarations, IReadOnlyDictionary<SyntaxBase, Symbol> bindings)
{
return CyclicCheckVisitor.FindCycles(syntaxTree.ProgramSyntax, uniqueDeclarations, bindings);
}
}
}
96 changes: 6 additions & 90 deletions src/Bicep.Core/Semantics/Compilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,119 +4,35 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Bicep.Core.Diagnostics;
using Bicep.Core.Semantics.Namespaces;
using Bicep.Core.Syntax;
using Bicep.Core.TypeSystem;

namespace Bicep.Core.Semantics
{
public class Compilation
{
private readonly IResourceTypeProvider resourceTypeProvider;
private readonly ImmutableDictionary<SyntaxTree, Lazy<SemanticModel>> lazySemanticModelLookup;

public SyntaxTreeGrouping SyntaxTreeGrouping { get; }

public Compilation(IResourceTypeProvider resourceTypeProvider, SyntaxTreeGrouping syntaxTreeGrouping)
{
this.SyntaxTreeGrouping = syntaxTreeGrouping;
this.resourceTypeProvider = resourceTypeProvider;
this.ResourceTypeProvider = resourceTypeProvider;
this.lazySemanticModelLookup = syntaxTreeGrouping.SyntaxTrees.ToImmutableDictionary(
syntaxTree => syntaxTree,
syntaxTree => new Lazy<SemanticModel>(() => GetSemanticModelInternal(syntaxTree)));
syntaxTree => new Lazy<SemanticModel>(() => new SemanticModel(this, syntaxTree)));
}

public SyntaxTreeGrouping SyntaxTreeGrouping { get; }

public IResourceTypeProvider ResourceTypeProvider { get; }

public SemanticModel GetEntrypointSemanticModel()
=> GetSemanticModel(SyntaxTreeGrouping.EntryPoint);

public SemanticModel GetSemanticModel(SyntaxTree syntaxTree)
=> this.lazySemanticModelLookup[syntaxTree].Value;

private static ResourceScopeType GetTargetScope(SyntaxTree syntaxTree)
{
var defaultTargetScope = ResourceScopeType.ResourceGroupScope;
var targetSyntax = syntaxTree.ProgramSyntax.Children.OfType<TargetScopeSyntax>().FirstOrDefault();
if (targetSyntax == null)
{
return defaultTargetScope;
}

var targetScope = SyntaxHelper.GetTargetScope(targetSyntax);
if (targetScope == ResourceScopeType.None)
{
return defaultTargetScope;
}

return targetScope;
}

private SemanticModel GetSemanticModelInternal(SyntaxTree syntaxTree)
{
var targetScope = GetTargetScope(syntaxTree);

var builtinNamespaces =
new NamespaceSymbol[] { new SystemNamespaceSymbol(), new AzNamespaceSymbol(targetScope) }
.ToImmutableDictionary(property => property.Name, property => property, LanguageConstants.IdentifierComparer);

var bindings = new Dictionary<SyntaxBase, Symbol>();
var cyclesBySymbol = new Dictionary<DeclaredSymbol, ImmutableArray<DeclaredSymbol>>();

var hierarchy = new SyntaxHierarchy();
hierarchy.AddRoot(syntaxTree.ProgramSyntax);

// create this in locked mode by default
// this blocks accidental type or binding queries until binding is done
// (if a type check is done too early, unbound symbol references would cause incorrect type check results)
var symbolContext = new SymbolContext(new TypeManager(resourceTypeProvider, bindings, cyclesBySymbol, hierarchy, targetScope), bindings, this);

// collect declarations
var declarations = new List<DeclaredSymbol>();
var declarationVisitor = new DeclarationVisitor(symbolContext, declarations);
declarationVisitor.Visit(syntaxTree.ProgramSyntax);

// in cases of duplicate declarations we will see multiple declaration symbols in the result list
// for simplicitly we will bind to the first one
// it may cause follow-on type errors, but there will also be errors about duplicate identifiers as well
var uniqueDeclarations = declarations
.ToLookup(x => x.Name, LanguageConstants.IdentifierComparer)
.ToImmutableDictionary(x => x.Key, x => x.First(), LanguageConstants.IdentifierComparer);

// bind identifiers to declarations
var binder = new NameBindingVisitor(uniqueDeclarations, bindings, builtinNamespaces);
binder.Visit(syntaxTree.ProgramSyntax);

var shortestCycleBySymbol = CyclicCheckVisitor.FindCycles(syntaxTree.ProgramSyntax, uniqueDeclarations, bindings);
foreach (var kvp in shortestCycleBySymbol)
{
cyclesBySymbol.Add(kvp.Key, kvp.Value);
}

// TODO: Avoid looping 5 times?
var file = new FileSymbol(
syntaxTree.FileUri.LocalPath,
syntaxTree.ProgramSyntax,
builtinNamespaces,
declarations.OfType<ParameterSymbol>(),
declarations.OfType<VariableSymbol>(),
declarations.OfType<ResourceSymbol>(),
declarations.OfType<ModuleSymbol>(),
declarations.OfType<OutputSymbol>());

// name binding is done
// allow type queries now
symbolContext.Unlock();

foreach (var targetScopeSyntax in syntaxTree.ProgramSyntax.Children.OfType<TargetScopeSyntax>())
{
// force a type check to get diagnostics for the target scope
symbolContext.TypeManager.GetTypeInfo(targetScopeSyntax);
}

return new SemanticModel(file, symbolContext.TypeManager, bindings, targetScope);
}

public IReadOnlyDictionary<SyntaxTree, IEnumerable<Diagnostic>> GetAllDiagnosticsBySyntaxTree()
=> SyntaxTreeGrouping.SyntaxTrees.ToDictionary(
syntaxTree => syntaxTree,
Expand Down
24 changes: 24 additions & 0 deletions src/Bicep.Core/Semantics/IBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Collections.Immutable;
using Bicep.Core.Syntax;
using Bicep.Core.TypeSystem;

namespace Bicep.Core.Semantics
{
public interface IBinder
{
ResourceScopeType TargetScope { get; }

FileSymbol FileSymbol { get; }

SyntaxBase? GetParent(SyntaxBase syntax);

IEnumerable<SyntaxBase> FindReferences(Symbol symbol);

Symbol? GetSymbolInfo(SyntaxBase syntax);

ImmutableArray<DeclaredSymbol>? TryGetCycle(DeclaredSymbol declaredSymbol);
}
}
2 changes: 0 additions & 2 deletions src/Bicep.Core/Semantics/ISymbolContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ public interface ISymbolContext
{
ITypeManager TypeManager { get; }

IReadOnlyDictionary<SyntaxBase, Symbol> Bindings { get; }

Compilation Compilation { get; }
}
}
2 changes: 0 additions & 2 deletions src/Bicep.Core/Semantics/ITypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ namespace Bicep.Core.Semantics
{
public interface ITypeManager
{
IResourceTypeProvider ResourceTypeProvider { get; }

TypeSymbol GetTypeInfo(SyntaxBase syntax);

TypeSymbol? GetDeclaredType(SyntaxBase syntax);
Expand Down
Loading

0 comments on commit 7e07f45

Please sign in to comment.