Skip to content

Commit

Permalink
Merge pull request #1686 from riganti/descriptor-api-change
Browse files Browse the repository at this point in the history
ExtractGenericArgumentDataContextChangeAttribute attrribute VS Extension compatibility
  • Loading branch information
exyi authored Oct 12, 2023
2 parents a5750f6 + f7a83fd commit 3c6ee78
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Utils;
using FastExpressionCompiler;
Expand All @@ -9,17 +10,23 @@ namespace DotVVM.Framework.Binding;

public class ExtractGenericArgumentDataContextChangeAttribute : DataContextChangeAttribute
{
public Type GenericType { get; }
public ITypeDescriptor GenericType { get; }
public int TypeArgumentIndex { get; }
public override int Order { get; }

public ExtractGenericArgumentDataContextChangeAttribute(Type genericType, int typeArgumentIndex, int order = 0)
: this(new ResolvedTypeDescriptor(genericType), typeArgumentIndex, order)
{
}


public ExtractGenericArgumentDataContextChangeAttribute(ITypeDescriptor genericType, int typeArgumentIndex, int order = 0)
{
if (!genericType.IsGenericTypeDefinition)
{
throw new ArgumentException($"The {nameof(genericType)} argument must be a generic type definition!", nameof(genericType));
}
if (typeArgumentIndex < 0 || typeArgumentIndex >= genericType.GetGenericArguments().Length)
if (typeArgumentIndex < 0 || typeArgumentIndex >= genericType.GetGenericArguments()!.Length)
{
throw new ArgumentOutOfRangeException(nameof(typeArgumentIndex), $"The {nameof(typeArgumentIndex)} is not a valid index of a type argument!");
}
Expand All @@ -34,27 +41,27 @@ public ExtractGenericArgumentDataContextChangeAttribute(Type genericType, int ty
var implementations = dataContext.FindGenericImplementations(GenericType).ToList();
if (implementations.Count == 0)
{
throw new Exception($"The data context {dataContext.CSharpFullName} doesn't implement {GenericType}!");
throw new Exception($"The data context {dataContext.CSharpFullName} doesn't implement {GenericType.CSharpFullName}!");
}
else if (implementations.Count > 1)
{
throw new Exception($"The data context {dataContext.CSharpFullName} has multiple implementations of {GenericType}! Cannot decide which one to extract:\n" + string.Join("\n", implementations.Select(t => t.CSharpFullName)));
throw new Exception($"The data context {dataContext.CSharpFullName} has multiple implementations of {GenericType.CSharpFullName}! Cannot decide which one to extract:\n" + string.Join("\n", implementations.Select(t => t.CSharpFullName)));
}
return implementations[0].GetGenericArguments()![TypeArgumentIndex];
}

public override Type? GetChildDataContextType(Type dataContext, DataContextStack controlContextStack, DotvvmBindableObject control, DotvvmProperty? property = null)
{
var implementations = ReflectionUtils.GetBaseTypesAndInterfaces(dataContext)
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == GenericType)
.Where(i => i.IsGenericType && new ResolvedTypeDescriptor(i.GetGenericTypeDefinition()).IsEqualTo(GenericType))
.ToList();
if (implementations.Count == 0)
{
throw new Exception($"The data context {dataContext} doesn't implement {GenericType}!");
throw new Exception($"The data context {dataContext.ToCode()} doesn't implement {GenericType.CSharpFullName}!");
}
else if (implementations.Count > 1)
{
throw new Exception($"The data context {dataContext.ToCode()} has multiple implementations of {GenericType.ToCode()}! Cannot decide which one to extract:\n" + string.Join("\n", implementations.Select(t => t.ToCode())));
throw new Exception($"The data context {dataContext.ToCode()} has multiple implementations of {GenericType.CSharpFullName}! Cannot decide which one to extract:\n" + string.Join("\n", implementations.Select(t => t.ToCode())));
}
return implementations[0].GetGenericArguments()[TypeArgumentIndex];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DotVVM.Framework.Compilation.Parser.Binding.Parser;
using System.Collections.Generic;
using System.Reflection;

namespace DotVVM.Framework.Compilation.ControlTree
Expand All @@ -13,9 +14,11 @@ public interface IAbstractPropertyDeclarationDirective : IAbstractDirective, ICu
{
SimpleNameBindingParserNode NameSyntax { get; }
TypeReferenceBindingParserNode PropertyTypeSyntax { get; }
BindingParserNode? InitializerSyntax { get; }
ITypeDescriptor? PropertyType { get; }
ITypeDescriptor? DeclaringType { get; set; }
object? InitialValue { get; }
IList<IAbstractDirectiveAttributeReference> Attributes { get; }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface ITypeDescriptor
string CSharpName { get; }
/// <summary> Returns type name including namespace with generic arguments in the C# style. </summary>
string CSharpFullName { get; }
bool IsGenericTypeDefinition { get; }

bool IsAssignableTo(ITypeDescriptor typeDescriptor);

Expand All @@ -34,7 +35,7 @@ public interface ITypeDescriptor

ITypeDescriptor MakeGenericType(params ITypeDescriptor[] typeArguments);

IEnumerable<ITypeDescriptor> FindGenericImplementations(Type genericType);
IEnumerable<ITypeDescriptor> FindGenericImplementations(ITypeDescriptor genericType);

ITypeDescriptor[]? GetGenericArguments();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public ResolvedTypeDescriptor(Type type)
public string CSharpName => Type.ToCode(stripNamespace: true);
public string CSharpFullName => Type.ToCode();

public bool IsGenericTypeDefinition => Type.IsGenericTypeDefinition;

public bool IsAssignableTo(ITypeDescriptor typeDescriptor)
{
return ToSystemType(typeDescriptor).IsAssignableFrom(Type);
Expand Down Expand Up @@ -138,10 +140,11 @@ public ITypeDescriptor MakeGenericType(params ITypeDescriptor[] typeArguments)
return new ResolvedTypeDescriptor(genericType);
}

public IEnumerable<ITypeDescriptor> FindGenericImplementations(Type genericType)
public IEnumerable<ITypeDescriptor> FindGenericImplementations(ITypeDescriptor genericType)
{
return ReflectionUtils.GetBaseTypesAndInterfaces(Type)
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericType)
var generic = ToSystemType(genericType);
return ReflectionUtils.GetBaseTypesAndInterfaces(Type)
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == generic)
.Select(t => new ResolvedTypeDescriptor(t));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@

namespace DotVVM.Framework.Compilation.Directives
{
public class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBaseTypeDirective, ITypeDescriptor>
public abstract class BaseTypeDirectiveCompiler : DirectiveCompiler<IAbstractBaseTypeDirective, ITypeDescriptor>
{
private static readonly Lazy<ModuleBuilder> DynamicMarkupControlAssembly = new (CreateDynamicMarkupControlAssembly);

private readonly string fileName;
private readonly ImmutableList<NamespaceImport> imports;

public override string DirectiveName => ParserConstants.BaseTypeDirective;

protected virtual ITypeDescriptor DotvvmViewType => new ResolvedTypeDescriptor(typeof(DotvvmView));
protected virtual ITypeDescriptor DotvvmMarkupControlType => new ResolvedTypeDescriptor(typeof(DotvvmMarkupControl));

public BaseTypeDirectiveCompiler(
IReadOnlyDictionary<string, IReadOnlyList<DothtmlDirectiveNode>> directiveNodesByName, IAbstractTreeBuilder treeBuilder, string fileName, ImmutableList<NamespaceImport> imports)
: base(directiveNodesByName, treeBuilder)
Expand All @@ -36,19 +37,21 @@ public BaseTypeDirectiveCompiler(
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(IReadOnlyList<IAbstractBaseTypeDirective> resolvedDirectives)
{
var wrapperType = GetDefaultWrapperType();

var baseControlDirective = resolvedDirectives.SingleOrDefault();

if (baseControlDirective != null)
{
var baseType = baseControlDirective.ResolvedType;

if (baseType == null)
{
baseControlDirective.DothtmlNode!.AddError($"The type '{baseControlDirective.Value}' specified in baseType directive was not found!");
}
else if (!baseType.IsAssignableTo(new ResolvedTypeDescriptor(typeof(DotvvmMarkupControl))))
else if (!baseType.IsAssignableTo(DotvvmMarkupControlType))
{
baseControlDirective.DothtmlNode!.AddError("Markup controls must derive from DotvvmMarkupControl class!");
wrapperType = baseType;
Expand Down Expand Up @@ -83,7 +86,7 @@ IEnumerable<DothtmlDirectiveNode> propertyDirectives
.Select(d => d.Value.Trim()).OrderBy(s => s).ToImmutableArray();
var properties = propertyDirectives
.Select(p => p.Value.Trim()).OrderBy(s => s).ToImmutableArray();
var baseType = originalWrapperType?.CastTo<ResolvedTypeDescriptor>().Type ?? typeof(DotvvmMarkupControl);
var baseType = originalWrapperType ?? DotvvmMarkupControlType;

using var sha = SHA256.Create();
var hashBytes = sha.ComputeHash(
Expand All @@ -94,19 +97,11 @@ IEnumerable<DothtmlDirectiveNode> propertyDirectives
var hash = Convert.ToBase64String(hashBytes, 0, 16);

var typeName = "DotvvmMarkupControl-" + hash;
if (DynamicMarkupControlAssembly.Value.GetType(typeName) is { } type)
{
return new ResolvedTypeDescriptor(type);
}

var declaringTypeBuilder =
DynamicMarkupControlAssembly.Value.DefineType(typeName, TypeAttributes.Public, baseType);
var createdTypeInfo = declaringTypeBuilder.CreateTypeInfo()?.AsType();

return createdTypeInfo is not null
? new ResolvedTypeDescriptor(createdTypeInfo)
: null;
return GetOrCreateDynamicType(baseType, typeName);
}

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

/// <summary>
/// Gets the default type of the wrapper for the view.
Expand All @@ -115,26 +110,10 @@ private ITypeDescriptor GetDefaultWrapperType()
{
if (fileName.EndsWith(".dotcontrol", StringComparison.Ordinal))
{
return new ResolvedTypeDescriptor(typeof(DotvvmMarkupControl));
return DotvvmMarkupControlType;
}

return new ResolvedTypeDescriptor(typeof(DotvvmView));
}

private static ModuleBuilder CreateDynamicMarkupControlAssembly()
{
var newDynamicAssemblyName = $"DotvvmMarkupControlDynamicAssembly-{Guid.NewGuid()}";
var assemblyName = new AssemblyName(newDynamicAssemblyName);
var assemblyBuilder =
AssemblyBuilder.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);

// For a single-module assembly, the module name is usually
// the assembly name plus an extension.
var mb =
assemblyBuilder.DefineDynamicModule(newDynamicAssemblyName);
return mb;
return DotvvmViewType;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,47 @@
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using DotVVM.Framework.Controls.Infrastructure;
using DotVVM.Framework.ResourceManagement;
using System;
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
{
public class MarkupDirectiveCompilerPipeline : IMarkupDirectiveCompilerPipeline
public class MarkupDirectiveCompilerPipeline : MarkupDirectiveCompilerPipelineBase
{
private readonly IAbstractTreeBuilder treeBuilder;
private readonly DotvvmResourceRepository resourceRepository;

public MarkupDirectiveCompilerPipeline(IAbstractTreeBuilder treeBuilder, DotvvmResourceRepository resourceRepository)
public MarkupDirectiveCompilerPipeline(IAbstractTreeBuilder treeBuilder, DotvvmResourceRepository resourceRepository) : base()
{
this.treeBuilder = treeBuilder;
this.resourceRepository = resourceRepository;
}

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);

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

var importCompiler = new ImportDirectiveCompiler(directivesByName, treeBuilder);
var importResult = importCompiler.Compile();
var imports = importResult.Artefact;
resolvedDirectives.AddIfAny(importCompiler.DirectiveName, importResult.Directives);

var viewModelDirectiveCompiler = new ViewModelDirectiveCompiler(directivesByName, treeBuilder, fileName, imports);
var viewModelTypeResult = viewModelDirectiveCompiler.Compile();
var viewModelType = viewModelTypeResult.Artefact;
if (!string.IsNullOrEmpty(viewModelType.Error)) { dothtmlRoot.AddError(viewModelType.Error!); }
resolvedDirectives.AddIfAny(viewModelDirectiveCompiler.DirectiveName, viewModelTypeResult.Directives);

var masterPageDirectiveCompiler = new MasterPageDirectiveCompiler(directivesByName, treeBuilder);
var masterPageDirectiveResult = masterPageDirectiveCompiler.Compile();
var masterPage = masterPageDirectiveResult.Artefact;
resolvedDirectives.AddIfAny(masterPageDirectiveCompiler.DirectiveName, masterPageDirectiveResult.Directives);

var serviceCompiler = new ServiceDirectiveCompiler(directivesByName, treeBuilder, imports);
var injectedServicesResult = serviceCompiler.Compile();
resolvedDirectives.AddIfAny(serviceCompiler.DirectiveName, injectedServicesResult.Directives);

var baseTypeCompiler = new BaseTypeDirectiveCompiler(directivesByName, treeBuilder, fileName, imports);
var baseTypeResult = baseTypeCompiler.Compile();
var baseType = baseTypeResult.Artefact;
resolvedDirectives.AddIfAny(baseTypeCompiler.DirectiveName, baseTypeResult.Directives);

var viewModuleDirectiveCompiler = new ViewModuleDirectiveCompiler(
directivesByName,
treeBuilder,
!baseType.IsEqualTo(ResolvedTypeDescriptor.Create(typeof(DotvvmView))),
resourceRepository);
var viewModuleResult = viewModuleDirectiveCompiler.Compile();
resolvedDirectives.AddIfAny(viewModuleDirectiveCompiler.DirectiveName, viewModuleResult.Directives);

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

var defaultResolver = new DefaultDirectiveResolver(directivesByName, treeBuilder);

foreach (var directiveGroup in directivesByName)
{
if (!resolvedDirectives.ContainsKey(directiveGroup.Key))
{
resolvedDirectives.Add(directiveGroup.Key, defaultResolver.ResolveAll(directiveGroup.Key));
}
}

return new MarkupPageMetadata(
resolvedDirectives,
imports,
masterPageDirectiveResult.Artefact,
injectedServicesResult.Artefact,
baseType,
viewModelType.TypeDescriptor,
viewModuleResult.Artefact,
propertyResult.Artefact);
}
protected override DefaultDirectiveResolver CreateDefaultResolver(DirectiveDictionary directivesByName)
=> new(directivesByName, treeBuilder);

protected override PropertyDeclarationDirectiveCompiler CreatePropertyDirectiveCompiler(DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports, ITypeDescriptor baseType)
=> new ResolvedPropertyDeclarationDirectiveCompiler (directivesByName, treeBuilder, baseType, imports);

protected override ViewModuleDirectiveCompiler CreateViewModuleDirectiveCompiler(DirectiveDictionary directivesByName, ITypeDescriptor baseType)
=> new(
directivesByName,
treeBuilder,
!baseType.IsEqualTo(ResolvedTypeDescriptor.Create(typeof(DotvvmView))),
resourceRepository);

protected override BaseTypeDirectiveCompiler CreateBaseTypeCompiler(string fileName, DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports)
=> new ResolvedBaseTypeDirectiveCompiler(directivesByName, treeBuilder, fileName, imports);
protected override ServiceDirectiveCompiler CreateServiceCompiler(DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports)
=> new(directivesByName, treeBuilder, imports);
protected override MasterPageDirectiveCompiler CreateMasterPageDirectiveCompiler(DirectiveDictionary directivesByName)
=> new(directivesByName, treeBuilder);
protected override ViewModelDirectiveCompiler CreateViewModelDirectiveCompiler( string fileName, DirectiveDictionary directivesByName, ImmutableList<NamespaceImport> imports)
=> new(directivesByName, treeBuilder, fileName, imports);
protected override ImportDirectiveCompiler CreateImportCompiler(DirectiveDictionary directivesByName)
=> new(directivesByName, treeBuilder);
}

internal static class DirectivesExtensions
{
internal static void AddIfAny(this Dictionary<string, IReadOnlyList<IAbstractDirective>> resolvedDirectives, string directiveName, IReadOnlyList<IAbstractDirective> newDirectives)
{
if (newDirectives.Any())
{
resolvedDirectives.Add(directiveName, newDirectives);
}
}
}

}
Loading

0 comments on commit 3c6ee78

Please sign in to comment.