@ -31,6 +31,7 @@ public partial class AvaloniaPropertyAnalyzer
private readonly INamedTypeSymbol _ avaloniaObjectType ;
private readonly ImmutableHashSet < IMethodSymbol > _ getValueMethods ;
private readonly ImmutableHashSet < IMethodSymbol > _ setValueMethods ;
private readonly ImmutableHashSet < IMethodSymbol > _ allGetSetMethods ;
private readonly INamedTypeSymbol _ avaloniaPropertyType ;
private readonly INamedTypeSymbol _ styledPropertyType ;
private readonly INamedTypeSymbol _ attachedPropertyType ;
@ -38,6 +39,11 @@ public partial class AvaloniaPropertyAnalyzer
private readonly ImmutableArray < INamedTypeSymbol > _ allAvaloniaPropertyTypes ;
private readonly ImmutableHashSet < IMethodSymbol > _ avaloniaPropertyRegisterMethods ;
private readonly ImmutableHashSet < IMethodSymbol > _ avaloniaPropertyAddOwnerMethods ;
private readonly ImmutableHashSet < IMethodSymbol > _ allAvaloniaPropertyMethods ;
private readonly ImmutableDictionary < IMethodSymbol , ITypeParameterSymbol > _ ownerTypeParams ;
private readonly ImmutableDictionary < IMethodSymbol , ITypeParameterSymbol > _ valueTypeParams ;
private readonly ImmutableDictionary < IMethodSymbol , ITypeParameterSymbol > _ hostTypeParams ;
private readonly ImmutableDictionary < IMethodSymbol , IParameterSymbol > _ inheritsParams ;
public CompileAnalyzer ( CompilationStartAnalysisContext context , INamedTypeSymbol avaloniaObjectType )
{
@ -47,6 +53,7 @@ public partial class AvaloniaPropertyAnalyzer
_ avaloniaObjectType = avaloniaObjectType ;
_ getValueMethods = _ avaloniaObjectType . GetMembers ( "GetValue" ) . OfType < IMethodSymbol > ( ) . ToImmutableHashSet ( methodComparer ) ;
_ setValueMethods = _ avaloniaObjectType . GetMembers ( "SetValue" ) . OfType < IMethodSymbol > ( ) . ToImmutableHashSet ( methodComparer ) ;
_ allGetSetMethods = _ getValueMethods . Concat ( _ setValueMethods ) . ToImmutableHashSet ( methodComparer ) ;
_ avaloniaPropertyType = GetTypeOrThrow ( "Avalonia.AvaloniaProperty" ) ;
_ styledPropertyType = GetTypeOrThrow ( "Avalonia.StyledProperty`1" ) ;
@ -61,11 +68,19 @@ public partial class AvaloniaPropertyAnalyzer
_ avaloniaPropertyAddOwnerMethods = _ allAvaloniaPropertyTypes
. SelectMany ( t = > t . GetMembers ( "AddOwner" ) . OfType < IMethodSymbol > ( ) ) . ToImmutableHashSet ( methodComparer ) ;
_ allAvaloniaPropertyMethods = _ avaloniaPropertyRegisterMethods . Concat ( _ avaloniaPropertyAddOwnerMethods ) . ToImmutableHashSet ( SymbolEqualityComparer < IMethodSymbol > . Default ) ;
_ ownerTypeParams = GetParamDictionary ( "TOwner" , m = > m . TypeParameters ) ;
_ valueTypeParams = GetParamDictionary ( "TValue" , m = > m . TypeParameters ) ;
_ hostTypeParams = GetParamDictionary ( "THost" , m = > m . TypeParameters ) ;
_ inheritsParams = GetParamDictionary ( "inherits" , m = > m . Parameters ) ;
RegisterAvaloniaPropertySymbols ( context . Compilation , context . CancellationToken ) ;
context . RegisterOperationAction ( AnalyzeFieldInitializer , OperationKind . FieldInitializer ) ;
context . RegisterOperationAction ( AnalyzePropertyInitializer , OperationKind . PropertyInitializer ) ;
context . RegisterOperationAction ( AnalyzeAssignment , OperationKind . SimpleAssignment ) ;
context . RegisterOperationAction ( AnalyzeMethodInvocation , OperationKind . Invocation ) ;
context . RegisterSymbolStartAction ( StartPropertySymbolAnalysis , SymbolKind . Property ) ;
@ -75,6 +90,11 @@ public partial class AvaloniaPropertyAnalyzer
}
INamedTypeSymbol GetTypeOrThrow ( string name ) = > context . Compilation . GetTypeByMetadataName ( name ) ? ? throw new KeyNotFoundException ( $"Could not locate {name} in the compilation context." ) ;
ImmutableDictionary < IMethodSymbol , TSymbol > GetParamDictionary < TSymbol > ( string name , Func < IMethodSymbol , IEnumerable < TSymbol > > methodSymbolSelector ) where TSymbol : ISymbol = > _ allAvaloniaPropertyMethods
. Select ( m = > methodSymbolSelector ( m ) . SingleOrDefault ( p = > p . Name = = name ) )
. Where ( p = > p ! = null ) . Cast < TSymbol > ( )
. ToImmutableDictionary ( p = > ( IMethodSymbol ) p . ContainingSymbol , SymbolEqualityComparer < IMethodSymbol > . Default ) ;
}
private bool IsAvaloniaPropertyStorage ( IFieldSymbol symbol ) = > symbol . Type is INamedTypeSymbol namedType & & IsAvaloniaPropertyType ( namedType , _ allAvaloniaPropertyTypes ) ;
@ -91,13 +111,7 @@ public partial class AvaloniaPropertyAnalyzer
{
var current = namespaceStack . Pop ( ) ;
foreach ( var type in current . GetTypeMembers ( ) )
{
if ( DerivesFrom ( type , _ avaloniaObjectType ) )
{
types . Add ( type ) ;
}
}
types . AddRange ( current . GetTypeMembers ( ) ) ;
foreach ( var child in current . GetNamespaceMembers ( ) )
{
@ -114,6 +128,8 @@ public partial class AvaloniaPropertyAnalyzer
var parallelOptions = new ParallelOptions ( ) { CancellationToken = cancellationToken } ;
var semanticModels = new ConcurrentDictionary < SyntaxTree , SemanticModel > ( ) ;
Parallel . ForEach ( types , parallelOptions , type = >
{
try
@ -133,38 +149,17 @@ public partial class AvaloniaPropertyAnalyzer
foreach ( var constructor in type . StaticConstructors )
{
foreach ( var syntaxRef in constructor . DeclaringSyntaxReferences )
foreach ( var syntaxRef in constructor . DeclaringSyntaxReferences . Where ( sr = > compilation . ContainsSyntaxTree ( sr . SyntaxTree ) ) )
{
var node = syntaxRef . GetSyntax ( cancellationToken ) ;
if ( ! compilation . ContainsSyntaxTree ( node . SyntaxTree ) )
{
continue ;
}
var model = compilation . GetSemanticModel ( node . SyntaxTree ) ;
var ( node , model ) = GetNodeAndModel ( syntaxRef ) ;
foreach ( var descendant in node . DescendantNodes ( ) )
foreach ( var descendant in node . DescendantNodes ( ) . Where ( n = > n . IsKind ( SyntaxKind . SimpleAssignmentExpression ) ) )
{
switch ( descendant . Kind ( ) )
{
case SyntaxKind . SimpleAssignmentExpression :
var assignmentOperation = ( IAssignmentOperation ) model . GetOperation ( descendant , cancellationToken ) ! ;
var target = assignmentOperation . Target switch
{
IFieldReferenceOperation fieldRef = > fieldRef . Field ,
IPropertyReferenceOperation propertyRef = > propertyRef . Property ,
_ = > default ( ISymbol ) ,
} ;
if ( target = = null )
{
break ;
}
var assignmentOperation = ( IAssignmentOperation ) model . GetOperation ( descendant , cancellationToken ) ! ;
RegisterAssignment ( target , assignmentOperation . Value ) ;
break ;
if ( GetReferencedFieldOrProperty ( assignmentOperation . Target ) is { } target )
{
RegisterAssignment ( target , assignmentOperation . Value ) ;
}
}
}
@ -178,15 +173,10 @@ public partial class AvaloniaPropertyAnalyzer
Parallel . ForEach ( avaloniaPropertyStorageSymbols , parallelOptions , symbol = >
{
foreach ( var syntaxRef in symbol . DeclaringSyntaxReferences )
foreach ( var syntaxRef in symbol . DeclaringSyntaxReferences . Where ( sr = > compilation . ContainsSyntaxTree ( sr . SyntaxTree ) ) )
{
var node = syntaxRef . GetSyntax ( cancellationToken ) ;
if ( ! compilation . ContainsSyntaxTree ( node . SyntaxTree ) )
{
continue ;
}
var ( node , model ) = GetNodeAndModel ( syntaxRef ) ;
var model = compilation . GetSemanticModel ( node . SyntaxTree ) ;
var operation = node . ChildNodes ( ) . Select ( n = > model . GetOperation ( n , cancellationToken ) ) . OfType < ISymbolInitializerOperation > ( ) . FirstOrDefault ( ) ;
if ( operation = = null )
@ -199,7 +189,7 @@ public partial class AvaloniaPropertyAnalyzer
} ) ;
// we have recorded every Register and AddOwner call. Now follow assignment chains.
foreach ( var root in fieldInitializations . Keys . Intersect ( propertyDescriptions . Keys , SymbolEqualityComparer . Default ) . ToArray ( ) )
Parallel . ForEach ( fieldInitializations . Keys . Intersect ( propertyDescriptions . Keys , SymbolEqualityComparer . Default ) . ToArray ( ) , root = >
{
var propertyDescription = propertyDescriptions [ root ] ;
var owner = propertyDescription . AssignedTo [ root ] ;
@ -209,13 +199,13 @@ public partial class AvaloniaPropertyAnalyzer
{
var target = fieldInitializations [ current ] ;
propertyDescription . AddAssignment ( target , owner ) ; // This loop handles simple assignment operations, so do NOT change the owner
propertyDescription . AddAssignment ( target , new ( owner . Type , target . Locations [ 0 ] ) ) ; // This loop handles simple assignment operations, so do NOT change the owner type
propertyDescriptions [ target ] = propertyDescription ;
fieldInitializations . TryGetValue ( target , out current ) ;
}
while ( current ! = null ) ;
}
} ) ;
var clrPropertyWrapCandidates = new ConcurrentBag < ( IPropertySymbol , AvaloniaPropertyDescription ) > ( ) ;
@ -251,7 +241,7 @@ public partial class AvaloniaPropertyAnalyzer
// convert our dictionaries to immutable form
_ clrPropertyToAvaloniaProperties = clrPropertyWrapCandidates . ToLookup ( t = > t . Item1 , t = > t . Item2 , SymbolEqualityComparer < IPropertySymbol > . Default )
. ToImmutableDictionary ( kvp = > kvp . Key , kvp = > kvp . ToImmutableArray ( ) , SymbolEqualityComparer < IPropertySymbol > . Default ) ;
. ToImmutableDictionary ( g = > g . Key , g = > g . Distinct ( ) . ToImmutableArray ( ) , SymbolEqualityComparer < IPropertySymbol > . Default ) ;
_ avaloniaPropertyDescriptions = propertyDescriptions . ToImmutableDictionary ( kvp = > kvp . Key , kvp = > kvp . Value . Seal ( ) , SymbolEqualityComparer . Default ) ;
void RegisterAssignment ( ISymbol target , IOperation value )
@ -269,8 +259,12 @@ public partial class AvaloniaPropertyAnalyzer
break ;
}
}
( SyntaxNode , SemanticModel ) GetNodeAndModel ( SyntaxReference syntaxRef ) = >
( syntaxRef . GetSyntax ( cancellationToken ) , semanticModels . GetOrAdd ( syntaxRef . SyntaxTree , st = > compilation . GetSemanticModel ( st ) ) ) ;
}
// This method handles registration of a new AvaloniaProperty, and calls to AddOwner.
private void RegisterInitializer_Invocation ( IInvocationOperation invocation , ISymbol target , ConcurrentDictionary < ISymbol , AvaloniaPropertyDescription > propertyDescriptions )
{
try
@ -280,49 +274,38 @@ public partial class AvaloniaPropertyAnalyzer
return ;
}
if ( _ avaloniaPropertyRegisterMethods . Contains ( invocation . TargetMethod . OriginalDefinition ) ) // This is a call to one of the AvaloniaProperty.Register* methods
var originalMethod = invocation . TargetMethod . OriginalDefinition ;
if ( _ avaloniaPropertyRegisterMethods . Contains ( originalMethod ) ) // This is a call to one of the AvaloniaProperty.Register* methods
{
if ( ! invocation . TargetMethod . IsGenericMethod )
TypeReference ownerTypeRef ;
if ( _ ownerTypeParams . TryGetValue ( originalMethod , out var ownerTypeParam ) )
{
return ;
ownerTypeRef = TypeReference . FromInvocationTypeParameter ( invocation , ownerTypeParam ) ;
}
var typeParamLookup = invocation . TargetMethod . TypeParameters . Select ( ( s , i ) = > ( param : s , index : i ) )
. ToDictionary ( t = > t . param . Name , t = >
{
var argument = invocation . TargetMethod . TypeArguments [ t . index ] ;
var typeArgumentSyntax = invocation . Syntax ;
if ( invocation . Language = = LanguageNames . CSharp ) // type arguments do not appear in the invocation, so search the code for them
{
try
{
typeArgumentSyntax = invocation . Syntax . DescendantNodes ( )
. First ( n = > n . IsKind ( SyntaxKind . TypeArgumentList ) )
. DescendantNodes ( ) . ElementAt ( t . index ) ;
}
catch
{
// ignore, this is unimportant
}
}
return new TypeReference ( ( INamedTypeSymbol ) argument , typeArgumentSyntax . GetLocation ( ) ) ;
} ) ;
if ( ! typeParamLookup . TryGetValue ( "TOwner" , out var ownerTypeRef ) & & // if it's NOT a generic parameter, try to work out the runtime value
invocation . TargetMethod . Parameters . FirstOrDefault ( p = > p . Name = = "ownerType" ) is INamedTypeSymbol ownerTypeParam & &
invocation . Arguments . FirstOrDefault ( a = > SymbolEquals ( a . Parameter , ownerTypeParam ) ) is IArgumentOperation argument )
else if ( invocation . TargetMethod . Parameters . FirstOrDefault ( p = > p . Name = = "ownerType" ) is { } ownerParam ) // try extracting the runtime argument
{
switch ( ResolveOperationSource ( a rgument. Value ) )
switch ( ResolveOperationSource ( invocation . Arguments [ ownerParam . Ordinal ] . Value ) )
{
case ITypeOfOperation typeOf when typeOf . Type is INamedTypeSymbol type :
case ITypeOfOperation { Type : INamedTypeSymbol type } typeOf :
ownerTypeRef = new TypeReference ( type , typeOf . Syntax . GetLocation ( ) ) ;
break ;
default :
return ;
}
}
else
{
return ;
}
if ( ownerTypeRef . Type = = null | | ! typeParamLookup . TryGetValue ( "TValue" , out var propertyValueTypeRef ) )
TypeReference valueTypeRef ;
if ( _ valueTypeParams . TryGetValue ( originalMethod , out var valueTypeParam ) )
{
valueTypeRef = TypeReference . FromInvocationTypeParameter ( invocation , valueTypeParam ) ;
}
else
{
return ;
}
@ -343,47 +326,69 @@ public partial class AvaloniaPropertyAnalyzer
return ;
}
var description = propertyDescriptions . GetOrAdd ( target , s = > new AvaloniaPropertyDescription ( name , propertyType , propertyValueTypeRef . Type ) ) ;
var inherits = false ;
if ( _ inheritsParams . TryGetValue ( originalMethod , out var inheritsParam ) & &
invocation . Arguments [ inheritsParam . Ordinal ] . Value is ILiteralOperation literalOp & &
literalOp . ConstantValue . Value is bool constValue )
{
inherits = constValue ;
}
TypeReference ? hostTypeRef = null ;
if ( SymbolEquals ( propertyType . OriginalDefinition , _ attachedPropertyType ) )
{
if ( _ hostTypeParams . TryGetValue ( originalMethod , out var hostTypeParam ) )
{
hostTypeRef = TypeReference . FromInvocationTypeParameter ( invocation , hostTypeParam ) ;
}
else
{
hostTypeRef = new ( _ avaloniaObjectType , Location . None ) ;
}
}
var description = propertyDescriptions . GetOrAdd ( target , s = > new AvaloniaPropertyDescription ( name , propertyType , valueTypeRef . Type ) ) ;
description . Name = name ;
description . HostType = hostTypeRef ;
description . Inherits = inherits ;
description . AddAssignment ( target , ownerTypeRef ) ;
description . AddOwner ( ownerTypeRef ) ;
}
else if ( _ avaloniaPropertyAddOwnerMethods . Contains ( invocation . TargetMethod . OriginalDefinition ) ) // This is a call to one of the AddOwner methods
{
if ( invocation . TargetMethod . TypeArguments [ 0 ] is not INamedTypeSymbol ownerType )
if ( ! _ ownerTypeParams . TryGetValue ( invocation . TargetMethod . OriginalDefinition , out var ownerTypeParam ) | |
invocation . TargetMethod . TypeArguments [ ownerTypeParam . Ordinal ] is not INamedTypeSymbol )
{
return ;
}
var ownerTypeRef = new TypeReference ( ownerType , invocation . TargetMethod . TypeArguments [ 0 ] . Locations [ 0 ] ) ;
ISymbol sourceSymbol ;
switch ( invocation . Instance )
if ( GetReferencedFieldOrProperty ( invocation . Instance ) is not { } sourceSymbol )
{
case IFieldReferenceOperation fieldReference :
sourceSymbol = fieldReference . Field ;
break ;
case IPropertyReferenceOperation propertyReference :
sourceSymbol = propertyReference . Property ;
break ;
default :
return ;
return ;
}
var propertyValueType = AvaloniaPropertyType_GetValueType ( propertyType ) ;
var description = propertyDescriptions . GetOrAdd ( target , s = >
var description = propertyDescriptions . GetOrAdd ( sourceSymbol , s = >
{
string inferredName = target . Name ;
string inferredName = s . Name ;
var match = Regex . Match ( target . Name , "(?<name>.*)Property$" ) ;
var match = Regex . Match ( s . Name , "(?<name>.*)Property$" ) ;
if ( match . Success )
{
inferredName = match . Groups [ "name" ] . Value ;
}
return new AvaloniaPropertyDescription ( inferredName , ( INamedTypeSymbol ) invocation . TargetMethod . ReturnType , propertyValueType ) ;
var propertyValueType = AvaloniaPropertyType_GetValueType ( propertyType ) ;
TypeReference ? hostTypeRef = null ;
if ( SymbolEquals ( propertyType . OriginalDefinition , _ attachedPropertyType ) )
{
hostTypeRef = new ( _ avaloniaObjectType , Location . None ) ; // assume that an attached property applies everywhere until we find its registration
}
return new AvaloniaPropertyDescription ( inferredName , propertyType , propertyValueType ) { HostType = hostTypeRef } ;
} ) ;
var ownerTypeRef = TypeReference . FromInvocationTypeParameter ( invocation , ownerTypeParam ) ;
description . AddAssignment ( target , ownerTypeRef ) ;
description . AddOwner ( ownerTypeRef ) ;
}
@ -458,7 +463,7 @@ public partial class AvaloniaPropertyAnalyzer
try
{
var ( target , isValid ) = operation . Target switch
var ( target , isValid ) = ResolveOperationSource ( operation . Target ) switch
{
IFieldReferenceOperation fieldRef = > ( fieldRef . Field , IsValidAvaloniaPropertyStorage ( fieldRef . Field ) ) ,
IPropertyReferenceOperation propertyRef = > ( propertyRef . Property , IsValidAvaloniaPropertyStorage ( propertyRef . Property ) ) ,
@ -508,6 +513,58 @@ public partial class AvaloniaPropertyAnalyzer
}
}
/// <seealso cref="UnexpectedPropertyAccess"/>
/// <seealso cref="InappropriatePropertyRegistration"/>
private void AnalyzeMethodInvocation ( OperationAnalysisContext context )
{
var invocation = ( IInvocationOperation ) context . Operation ;
var originalMethod = invocation . TargetMethod . OriginalDefinition ;
if ( _ allGetSetMethods . Contains ( originalMethod ) )
{
var avaloniaPropertyOperation = invocation . Arguments [ 0 ] . Value ;
var propertyStorageSymbol = GetReferencedFieldOrProperty ( ResolveOperationSource ( avaloniaPropertyOperation ) ) ;
if ( propertyStorageSymbol = = null | | ! _ avaloniaPropertyDescriptions . TryGetValue ( propertyStorageSymbol , out var propertyDescription ) )
{
return ;
}
TypeReference ownerOrHostType ;
if ( SymbolEquals ( propertyDescription . PropertyType . OriginalDefinition , _ attachedPropertyType ) )
{
ownerOrHostType = propertyDescription . HostType ! . Value ;
}
else if ( ! propertyDescription . AssignedTo . TryGetValue ( propertyStorageSymbol , out ownerOrHostType ) )
{
return ;
}
if ( invocation . Instance is IInstanceReferenceOperation { ReferenceKind : InstanceReferenceKind . ContainingTypeInstance } & &
! DerivesFrom ( context . ContainingSymbol . ContainingType , ownerOrHostType . Type ) )
{
context . ReportDiagnostic ( Diagnostic . Create ( UnexpectedPropertyAccess , invocation . Arguments [ 0 ] . Syntax . GetLocation ( ) ,
GetReferencedFieldOrProperty ( avaloniaPropertyOperation ) , context . ContainingSymbol . ContainingType ) ) ;
}
}
else if ( ! IsStaticConstructorOrInitializer ( ) & & _ allAvaloniaPropertyMethods . Contains ( originalMethod ) )
{
context . ReportDiagnostic ( Diagnostic . Create ( InappropriatePropertyRegistration , invocation . Syntax . GetLocation ( ) ,
originalMethod . ToDisplayString ( TypeQualifiedName ) ) ) ;
}
bool IsStaticConstructorOrInitializer ( ) = >
context . ContainingSymbol is IMethodSymbol { MethodKind : MethodKind . StaticConstructor } | |
ResolveOperationTarget ( invocation . Parent ! ) switch
{
IFieldInitializerOperation fieldInit when fieldInit . InitializedFields . All ( f = > f . IsStatic ) = > true ,
IPropertyInitializerOperation propInit when propInit . InitializedProperties . All ( p = > p . IsStatic ) = > true ,
_ = > false ,
} ;
}
/// <seealso cref="AmbiguousPropertyName"/>
/// <seealso cref="PropertyTypeMismatch"/>
/// <seealso cref="AssociatedAvaloniaProperty"/>
@ -520,7 +577,7 @@ public partial class AvaloniaPropertyAnalyzer
{
if ( ! _ clrPropertyToAvaloniaProperties . TryGetValue ( property , out var candidateTargetProperties ) )
{
return ; // does not refer to an AvaloniaProperty
return ; // does not refer to an AvaloniaProperty
}
context . RegisterSymbolEndAction ( context = >