Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExtractGenericArgumentDataContextChangeAttribute attrribute VS Extension compatibility #1686

Merged
merged 6 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading