Skip to content

Commit

Permalink
Merge pull request #1771 from riganti/property-compilation-improvements
Browse files Browse the repository at this point in the history
Property directive compilation extensibility improvements
  • Loading branch information
exyi committed Feb 28, 2024
2 parents d73e409 + 6117fc9 commit 88ffbf6
Show file tree
Hide file tree
Showing 18 changed files with 193 additions and 192 deletions.
Expand Up @@ -12,7 +12,7 @@ namespace DotVVM.Framework.Compilation.ControlTree
{
public interface IAbstractTreeBuilder
{
IAbstractTreeRoot BuildTreeRoot(IControlTreeResolver controlTreeResolver, IControlResolverMetadata metadata, DothtmlRootNode node, IDataContextStack dataContext, IReadOnlyDictionary<string, IReadOnlyList<IAbstractDirective>> directives, IAbstractControlBuilderDescriptor? masterPage);
IAbstractTreeRoot BuildTreeRoot(IControlTreeResolver controlTreeResolver, IControlResolverMetadata metadata, DothtmlRootNode node, IDataContextStack dataContext, ImmutableDictionary<string, ImmutableList<IAbstractDirective>> directives, IAbstractControlBuilderDescriptor? masterPage);

IAbstractControl BuildControl(IControlResolverMetadata metadata, DothtmlNode? node, IDataContextStack dataContext);

Expand Down
Expand Up @@ -22,7 +22,7 @@ public ResolvedTreeBuilder(BindingCompilationService bindingService, DirectiveCo
this.directiveService = directiveService;
}

public IAbstractTreeRoot BuildTreeRoot(IControlTreeResolver controlTreeResolver, IControlResolverMetadata metadata, DothtmlRootNode node, IDataContextStack dataContext, IReadOnlyDictionary<string, IReadOnlyList<IAbstractDirective>> directives, IAbstractControlBuilderDescriptor? masterPage)
public IAbstractTreeRoot BuildTreeRoot(IControlTreeResolver controlTreeResolver, IControlResolverMetadata metadata, DothtmlRootNode node, IDataContextStack dataContext, ImmutableDictionary<string, ImmutableList<IAbstractDirective>> directives, IAbstractControlBuilderDescriptor? masterPage)
{
return new ResolvedTreeRoot((ControlResolverMetadata)metadata, node, (DataContextStack)dataContext, directives, (ControlBuilderDescriptor?)masterPage);
}
Expand Down
Expand Up @@ -41,7 +41,7 @@ public class ResolvedTreeRoot : ResolvedControl, IAbstractTreeRoot
return null;
}

public ResolvedTreeRoot(ControlResolverMetadata metadata, DothtmlNode node, DataContextStack dataContext, IReadOnlyDictionary<string, IReadOnlyList<IAbstractDirective>> directives, ControlBuilderDescriptor? masterPage)
public ResolvedTreeRoot(ControlResolverMetadata metadata, DothtmlNode node, DataContextStack dataContext, ImmutableDictionary<string, ImmutableList<IAbstractDirective>> directives, ControlBuilderDescriptor? masterPage)
: base(metadata, node, null, dataContext)
{
this.MasterPage = masterPage;
Expand Down
@@ -1,22 +1,18 @@
using System;
using System.Collections.Generic;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.Parser;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Controls;
using System.Reflection;
using System.Reflection.Emit;
using DotVVM.Framework.Controls.Infrastructure;
using DotVVM.Framework.Utils;
using System.Linq;
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;

namespace DotVVM.Framework.Compilation.Directives
{
public abstract class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBaseTypeDirective, ITypeDescriptor>
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBaseTypeDirective, ITypeDescriptor>
{
private readonly string fileName;
private readonly ImmutableList<NamespaceImport> imports;
Expand All @@ -27,7 +23,7 @@ public abstract class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBas
protected virtual ITypeDescriptor DotvvmMarkupControlType => new ResolvedTypeDescriptor(typeof(DotvvmMarkupControl));

public BaseTypeDirectiveCompiler(
IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder, string fileName, ImmutableList<NamespaceImport> imports)
DirectiveDictionary directiveNodesByName, IAbstractTreeBuilder treeBuilder, string fileName, ImmutableList<NamespaceImport> imports)
: base(directiveNodesByName, treeBuilder)
{
this.fileName = fileName;
Expand All @@ -37,7 +33,7 @@ public abstract class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBas
protected override IAbstractBaseTypeDirective Resolve(DothtmlDirectiveNode directiveNode)
=> TreeBuilder.BuildBaseTypeDirective(directiveNode, ParseDirective(directiveNode, p => p.ReadDirectiveTypeName()), imports);

protected override ITypeDescriptor CreateArtefact(IReadOnlyList<IAbstractBaseTypeDirective> resolvedDirectives)
protected override ITypeDescriptor CreateArtefact(ImmutableList<IAbstractBaseTypeDirective> resolvedDirectives)
{
var wrapperType = GetDefaultWrapperType();

Expand Down Expand Up @@ -68,41 +64,9 @@ protected override ITypeDescriptor CreateArtefact(IReadOnlyList<IAbstractBaseTyp
}
}

if (DirectiveNodesByName.TryGetValue(ParserConstants.PropertyDeclarationDirective, out var propertyDirectives) && propertyDirectives.Any())
{
wrapperType = CreateDynamicDeclaringType(wrapperType, propertyDirectives) ?? wrapperType;
}

return wrapperType;
}

/// <summary> Gets or creates dynamic declaring type, and registers on it the properties declared using `@property` directives </summary>
protected virtual ITypeDescriptor? CreateDynamicDeclaringType(
ITypeDescriptor? originalWrapperType,
IEnumerable<DothtmlDirectiveNode> propertyDirectives
)
{
var imports = DirectiveNodesByName.GetValueOrDefault(ParserConstants.ImportNamespaceDirective, Array.Empty<DothtmlDirectiveNode>())
.Select(d => d.Value.Trim()).OrderBy(s => s).ToImmutableArray();
var properties = propertyDirectives
.Select(p => p.Value.Trim()).OrderBy(s => s).ToImmutableArray();
var baseType = originalWrapperType ?? DotvvmMarkupControlType;

using var sha = SHA256.Create();
var hashBytes = sha.ComputeHash(
new UTF8Encoding(false).GetBytes(
baseType.FullName + "||" + string.Join("|", imports) + "||" + string.Join("|", properties)
)
);
var hash = Convert.ToBase64String(hashBytes, 0, 16);

var typeName = "DotvvmMarkupControl-" + hash;

return GetOrCreateDynamicType(baseType, typeName);
}

protected abstract ITypeDescriptor? GetOrCreateDynamicType(ITypeDescriptor baseType, string typeName);

/// <summary>
/// Gets the default type of the wrapper for the view.
/// </summary>
Expand Down
@@ -1,15 +1,16 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Immutable;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
using DotVVM.Framework.Compilation.ControlTree;

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public class DefaultDirectiveResolver : DirectiveResolver<IAbstractDirective>
{
private readonly IAbstractTreeBuilder treeBuilder;

public DefaultDirectiveResolver(IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder)
public DefaultDirectiveResolver(DirectiveDictionary directiveNodesByName, IAbstractTreeBuilder treeBuilder)
: base(directiveNodesByName)
{
this.treeBuilder = treeBuilder;
Expand Down
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
using DotVVM.Framework.Compilation.Parser.Binding.Tokenizer;
Expand All @@ -8,13 +7,15 @@

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public abstract class DirectiveCompiler<TDirective, TArtefact> : DirectiveResolver<TDirective>
where TDirective : IAbstractDirective
{
public abstract string DirectiveName { get; }
protected IAbstractTreeBuilder TreeBuilder { get; }

public DirectiveCompiler(IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder)
public DirectiveCompiler(DirectiveDictionary directiveNodesByName, IAbstractTreeBuilder treeBuilder)
: base(directiveNodesByName)
{
TreeBuilder = treeBuilder;
Expand All @@ -29,7 +30,7 @@ public DirectiveCompilationResult Compile()
);
}

protected abstract TArtefact CreateArtefact(IReadOnlyList<TDirective> resolvedDirectives);
protected abstract TArtefact CreateArtefact(ImmutableList<TDirective> resolvedDirectives);

protected BindingParserNode ParseDirective(DothtmlDirectiveNode directiveNode, Func<BindingParser, BindingParserNode> parserFunc)
{
Expand Down
Expand Up @@ -8,6 +8,8 @@

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public abstract class DirectiveResolver<TDirective>
where TDirective : IAbstractDirective
{
Expand All @@ -19,9 +21,9 @@ public abstract class DirectiveResolver<TDirective>
ParserConstants.ViewModelDirectiveName
};

protected IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> DirectiveNodesByName { get; }
protected DirectiveDictionary DirectiveNodesByName { get; }

public DirectiveResolver(IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName)
public DirectiveResolver(DirectiveDictionary directiveNodesByName)
{
DirectiveNodesByName = directiveNodesByName;
}
Expand Down
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Linq;
using DotVVM.Framework.Compilation.Parser;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
Expand All @@ -9,11 +8,13 @@

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public class ImportDirectiveCompiler : DirectiveCompiler<IAbstractImportDirective, ImmutableList<NamespaceImport>>
{
public override string DirectiveName => ParserConstants.ImportNamespaceDirective;

public ImportDirectiveCompiler(IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder)
public ImportDirectiveCompiler(DirectiveDictionary directiveNodesByName, IAbstractTreeBuilder treeBuilder)
: base(directiveNodesByName, treeBuilder)
{
}
Expand All @@ -37,13 +38,11 @@ protected override IAbstractImportDirective Resolve(DothtmlDirectiveNode directi
return TreeBuilder.BuildImportDirective(directiveNode, alias, name);
}

protected override ImmutableList<NamespaceImport> CreateArtefact(IReadOnlyList<IAbstractImportDirective> directives)
=> ResolveNamespaceImportsCore(directives).ToImmutableList();

private IEnumerable<NamespaceImport> ResolveNamespaceImportsCore(IReadOnlyList<IAbstractImportDirective> directives)
protected override ImmutableList<NamespaceImport> CreateArtefact(ImmutableList<IAbstractImportDirective> directives)
=> directives
.Where(d => !d.HasError)
.Select(d => new NamespaceImport(d.NameSyntax.ToDisplayString(), d.AliasSyntax.As<IdentifierNameBindingParserNode>()?.Name));
.Select(d => new NamespaceImport(d.NameSyntax.ToDisplayString(), d.AliasSyntax.As<IdentifierNameBindingParserNode>()?.Name))
.ToImmutableList();
}

}
Expand Up @@ -2,13 +2,13 @@
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Controls.Infrastructure;
using DotVVM.Framework.ResourceManagement;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using DirectiveDictionary = System.Collections.Generic.Dictionary<string, System.Collections.Generic.IReadOnlyList<DotVVM.Framework.Compilation.Parser.Dothtml.Parser.DothtmlDirectiveNode>>;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public class MarkupDirectiveCompilerPipeline : MarkupDirectiveCompilerPipelineBase
{
private readonly IAbstractTreeBuilder treeBuilder;
Expand All @@ -34,7 +34,7 @@ protected override ViewModuleDirectiveCompiler CreateViewModuleDirectiveCompiler
resourceRepository);

protected override BaseTypeDirectiveCompiler CreateBaseTypeCompiler(string fileName, DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports)
=> new ResolvedBaseTypeDirectiveCompiler(directivesByName, treeBuilder, fileName, imports);
=> new BaseTypeDirectiveCompiler (directivesByName, treeBuilder, fileName, imports);
protected override ServiceDirectiveCompiler CreateServiceCompiler(DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports)
=> new(directivesByName, treeBuilder, imports);
protected override MasterPageDirectiveCompiler CreateMasterPageDirectiveCompiler(DirectiveDictionary directivesByName)
Expand Down
Expand Up @@ -4,19 +4,20 @@
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using DirectiveDictionary = System.Collections.Generic.Dictionary<string, System.Collections.Generic.IReadOnlyList<DotVVM.Framework.Compilation.Parser.Dothtml.Parser.DothtmlDirectiveNode>>;

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public abstract class MarkupDirectiveCompilerPipelineBase : IMarkupDirectiveCompilerPipeline
{
public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)
{
var directivesByName = dothtmlRoot.Directives
.GroupBy(d => d.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(d => d.Key, d => (IReadOnlyList<DothtmlDirectiveNode>)d.ToList(), StringComparer.OrdinalIgnoreCase);
.ToImmutableDictionary(d => d.Key, d => d.ToImmutableList(), StringComparer.OrdinalIgnoreCase);

var resolvedDirectives = new Dictionary<string, IReadOnlyList<IAbstractDirective>>();
var resolvedDirectives = new Dictionary<string, ImmutableList<IAbstractDirective>>();

var importCompiler = CreateImportCompiler(directivesByName);
var importResult = importCompiler.Compile();
Expand All @@ -43,14 +44,14 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)
var baseType = baseTypeResult.Artefact;
resolvedDirectives.AddIfAny(baseTypeCompiler.DirectiveName, baseTypeResult.Directives);

var viewModuleDirectiveCompiler = CreateViewModuleDirectiveCompiler(directivesByName, baseType);
var viewModuleResult = viewModuleDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(viewModuleDirectiveCompiler.DirectiveName, viewModuleResult.Directives);

var propertyDirectiveCompiler = CreatePropertyDirectiveCompiler(directivesByName, imports, baseType);
var propertyResult = propertyDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(propertyDirectiveCompiler.DirectiveName, propertyResult.Directives);

var viewModuleDirectiveCompiler = CreateViewModuleDirectiveCompiler(directivesByName, propertyResult.Artefact.ModifiedMarkupControlType);
var viewModuleResult = viewModuleDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(viewModuleDirectiveCompiler.DirectiveName, viewModuleResult.Directives);

var defaultResolver = CreateDefaultResolver(directivesByName);

foreach (var directiveGroup in directivesByName)
Expand All @@ -62,14 +63,14 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)
}

return new MarkupPageMetadata(
resolvedDirectives,
resolvedDirectives.ToImmutableDictionary(),
imports,
masterPageDirectiveResult.Artefact,
injectedServicesResult.Artefact,
baseType,
propertyResult.Artefact.ModifiedMarkupControlType,
viewModelType.TypeDescriptor,
viewModuleResult.Artefact,
propertyResult.Artefact);
propertyResult.Artefact.Properties);
}

protected abstract DefaultDirectiveResolver CreateDefaultResolver(DirectiveDictionary directivesByName);
Expand All @@ -84,11 +85,11 @@ public MarkupPageMetadata Compile(DothtmlRootNode dothtmlRoot, string fileName)

internal static class DirectivesExtensions
{
internal static void AddIfAny(this Dictionary<string, IReadOnlyList<IAbstractDirective>> resolvedDirectives, string directiveName, IReadOnlyList<IAbstractDirective> newDirectives)
internal static void AddIfAny(this Dictionary<string, ImmutableList<IAbstractDirective>> resolvedDirectives, string directiveName, IReadOnlyList<IAbstractDirective> newDirectives)
{
if (newDirectives.Any())
{
resolvedDirectives.Add(directiveName, newDirectives);
resolvedDirectives.Add(directiveName, newDirectives.ToImmutableList());
}
}
}
Expand Down
@@ -1,17 +1,15 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using DotVVM.Framework.Binding;
using System.Collections.Immutable;
using DotVVM.Framework.Compilation.ControlTree;

namespace DotVVM.Framework.Compilation.Directives
{
public record MarkupPageMetadata(
IReadOnlyDictionary<string, IReadOnlyList<IAbstractDirective>> Directives,
ImmutableDictionary<string, ImmutableList<IAbstractDirective>> Directives,
ImmutableList<NamespaceImport> Imports,
IAbstractDirective? MasterPageDirective,
ImmutableList<InjectedServiceExtensionParameter> InjectedServices,
ITypeDescriptor BaseType,
ITypeDescriptor? ViewModelType,
ViewModuleCompilationResult? ViewModuleResult,
ImmutableList<DotvvmProperty> Properties);
ImmutableList<IPropertyDescriptor> Properties);
}
@@ -1,21 +1,23 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.Parser;
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;

namespace DotVVM.Framework.Compilation.Directives
{
using DirectiveDictionary = ImmutableDictionary<string, ImmutableList<DothtmlDirectiveNode>>;

public class MasterPageDirectiveCompiler : DirectiveCompiler<IAbstractDirective, IAbstractDirective?>
{
public MasterPageDirectiveCompiler(IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder)
public MasterPageDirectiveCompiler(DirectiveDictionary directiveNodesByName, IAbstractTreeBuilder treeBuilder)
: base(directiveNodesByName, treeBuilder)
{
}

public override string DirectiveName => ParserConstants.MasterPageDirective;

protected override IAbstractDirective? CreateArtefact(IReadOnlyList<IAbstractDirective> resolvedDirectives)
protected override IAbstractDirective? CreateArtefact(ImmutableList<IAbstractDirective> resolvedDirectives)
{
return resolvedDirectives.FirstOrDefault();
}
Expand Down

0 comments on commit 88ffbf6

Please sign in to comment.