diff --git a/src/Framework/Framework/Binding/ExtractGenericArgumentDataContextChangeAttribute.cs b/src/Framework/Framework/Binding/ExtractGenericArgumentDataContextChangeAttribute.cs index 69e49bbb35..246e9d6b37 100644 --- a/src/Framework/Framework/Binding/ExtractGenericArgumentDataContextChangeAttribute.cs +++ b/src/Framework/Framework/Binding/ExtractGenericArgumentDataContextChangeAttribute.cs @@ -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; @@ -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!"); } @@ -34,11 +41,11 @@ 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]; } @@ -46,15 +53,15 @@ public ExtractGenericArgumentDataContextChangeAttribute(Type genericType, int ty 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]; } diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractPropertyDeclarationDirective.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractPropertyDeclarationDirective.cs index 967c6bdf0d..0cc86690db 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractPropertyDeclarationDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractPropertyDeclarationDirective.cs @@ -1,4 +1,5 @@ using DotVVM.Framework.Compilation.Parser.Binding.Parser; +using System.Collections.Generic; using System.Reflection; namespace DotVVM.Framework.Compilation.ControlTree @@ -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 Attributes { get; } } } diff --git a/src/Framework/Framework/Compilation/ControlTree/ITypeDescriptor.cs b/src/Framework/Framework/Compilation/ControlTree/ITypeDescriptor.cs index 65e6633835..1d3e9fa555 100644 --- a/src/Framework/Framework/Compilation/ControlTree/ITypeDescriptor.cs +++ b/src/Framework/Framework/Compilation/ControlTree/ITypeDescriptor.cs @@ -18,6 +18,7 @@ public interface ITypeDescriptor string CSharpName { get; } /// Returns type name including namespace with generic arguments in the C# style. string CSharpFullName { get; } + bool IsGenericTypeDefinition { get; } bool IsAssignableTo(ITypeDescriptor typeDescriptor); @@ -34,7 +35,7 @@ public interface ITypeDescriptor ITypeDescriptor MakeGenericType(params ITypeDescriptor[] typeArguments); - IEnumerable FindGenericImplementations(Type genericType); + IEnumerable FindGenericImplementations(ITypeDescriptor genericType); ITypeDescriptor[]? GetGenericArguments(); } diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTypeDescriptor.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTypeDescriptor.cs index 93e1427388..05b84f1b0c 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTypeDescriptor.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTypeDescriptor.cs @@ -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); @@ -138,10 +140,11 @@ public ITypeDescriptor MakeGenericType(params ITypeDescriptor[] typeArguments) return new ResolvedTypeDescriptor(genericType); } - public IEnumerable FindGenericImplementations(Type genericType) + public IEnumerable 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)); } diff --git a/src/Framework/Framework/Compilation/Directives/BaseTypeDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/BaseTypeDirectiveCompiler.cs index 194d4867e2..39a72c8aa4 100644 --- a/src/Framework/Framework/Compilation/Directives/BaseTypeDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/BaseTypeDirectiveCompiler.cs @@ -16,15 +16,16 @@ namespace DotVVM.Framework.Compilation.Directives { - public class BaseTypeDirectiveCompiler : DirectiveCompiler + public abstract class BaseTypeDirectiveCompiler : DirectiveCompiler { - private static readonly Lazy DynamicMarkupControlAssembly = new (CreateDynamicMarkupControlAssembly); - private readonly string fileName; private readonly ImmutableList 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> directiveNodesByName, IAbstractTreeBuilder treeBuilder, string fileName, ImmutableList imports) : base(directiveNodesByName, treeBuilder) @@ -36,7 +37,8 @@ public BaseTypeDirectiveCompiler( protected override IAbstractBaseTypeDirective Resolve(DothtmlDirectiveNode directiveNode) => TreeBuilder.BuildBaseTypeDirective(directiveNode, ParseDirective(directiveNode, p => p.ReadDirectiveTypeName()), imports); - protected override ITypeDescriptor CreateArtefact(IReadOnlyList resolvedDirectives) { + protected override ITypeDescriptor CreateArtefact(IReadOnlyList resolvedDirectives) + { var wrapperType = GetDefaultWrapperType(); var baseControlDirective = resolvedDirectives.SingleOrDefault(); @@ -44,11 +46,12 @@ protected override ITypeDescriptor CreateArtefact(IReadOnlyList 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().Type ?? typeof(DotvvmMarkupControl); + var baseType = originalWrapperType ?? DotvvmMarkupControlType; using var sha = SHA256.Create(); var hashBytes = sha.ComputeHash( @@ -94,19 +97,11 @@ IEnumerable 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); /// /// Gets the default type of the wrapper for the view. @@ -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; } } diff --git a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs index 742d08da85..3c95e80470 100644 --- a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs +++ b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipeline.cs @@ -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>; 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)d.ToList(), StringComparer.OrdinalIgnoreCase); - - var resolvedDirectives = new Dictionary>(); - - 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 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 imports) + => new ResolvedBaseTypeDirectiveCompiler(directivesByName, treeBuilder, fileName, imports); + protected override ServiceDirectiveCompiler CreateServiceCompiler(DirectiveDictionary directivesByName, ImmutableList imports) + => new(directivesByName, treeBuilder, imports); + protected override MasterPageDirectiveCompiler CreateMasterPageDirectiveCompiler(DirectiveDictionary directivesByName) + => new(directivesByName, treeBuilder); + protected override ViewModelDirectiveCompiler CreateViewModelDirectiveCompiler( string fileName, DirectiveDictionary directivesByName, ImmutableList 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> resolvedDirectives, string directiveName, IReadOnlyList newDirectives) - { - if (newDirectives.Any()) - { - resolvedDirectives.Add(directiveName, newDirectives); - } - } - } - } diff --git a/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipelineBase.cs b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipelineBase.cs new file mode 100644 index 0000000000..19c4c5d65f --- /dev/null +++ b/src/Framework/Framework/Compilation/Directives/MarkupDirectiveCompilerPipelineBase.cs @@ -0,0 +1,95 @@ +using DotVVM.Framework.Compilation.Parser.Dothtml.Parser; +using DotVVM.Framework.Compilation.ControlTree; +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; +using DirectiveDictionary = System.Collections.Generic.Dictionary>; + +namespace DotVVM.Framework.Compilation.Directives +{ + 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)d.ToList(), StringComparer.OrdinalIgnoreCase); + + var resolvedDirectives = new Dictionary>(); + + var importCompiler = CreateImportCompiler(directivesByName); + var importResult = importCompiler.Compile(); + var imports = importResult.Artefact; + resolvedDirectives.AddIfAny(importCompiler.DirectiveName, importResult.Directives); + + var viewModelDirectiveCompiler = CreateViewModelDirectiveCompiler(fileName, directivesByName, 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 = CreateMasterPageDirectiveCompiler(directivesByName); + var masterPageDirectiveResult = masterPageDirectiveCompiler.Compile(); + var masterPage = masterPageDirectiveResult.Artefact; + resolvedDirectives.AddIfAny(masterPageDirectiveCompiler.DirectiveName, masterPageDirectiveResult.Directives); + + var serviceCompiler = CreateServiceCompiler(directivesByName, imports); + var injectedServicesResult = serviceCompiler.Compile(); + resolvedDirectives.AddIfAny(serviceCompiler.DirectiveName, injectedServicesResult.Directives); + + var baseTypeCompiler = CreateBaseTypeCompiler(fileName, directivesByName, imports); + var baseTypeResult = baseTypeCompiler.Compile(); + 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 defaultResolver = CreateDefaultResolver(directivesByName); + + 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 abstract DefaultDirectiveResolver CreateDefaultResolver(DirectiveDictionary directivesByName); + protected abstract PropertyDeclarationDirectiveCompiler CreatePropertyDirectiveCompiler(DirectiveDictionary directivesByName, ImmutableList imports, ITypeDescriptor baseType); + protected abstract ViewModuleDirectiveCompiler CreateViewModuleDirectiveCompiler(DirectiveDictionary directivesByName, ITypeDescriptor baseType); + protected abstract MasterPageDirectiveCompiler CreateMasterPageDirectiveCompiler(DirectiveDictionary directivesByName); + protected abstract ServiceDirectiveCompiler CreateServiceCompiler(DirectiveDictionary directivesByName, ImmutableList imports); + protected abstract BaseTypeDirectiveCompiler CreateBaseTypeCompiler(string fileName, DirectiveDictionary directivesByName, ImmutableList imports); + protected abstract ImportDirectiveCompiler CreateImportCompiler(DirectiveDictionary directivesByName); + protected abstract ViewModelDirectiveCompiler CreateViewModelDirectiveCompiler(string fileName, DirectiveDictionary directivesByName, ImmutableList imports); + } + + internal static class DirectivesExtensions + { + internal static void AddIfAny(this Dictionary> resolvedDirectives, string directiveName, IReadOnlyList newDirectives) + { + if (newDirectives.Any()) + { + resolvedDirectives.Add(directiveName, newDirectives); + } + } + } +} diff --git a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs index 4f7e3777e7..86bcbbd304 100644 --- a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs @@ -13,7 +13,7 @@ namespace DotVVM.Framework.Compilation.Directives { - public class PropertyDeclarationDirectiveCompiler : DirectiveCompiler> + public abstract class PropertyDeclarationDirectiveCompiler : DirectiveCompiler> { private readonly ITypeDescriptor controlWrapperType; private readonly ImmutableList imports; @@ -98,15 +98,32 @@ protected override ImmutableList CreateArtefact(IReadOnlyList d.PropertyType is ResolvedTypeDescriptor { Type: not null }) - .Select(d => TryCreateDotvvmPropertyFromDirective(d)) + .Where(HasPropertyType) + .Select(TryCreateDotvvmPropertyFromDirective) .ToImmutableList(); } - protected virtual DotvvmProperty TryCreateDotvvmPropertyFromDirective(IAbstractPropertyDeclarationDirective propertyDeclarationDirective) + protected abstract bool HasPropertyType(IAbstractPropertyDeclarationDirective directive); + protected abstract DotvvmProperty TryCreateDotvvmPropertyFromDirective(IAbstractPropertyDeclarationDirective propertyDeclarationDirective); + } + + public class ResolvedPropertyDeclarationDirectiveCompiler : PropertyDeclarationDirectiveCompiler + { + public ResolvedPropertyDeclarationDirectiveCompiler( + IReadOnlyDictionary> directiveNodesByName, + IAbstractTreeBuilder treeBuilder, ITypeDescriptor controlWrapperType, + ImmutableList imports) + : base(directiveNodesByName, treeBuilder, controlWrapperType, imports) + { + } + + protected override bool HasPropertyType(IAbstractPropertyDeclarationDirective directive) + => directive.PropertyType is ResolvedTypeDescriptor { Type: not null }; + + protected override DotvvmProperty TryCreateDotvvmPropertyFromDirective(IAbstractPropertyDeclarationDirective propertyDeclarationDirective) { - if(propertyDeclarationDirective.PropertyType is not ResolvedTypeDescriptor { Type: not null } propertyType) { throw new ArgumentException("propertyDeclarationDirective.PropertyType must be of type ResolvedTypeDescriptor and have non null type."); } - if(propertyDeclarationDirective.DeclaringType is not ResolvedTypeDescriptor { Type: not null } declaringType) { throw new ArgumentException("propertyDeclarationDirective.DeclaringType must be of type ResolvedTypeDescriptor and have non null type."); } + if (propertyDeclarationDirective.PropertyType is not ResolvedTypeDescriptor { Type: not null } propertyType) { throw new ArgumentException("propertyDeclarationDirective.PropertyType must be of type ResolvedTypeDescriptor and have non null type."); } + if (propertyDeclarationDirective.DeclaringType is not ResolvedTypeDescriptor { Type: not null } declaringType) { throw new ArgumentException("propertyDeclarationDirective.DeclaringType must be of type ResolvedTypeDescriptor and have non null type."); } return DotvvmProperty.Register( propertyDeclarationDirective.NameSyntax.Name, diff --git a/src/Framework/Framework/Compilation/Directives/ResolvedBaseTypeDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/ResolvedBaseTypeDirectiveCompiler.cs new file mode 100644 index 0000000000..7249ea8f6f --- /dev/null +++ b/src/Framework/Framework/Compilation/Directives/ResolvedBaseTypeDirectiveCompiler.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using DotVVM.Framework.Compilation.Parser.Dothtml.Parser; +using DotVVM.Framework.Compilation.ControlTree; +using DotVVM.Framework.Compilation.ControlTree.Resolved; +using System.Reflection; +using System.Reflection.Emit; +using System.Collections.Immutable; + +namespace DotVVM.Framework.Compilation.Directives +{ + public class ResolvedBaseTypeDirectiveCompiler : BaseTypeDirectiveCompiler + { + private static readonly Lazy DynamicMarkupControlAssembly = new(CreateDynamicMarkupControlAssembly); + + public ResolvedBaseTypeDirectiveCompiler(IReadOnlyDictionary> directiveNodesByName, IAbstractTreeBuilder treeBuilder, string fileName, ImmutableList imports) + : base(directiveNodesByName, treeBuilder, fileName, imports) + { + } + + protected override ITypeDescriptor? GetOrCreateDynamicType(ITypeDescriptor baseType, string typeName) + { + if (DynamicMarkupControlAssembly.Value.GetType(typeName) is { } type) + { + return new ResolvedTypeDescriptor(type); + } + + var declaringTypeBuilder = + DynamicMarkupControlAssembly.Value.DefineType(typeName, TypeAttributes.Public, ResolvedTypeDescriptor.ToSystemType(baseType)); + var createdTypeInfo = declaringTypeBuilder.CreateTypeInfo()?.AsType(); + + return createdTypeInfo is not null + ? new ResolvedTypeDescriptor(createdTypeInfo) + : null; + } + + 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; + } + } + +} diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs index 73db32e76a..49173b122c 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs @@ -120,7 +120,11 @@ public BindingParserNode ReadPropertyDirectiveValue() public BindingParserNode ReadDirectiveTypeName() { var startIndex = CurrentIndex; - var typeName = ReadNamespaceOrTypeName(); + + var typeName = TryReadTypeReference(out var resultType) + ? resultType + : new ActualTypeReferenceBindingParserNode(new SimpleNameBindingParserNode("")); + if (PeekType() == BindingTokenType.Comma) { Read(); diff --git a/src/Tests/Parser/Binding/BindingParserTests.cs b/src/Tests/Parser/Binding/BindingParserTests.cs index 4db7414a04..04e81e9efa 100644 --- a/src/Tests/Parser/Binding/BindingParserTests.cs +++ b/src/Tests/Parser/Binding/BindingParserTests.cs @@ -829,6 +829,35 @@ public void BindingParser_GenericExpression_MemberAccessInsteadOfType_Invalid() .As().Name == ""); } + [TestMethod] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[], Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[], Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[], Domain.Company.Product")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[], Product")] + public void BindingParser_ArrayType_AssemblyQualifiedName_ValidAssemblyName(string binding) + { + var parser = bindingParserNodeFactory.SetupParser(binding); + var node = parser.ReadDirectiveTypeName() as AssemblyQualifiedNameBindingParserNode; + Assert.IsNotNull(node, "expected qualified name node."); + + var array = node.TypeName as ArrayTypeReferenceBindingParserNode; + + Assert.IsNotNull(array, "Expected array type reference"); + Assert.IsFalse(node.AssemblyName.HasNodeErrors); + } + + [TestMethod] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[]")] + [DataRow("Domain.Company.Product.DotVVM.Feature.Type[]")] + public void BindingParser_ArrayType_ValidAssemblyName(string binding) + { + var parser = bindingParserNodeFactory.SetupParser(binding); + var array = parser.ReadDirectiveTypeName() as ArrayTypeReferenceBindingParserNode; + + Assert.IsNotNull(array, "Expected array type reference"); + Assert.IsFalse(array.HasNodeErrors); + } + [TestMethod] [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Domain.Company.Product")] [DataRow("Domain.Company.Product.DotVVM.Feature.Type, Product")]