Skip to content

Commit 86a56fc

Browse files
authored
Merge pull request #1877 from riganti/fix/binding-ambiguous-namespaces
Fixed ambiguous namespace resolution and matching non-public types
2 parents 22f9e4b + 4fc4930 commit 86a56fc

File tree

7 files changed

+77
-28
lines changed

7 files changed

+77
-28
lines changed

src/Adapters/Tests/WebForms/HybridRouteLinkTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public async Task HybridRouteLink_SuffixAndQueryString()
7878
}
7979
}
8080

81-
class ControlTestViewModel
81+
public class ControlTestViewModel
8282
{
8383
public int Value { get; set; } = 15;
8484

@@ -90,7 +90,7 @@ class ControlTestViewModel
9090
};
9191
}
9292

93-
class ControlTestChildViewModel
93+
public class ControlTestChildViewModel
9494
{
9595
public int Id { get; set; }
9696
public string Name { get; set; }

src/Framework/Framework/Compilation/Binding/TypeRegistry.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Linq.Expressions;
77
using DotVVM.Framework.Utils;
8+
using FastExpressionCompiler;
89

910
namespace DotVVM.Framework.Compilation.Binding
1011
{
@@ -34,8 +35,13 @@ public TypeRegistry(CompiledAssemblyCache compiledAssemblyCache, ImmutableDictio
3435
{
3536
return expr;
3637
}
37-
expr = resolvers.Select(r => r(name)).FirstOrDefault(e => e != null);
38-
if (expr != null) return expr;
38+
var matchedExpressions = resolvers.Select(r => r(name)).WhereNotNull().Distinct(ExpressionTypeComparer.Instance).ToList();
39+
if (matchedExpressions.Count > 1)
40+
{
41+
throw new InvalidOperationException($"The identifier '{name}' is ambiguous between the following types: {string.Join(", ", matchedExpressions.Select(e => e!.Type.ToCode()))}. Please specify the namespace explicitly.");
42+
}
43+
44+
if (matchedExpressions.Count == 1) return matchedExpressions[0];
3945
if (throwOnNotFound) throw new InvalidOperationException($"The identifier '{ name }' could not be resolved!");
4046
return null;
4147
}
@@ -56,7 +62,7 @@ public TypeRegistry AddSymbols(IEnumerable<Func<string, Expression?>> symbols)
5662
[return: NotNullIfNotNull("type")]
5763
public static Expression? CreateStatic(Type? type)
5864
{
59-
return type == null ? null : new StaticClassIdentifierExpression(type);
65+
return type?.IsPublicType() == true ? new StaticClassIdentifierExpression(type) : null;
6066
}
6167

6268
private static readonly ImmutableDictionary<string, Expression> predefinedTypes =
@@ -122,5 +128,20 @@ public TypeRegistry AddImportedTypes(CompiledAssemblyCache compiledAssemblyCache
122128
};
123129
else return t => TypeRegistry.CreateStatic(compiledAssemblyCache.FindType(import.Namespace + "." + t));
124130
}
131+
132+
class ExpressionTypeComparer : IEqualityComparer<Expression>
133+
{
134+
public static readonly ExpressionTypeComparer Instance = new();
135+
136+
public bool Equals(Expression? x, Expression? y)
137+
{
138+
if (ReferenceEquals(x, y)) return true;
139+
if (x is null) return false;
140+
if (y is null) return false;
141+
return ReferenceEquals(x.Type, y.Type);
142+
}
143+
144+
public int GetHashCode(Expression obj) => obj.Type.GetHashCode();
145+
}
125146
}
126147
}

src/Framework/Framework/Utils/ReflectionUtils.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,5 +778,18 @@ internal static Type AssignGenericParameters(Type t, IReadOnlyDictionary<Type, T
778778
return t;
779779
}
780780
}
781+
782+
/// <summary>
783+
/// Determines whether the type is public and has the entire chain of nested parents public.
784+
/// </summary>
785+
public static bool IsPublicType(this Type type)
786+
{
787+
if (type.IsPublic) return true;
788+
if (type.IsNested)
789+
{
790+
return type.IsNestedPublic && IsPublicType(type.DeclaringType!);
791+
}
792+
return false;
793+
}
781794
}
782795
}

src/Samples/Common/ViewModels/FeatureSamples/Redirect/RedirectPostbackConcurrencyViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.Redirect
99
{
10-
class RedirectPostbackConcurrencyViewModel : DotvvmViewModelBase
10+
public class RedirectPostbackConcurrencyViewModel : DotvvmViewModelBase
1111
{
1212
public static int GlobalCounter = 0;
1313
private readonly IReturnedFileStorage returnedFileStorage;

src/Samples/Common/Views/ComplexSamples/EventPropagation/EventPropagation.dothtml

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Tests/Binding/BindingCompilationTests.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,8 +1339,23 @@ public void BindingCompiler_InvalidStructComparison() =>
13391339
[ExpectedExceptionMessageSubstring(typeof(BindingPropertyException), "Cannot apply And operator to types TestEnum and Boolean")]
13401340
public void BindingCompiler_InvalidBitAndComparison() =>
13411341
ExecuteBinding("EnumProperty & 2 == 0", new TestViewModel());
1342+
1343+
[TestMethod]
1344+
public void BindingCompiler_DoNotMatchInternalClasses()
1345+
{
1346+
var result = ExecuteBinding("Strings.SomeResource", new[] { new NamespaceImport("System.Linq"), new NamespaceImport("DotVVM.Framework.Tests") }, new TestViewModel());
1347+
Assert.AreEqual("hello", result);
1348+
}
1349+
1350+
[TestMethod]
1351+
[ExpectedExceptionMessageSubstring(typeof(BindingPropertyException), "ambiguous")]
1352+
public void BindingCompiler_AmbiguousMatches()
1353+
{
1354+
var result = ExecuteBinding("Strings.SomeResource", new[] { new NamespaceImport("DotVVM.Framework.Tests.Ambiguous"), new NamespaceImport("DotVVM.Framework.Tests") }, new TestViewModel());
1355+
Assert.AreEqual("hello", result);
1356+
}
13421357
}
1343-
class TestViewModel
1358+
public class TestViewModel
13441359
{
13451360
public bool BoolProp { get; set; }
13461361
public string StringProp { get; set; }
@@ -1452,7 +1467,7 @@ public T GenericDefault<T>(T something, T somethingElse = default)
14521467
}
14531468

14541469

1455-
record struct VehicleNumber(
1470+
public record struct VehicleNumber(
14561471
[property: Range(100, 999)]
14571472
int Value
14581473
): IDotvvmPrimitiveType
@@ -1499,7 +1514,7 @@ public string CustomGenericDelegateInvoker<T>(T item, CustomGenericDelegate<T> f
14991514
string.Join(",", func(new List<T>() { item, item }));
15001515
}
15011516

1502-
class TestViewModel2
1517+
public class TestViewModel2
15031518
{
15041519
public int MyProperty { get; set; }
15051520
public string SomeString { get; set; }
@@ -1520,7 +1535,7 @@ public class TestViewModel3 : DotvvmViewModelBase
15201535
public string SomeString { get; set; }
15211536
}
15221537

1523-
class TestViewModel4
1538+
public class TestViewModel4
15241539
{
15251540
public int Number { get; set; }
15261541

@@ -1537,7 +1552,7 @@ public Task Multiply()
15371552
}
15381553
}
15391554

1540-
class TestViewModel5
1555+
public class TestViewModel5
15411556
{
15421557
public Dictionary<int, int> Dictionary { get; set; } = new Dictionary<int, int>()
15431558
{
@@ -1559,16 +1574,16 @@ class TestViewModel5
15591574
public int[] Array { get; set; } = new int[] { 1, 2, 3 };
15601575
}
15611576

1562-
struct TestStruct
1577+
public struct TestStruct
15631578
{
15641579
public int Int { get; set; }
15651580
}
1566-
class Something
1581+
public class Something
15671582
{
15681583
public bool Value { get; set; }
15691584
public string StringValue { get; set; }
15701585
}
1571-
enum TestEnum
1586+
public enum TestEnum
15721587
{
15731588
A,
15741589
B,
@@ -1584,4 +1599,17 @@ public static class TestStaticClass
15841599
{
15851600
public static string GetSomeString() => "string 123";
15861601
}
1602+
1603+
public class Strings
1604+
{
1605+
public static string SomeResource = "hello";
1606+
}
1607+
}
1608+
1609+
namespace DotVVM.Framework.Tests.Ambiguous
1610+
{
1611+
public class Strings
1612+
{
1613+
public static string SomeResource = "hello2";
1614+
}
15871615
}

src/Tests/Runtime/ControlTree/ControlValidationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace DotVVM.Framework.Tests.Runtime.ControlTree
1212
{
1313

14-
internal class TestViewModel : Tests.Binding.TestViewModel
14+
public class TestViewModel : Tests.Binding.TestViewModel
1515
{
1616
public StructList<DateTime?>? NullableDateList { get; }
1717
public StructList<DateTime>? DateList { get; }

0 commit comments

Comments
 (0)