From 100ea9180666cbf9281638578a2c2dc0461b2221 Mon Sep 17 00:00:00 2001 From: egokb Date: Wed, 8 Jan 2025 16:26:05 +0800 Subject: [PATCH] fix list binding error --- .../AttributeBindingAnalyzer.Property.cs | 32 ++-- .../AttributeBindingAnalyzer.ViewModel.cs | 2 +- FUIAnalyzer/AttributeBinding/FUIStubs.cs | 16 -- FUIAnalyzer/FUIStubs.cs | 42 +++++ FUIAnalyzer/FUITypeSymbolExtensions.cs | 158 ++++++++++++++++++ FUIAnalyzer/TypeSymbolExtensions.cs | 149 +++++++++++++---- 6 files changed, 329 insertions(+), 70 deletions(-) delete mode 100644 FUIAnalyzer/AttributeBinding/FUIStubs.cs create mode 100644 FUIAnalyzer/FUIStubs.cs create mode 100644 FUIAnalyzer/FUITypeSymbolExtensions.cs diff --git a/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.Property.cs b/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.Property.cs index cf8372e..c16fc96 100644 --- a/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.Property.cs +++ b/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.Property.cs @@ -62,7 +62,7 @@ public partial class AttributeBindingAnalyzer ConverterNotIConverterRule, PropertyToTargetWithoutConverterRule, PropertyToTargetWithConverterRule, - TargetMustBeNameOfRule + TargetMustBeNameOfRule, }; /// @@ -102,10 +102,16 @@ void AnalyzePropertyAttribute(SyntaxNodeAnalysisContext context, PropertyDeclara return; } + //如果属性和目标值类型都是可绑定列表,不进行下面的判断 + if (propertyType.IsObservableList() && targetPropertyType.IsObservableList()) + { + return; + } + if (converterInfo == default) { //如果没有转换器且属性类型无法转换成目标值类型 - if (!propertyType.InheritsFromOrEquals(targetPropertyType)) + if (!propertyType.Extends(targetPropertyType)) { var diagnostic = Diagnostic.Create(PropertyToTargetWithoutConverterRule, attribute.GetLocation(), propertyType, targetPropertyType); context.ReportDiagnostic(diagnostic); @@ -114,8 +120,8 @@ void AnalyzePropertyAttribute(SyntaxNodeAnalysisContext context, PropertyDeclara else { //如果有转换器,但是无法将属性类型转换成转换器源类型,或无法将转换器目标类型转换成绑定目标值类型 - if (!propertyType.InheritsFromOrEquals(converterInfo.sourceType) - || !converterInfo.targetType.InheritsFromOrEquals(targetPropertyType)) + if (!propertyType.Extends(converterInfo.sourceType) + || !converterInfo.targetType.Extends(targetPropertyType)) { var diagnostic = Diagnostic.Create(PropertyToTargetWithConverterRule, attribute.GetLocation(), propertyType, converterInfo.sourceType, converterInfo.targetType, targetPropertyType); context.ReportDiagnostic(diagnostic); @@ -153,7 +159,7 @@ void AnalyzePropertyAttribute(SyntaxNodeAnalysisContext context, PropertyDeclara //判断目标类型是否是IElement var targetTypeInfo = context.SemanticModel.GetTypeInfo(memberAccess.Expression); - if (targetTypeInfo.Type.AllInterfaces.FirstOrDefault((item) => item.ToString().StartsWith("FUI.IElement")) == null) + if(!targetTypeInfo.Type.Extends(typeof(FUI.IElement))) { var diagnostic = Diagnostic.Create(TargetNotElementRule, memberAccess.Expression.GetLocation(), targetTypeInfo.Type); context.ReportDiagnostic(diagnostic); @@ -175,8 +181,8 @@ INamedTypeSymbol GetTargetPropertyType(SyntaxNodeAnalysisContext context, Attrib return default; } - var @interface = targetPropertyTypeInfo.Value.Type.AllInterfaces.FirstOrDefault(item => item.IsGenericType && item.ToString().StartsWith("FUI.Bindable.IBindableProperty")); - if (@interface == null) + //如果不是则报错 + if(!targetPropertyTypeInfo.Value.Type.IsBindableProperty(out var targetValueType)) { var diagnostic = Diagnostic.Create(TargetPropertyNotBindableRule, memberAccess.Name.GetLocation(), targetPropertyTypeInfo.Value.Type); context.ReportDiagnostic(diagnostic); @@ -184,8 +190,7 @@ INamedTypeSymbol GetTargetPropertyType(SyntaxNodeAnalysisContext context, Attrib } //获取目标成员值类型 - var targetValueType = @interface.TypeArguments[0] as INamedTypeSymbol; - return targetValueType; + return targetValueType as INamedTypeSymbol; } /// @@ -207,10 +212,7 @@ INamedTypeSymbol GetTargetPropertyType(SyntaxNodeAnalysisContext context, Attrib var typeInfo = context.SemanticModel.GetTypeInfo(typeofExpression.Type); //判断是否继承自IValueConverter<> - var interfaces = typeInfo.Type.AllInterfaces.FirstOrDefault(item => item.IsGenericType && item.ToString().StartsWith("FUI.IValueConverter")); - - //如果不继承 则报错 - if (interfaces == null) + if(!typeInfo.Type.IsValueConverter(out var sourceType, out var targetType)) { var diagnostic = Diagnostic.Create(ConverterNotIConverterRule, typeofExpression.Type.GetLocation(), typeInfo.Type); context.ReportDiagnostic(diagnostic); @@ -218,9 +220,7 @@ INamedTypeSymbol GetTargetPropertyType(SyntaxNodeAnalysisContext context, Attrib } //返回其sourcesType和targetType - var sourceType = interfaces.TypeArguments[0] as INamedTypeSymbol; - var targetType = interfaces.TypeArguments[1] as INamedTypeSymbol; - return (sourceType, targetType); + return (sourceType as INamedTypeSymbol, targetType as INamedTypeSymbol); } } } diff --git a/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.ViewModel.cs b/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.ViewModel.cs index e6c7109..15d457d 100644 --- a/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.ViewModel.cs +++ b/FUIAnalyzer/AttributeBinding/AttributeBindingAnalyzer.ViewModel.cs @@ -60,7 +60,7 @@ void AnalyzeClassAttribute(SyntaxNodeAnalysisContext context, ClassDeclarationSy return; } - if (!namedType.InheritsFrom(typeof(FUI.Bindable.ObservableObject))) + if (!namedType.IsObservableObject()) { var diagnostic = Diagnostic.Create(BindingObjectNotObservableObjectRule, attribute.GetLocation(), namedType.ToString()); context.ReportDiagnostic(diagnostic); diff --git a/FUIAnalyzer/AttributeBinding/FUIStubs.cs b/FUIAnalyzer/AttributeBinding/FUIStubs.cs deleted file mode 100644 index dd3f912..0000000 --- a/FUIAnalyzer/AttributeBinding/FUIStubs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace FUI -{ - public class BindingAttribute : Attribute { } - - public class CommandAttribute : Attribute { } - - public interface IValueConverter { } -} - -namespace FUI.Bindable -{ - public class ObservableObject { } - public class BindableProperty { } -} \ No newline at end of file diff --git a/FUIAnalyzer/FUIStubs.cs b/FUIAnalyzer/FUIStubs.cs new file mode 100644 index 0000000..6fac388 --- /dev/null +++ b/FUIAnalyzer/FUIStubs.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace FUI +{ + public class BindingAttribute : Attribute { } + + public class CommandAttribute : Attribute { } + + public interface IValueConverter { } + + public interface IValueConverter { } + + public interface ISynchronizeProperties + { + void Synchronize(); + } + + public interface IElement { } +} + +namespace FUI.BindingDescriptor +{ + public class ContextDescriptor { } + + public class ContextDescriptor : ContextDescriptor where TViewModel : FUI.Bindable.ObservableObject { } + + public class CommandBindingDescriptor { } + + public class PropertyBindingDescriptor { } +} + +namespace FUI.Bindable +{ + public class ObservableObject { } + public class BindableProperty { } + public interface IBindableProperty { } + public interface INotifyPropertyChanged { } + public interface INotifyCollectionChanged { } + public interface IReadOnlyObservableList : IReadOnlyList, INotifyCollectionChanged { } + public class CommandTemplate { } +} \ No newline at end of file diff --git a/FUIAnalyzer/FUITypeSymbolExtensions.cs b/FUIAnalyzer/FUITypeSymbolExtensions.cs new file mode 100644 index 0000000..b1a9631 --- /dev/null +++ b/FUIAnalyzer/FUITypeSymbolExtensions.cs @@ -0,0 +1,158 @@ +using Microsoft.CodeAnalysis; + +using System.Collections.Generic; + +namespace FUIAnalyzer +{ + internal static class FUITypeSymbolExtensions + { + /// + /// 判断一个类型是否是可观察对象 + /// + public static bool IsObservableObject(this ITypeSymbol type) + { + return type.Extends(typeof(FUI.Bindable.INotifyPropertyChanged)); + } + + /// + /// 判断一个类型是否是可观察列表 + /// + public static bool IsObservableList(this ITypeSymbol type) + { + return type.Extends(typeof(FUI.Bindable.INotifyCollectionChanged)); + } + + /// + /// 判断一个类型是否是可观察列表 并获取元素类型 + /// + /// 源类型 + /// 列表元素类型 + /// + public static bool IsObservableList(this ITypeSymbol type, out ITypeSymbol elementType) + { + elementType = null; + if (!type.IsObservableList()) + { + return false; + } + + var listType = typeof(FUI.Bindable.IReadOnlyObservableList<>).GetGenericTypeDefinition(); + foreach (var interfaceType in type.AllInterfaces) + { + if (interfaceType.IsGenericType && interfaceType.ConstructedFrom.Matches(listType)) + { + elementType = interfaceType.TypeArguments[0]; + return true; + } + } + + return false; + } + + /// + /// 判断一个类型是否是可观察属性 + /// + public static bool IsBindableProperty(this ITypeSymbol type, out ITypeSymbol propertyValueType) + { + propertyValueType = null; + var bindablePropertyType = typeof(FUI.Bindable.IBindableProperty<>).GetGenericTypeDefinition(); + foreach (var interfaceType in type.AllInterfaces) + { + if (interfaceType.IsGenericType && interfaceType.ConstructedFrom.Matches(bindablePropertyType)) + { + propertyValueType = interfaceType.TypeArguments[0]; + return true; + } + } + + return false; + } + + /// + /// 判断一个类型是否是命令 + /// + public static bool IsCommand(this ITypeSymbol type, out IReadOnlyList arguments) + { + var commandType = typeof(FUI.Bindable.CommandTemplate<>).GetGenericTypeDefinition(); + arguments = null; + foreach (var t in type.GetBaseTypesAndThis()) + { + if (!(t is INamedTypeSymbol named)) + { + continue; + } + + if (named.IsGenericType && named.ConstructedFrom.Matches(commandType)) + { + var args = named.TypeArguments; + if (args.Length != 1) + { + return false; + } + + var actionType = args[0] as INamedTypeSymbol; + if (actionType == null) + { + return false; + } + + arguments = actionType.TypeArguments; + return true; + } + } + return false; + } + + /// + /// 判断一个类型是否是值转换器 + /// + /// 类型 + /// 值转换器 值类型 + /// 值转换器 目标类型 + /// + public static bool IsValueConverter(this ITypeSymbol type, out ITypeSymbol valueType, out ITypeSymbol targetType) + { + valueType = null; + targetType = null; + + var valueConverterType = typeof(FUI.IValueConverter<,>).GetGenericTypeDefinition(); + + foreach (var interfaceType in type.AllInterfaces) + { + if (interfaceType.IsGenericType && interfaceType.ConstructedFrom.Matches(valueConverterType)) + { + valueType = interfaceType.TypeArguments[0]; + targetType = interfaceType.TypeArguments[1]; + return true; + } + } + + return false; + } + + /// + /// 判断一个类型是否是绑定上下文描述器 + /// + /// 目标类型 + /// viewModel类型 + public static bool IsContextDescriptor(this ITypeSymbol type, out ITypeSymbol viewModelType) + { + viewModelType = null; + var descriptorType = typeof(FUI.BindingDescriptor.ContextDescriptor<>).GetGenericTypeDefinition(); + foreach (var t in type.GetBaseTypesAndThis()) + { + if (!(t is INamedTypeSymbol named)) + { + continue; + } + + if (named.IsGenericType && named.ConstructedFrom.Matches(descriptorType)) + { + viewModelType = named.TypeArguments[0]; + return true; + } + } + return false; + } + } +} diff --git a/FUIAnalyzer/TypeSymbolExtensions.cs b/FUIAnalyzer/TypeSymbolExtensions.cs index dc8e31f..b34ce03 100644 --- a/FUIAnalyzer/TypeSymbolExtensions.cs +++ b/FUIAnalyzer/TypeSymbolExtensions.cs @@ -1,30 +1,76 @@ using Microsoft.CodeAnalysis; -using System; using System.Collections.Generic; +using System; using System.Reflection; namespace FUIAnalyzer { internal static class TypeSymbolExtensions { - public static bool Extends(this ITypeSymbol symbol, Type type) + static Queue cache = new Queue(); + + /// + /// 判断一个类型是否继承自某个类型或者实现了某个接口 + /// + /// 源类型 + /// 目标类型 + /// + internal static bool Extends(this ITypeSymbol self, Type other) => Extends(self, other, (s, t) => s.Matches(t)); + + /// + /// 判断一个类型是否继承自另一个类型或者实现了某个接口 + /// + /// 源类型 + /// 目标类型 + /// + internal static bool Extends(this ITypeSymbol symbol, ITypeSymbol other) => Extends(symbol, other, (s, t) => s.Matches(t)); + + /// + /// 判断一个类型是否继承自另一个类型或者实现了某个接口 + /// + static bool Extends(this ITypeSymbol self, T other, Func matches) { - if (symbol == null || type == null) + if (self == null || other == null) + { return false; + } + + var openList = cache; + openList.Clear(); + openList.Enqueue(self); - while (symbol != null) + while (openList.Count > 0) { - if (symbol.Matches(type)) + var current = openList.Dequeue(); + + if (matches.Invoke(current, other)) + { return true; + } + + if (current.BaseType != null) + { + openList.Enqueue(current.BaseType); + } - symbol = symbol.BaseType; + foreach (var @interface in current.Interfaces) + { + openList.Enqueue(@interface); + } } return false; } - public static bool Matches(this ITypeSymbol symbol, Type type) + + /// + /// 判断一个类型是否和另一个类型匹配 + /// + /// 源类型 + /// 目标类型 + /// + internal static bool Matches(this ITypeSymbol symbol, Type type) { switch (symbol.SpecialType) { @@ -44,74 +90,103 @@ public static bool Matches(this ITypeSymbol symbol, Type type) } if (!(symbol is INamedTypeSymbol named)) + { return false; + } if (type.IsConstructedGenericType) { var args = type.GetTypeInfo().GenericTypeArguments; if (args.Length != named.TypeArguments.Length) + { return false; + } for (var i = 0; i < args.Length; i++) + { if (!Matches(named.TypeArguments[i], args[i])) + { return false; + } + } return Matches(named.ConstructedFrom, type.GetGenericTypeDefinition()); } - return named.Name == type.Name - && named.ContainingNamespace?.ToDisplayString() == type.Namespace; + return named.MetadataName == type.Name && named.ContainingNamespace?.ToDisplayString() == type.Namespace; } /// - /// 判断某个类型是否是指定类型 + /// 判断一个类型是否和另一个类型匹配 /// - /// 要判断的类型 - /// 目标类型 + /// 源类型 + /// 目标类型 /// - internal static bool IsType(this ITypeSymbol symbol, Type type) + internal static bool Matches(this ITypeSymbol self, ITypeSymbol other) { - if(!(symbol is INamedTypeSymbol named)) + switch (self.SpecialType) { - return false; + case SpecialType.System_Void: + case SpecialType.System_Boolean: + case SpecialType.System_Int32: + case SpecialType.System_Single: + return self.SpecialType == other.SpecialType; } - return named.Name == type.Name && named.ContainingNamespace?.ToDisplayString() == type.Namespace; - } + if (other is IArrayTypeSymbol otherArray) + { + return self is IArrayTypeSymbol array && Matches(array.ElementType, otherArray); + } - public static IEnumerable GetBaseTypesAndThis(this ITypeSymbol type) - { - var current = type; - while (current != null) + if (!(self is INamedTypeSymbol selfNamed) || !(other is INamedTypeSymbol otherNamed)) { - yield return current; - current = current.BaseType; + return false; } - } - public static bool InheritsFromOrEquals(this ITypeSymbol type, ITypeSymbol baseType) - { - foreach(var t in type.GetBaseTypesAndThis()) + if (selfNamed.IsGenericType && otherNamed.IsGenericType) { - if (SymbolEqualityComparer.Default.Equals(t, baseType)) + var otherTypeArgs = otherNamed.TypeArguments; + if (otherTypeArgs.Length != selfNamed.TypeArguments.Length) { - return true; + return false; + } + + for (var i = 0; i < otherTypeArgs.Length; i++) + { + if (!Matches(selfNamed.TypeArguments[i], otherTypeArgs[i])) + { + return false; + } } + + return Matches(selfNamed.ConstructedFrom, otherNamed.ConstructedFrom); } - return false; + return selfNamed.MetadataName == other.MetadataName && selfNamed.ContainingNamespace?.ToDisplayString() == other.ContainingNamespace?.ToDisplayString(); } - public static bool InheritsFrom(this ITypeSymbol type, Type baseType) + /// + /// 判断某个类型是否是指定类型 + /// + /// 要判断的类型 + /// 目标类型 + /// + internal static bool IsType(this ITypeSymbol symbol, Type type) + { + return Matches(symbol, type); + } + + /// + /// 获取类型的所有基类和自身 + /// + internal static IEnumerable GetBaseTypesAndThis(this ITypeSymbol type) { - foreach (var t in type.GetBaseTypesAndThis()) + var current = type; + while (current != null) { - if (t.IsType(baseType)) - { - return true; - } + yield return current; + current = current.BaseType; } - return false; } } }