diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs index 3bc17d053c..388e4ff663 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs @@ -6,6 +6,9 @@ using DotVVM.Framework.Compilation.Binding; using System.Collections.Immutable; using DotVVM.Framework.Configuration; +using System.Security.Cryptography; +using System.Diagnostics; +using FastExpressionCompiler; using DotVVM.Framework.Utils; namespace DotVVM.Framework.Compilation.ControlTree.Resolved @@ -27,7 +30,8 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, { if (CompileDirectiveExpression(directive, nameSyntax, imports) is not StaticClassIdentifierExpression expression) { - directive.AddError($"Could not resolve type '{nameSyntax.ToDisplayString()}'."); + var help = GetMissingTypeHelp(nameSyntax, imports); + directive.AddError($"Could not resolve type '{nameSyntax.ToDisplayString()}'.{(help is null ? "" : " " + help)}"); return null; } else return new ResolvedTypeDescriptor(expression.Type); @@ -86,7 +90,38 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, } } - private Expression? CompileDirectiveExpression(DothtmlDirectiveNode directive, BindingParserNode expressionSyntax, ImmutableList imports) + private object? CreateDefaultValue(Type? type) + { + if (type != null && type.IsValueType) + { + return Activator.CreateInstance(type); + } + return null; + } + + private string? GetMissingTypeHelp(BindingParserNode syntax, ImmutableList imports) + { + try + { + // wrong assembly name: + if (syntax is AssemblyQualifiedNameBindingParserNode assemblyQualifiedName) + { + var noAssemblyResolve = CompileDirectiveExpression(null, assemblyQualifiedName.TypeName, imports); + if (noAssemblyResolve is StaticClassIdentifierExpression expression) + { + return $"Did you mean the '{expression.Type.Assembly.GetName().Name}' assembly? Note that assembly name is optional."; + } + } + return null; + } + catch + { + Debug.Fail("This should not happen"); + return null; // just fancy error handling, ignore errors + } + } + + private Expression? CompileDirectiveExpression(DothtmlDirectiveNode? directive, BindingParserNode expressionSyntax, ImmutableList imports) { TypeRegistry registry; if (expressionSyntax is TypeOrFunctionReferenceBindingParserNode typeOrFunction) @@ -114,7 +149,7 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, } catch (Exception ex) { - directive.AddError($"{expressionSyntax.ToDisplayString()} is not a valid type or namespace: {ex.Message}"); + directive?.AddError($"{expressionSyntax.ToDisplayString()} is not a valid type or namespace: {ex.Message}"); return null; } } diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs index d5c21c8322..92787d8e09 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs @@ -112,5 +112,16 @@ public void ResolvedTree_ViewModel_DirectiveIdentifier_CaseInsensitivity(string Assert.IsFalse(root.Directives.Any(d => d.Value.Any(dd => dd.DothtmlNode.HasNodeErrors))); Assert.AreEqual(typeof(string), root.DataContextTypeStack.DataContextType); } + + [TestMethod] + public void ResolvedTree_ViewModel_AssemblyNotFound_Error() + { + var root = ParseSource($"@viewModel DotVVM.Framework.Controls.IGridViewDataSet, DotVVM.Framework"); + + var directive = root.Directives["viewModel"].Single().DothtmlNode; + Assert.IsTrue(directive.HasNodeErrors); + StringAssert.Contains(directive.NodeErrors.Single(), "'DotVVM.Core'"); + } + } }