-
Notifications
You must be signed in to change notification settings - Fork 22
Omsmith/destructured assignments #741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -333,6 +333,18 @@ ExpressionSyntax expression | |
| assignedType: assignedType | ||
| ); | ||
| } | ||
|
|
||
| public static AssignmentInfo Create( | ||
| bool isInitializer, | ||
| ExpressionSyntax expression, | ||
| ITypeSymbol assignedType | ||
| ) { | ||
| return new AssignmentInfo( | ||
| isInitializer: isInitializer, | ||
| expression: expression, | ||
| assignedType: assignedType | ||
| ); | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -357,13 +369,9 @@ ExpressionSyntax initializer | |
| .Select( sr => sr.GetSyntax() ) | ||
| .SelectMany( constructorSyntax => constructorSyntax.DescendantNodes() ) | ||
| .OfType<AssignmentExpressionSyntax>() | ||
| .Where( assignmentSyntax => IsAnAssignmentTo( assignmentSyntax, memberSymbol ) ) | ||
| .Select( assignmentSyntax => assignmentSyntax.Right ) | ||
| .Select( expr => AssignmentInfo.Create( | ||
| model: m_compilation.GetSemanticModel( expr.SyntaxTree ), | ||
| isInitializer: false, | ||
| expression: expr | ||
| ) ); | ||
| .Select( assignmentSyntax => GetAssignmentInfoIfToSymbolOrNull( assignmentSyntax, memberSymbol ) ) | ||
| .Where( info => info.HasValue ) | ||
| .Select( info => info.Value ); | ||
|
|
||
| if ( initializer != null ) { | ||
| assignmentExpressions = assignmentExpressions.Append( | ||
|
|
@@ -378,19 +386,86 @@ ExpressionSyntax initializer | |
| return assignmentExpressions; | ||
| } | ||
|
|
||
| private bool IsAnAssignmentTo( | ||
| private AssignmentInfo? GetAssignmentInfoIfToSymbolOrNull( | ||
| AssignmentExpressionSyntax assignmentSyntax, | ||
| ISymbol memberSymbol | ||
| ) { | ||
| var semanticModel = m_compilation.GetSemanticModel( assignmentSyntax.SyntaxTree ); | ||
|
|
||
| var leftSideSymbol = semanticModel.GetSymbolInfo( assignmentSyntax.Left ) | ||
| .Symbol; | ||
| var lhsExpressions = assignmentSyntax.Left switch { | ||
| TupleExpressionSyntax tuple => tuple.Arguments.Select( arg => arg.Expression ).ToImmutableArray(), | ||
| _ => ImmutableArray.Create( assignmentSyntax.Left ) | ||
| }; | ||
|
|
||
| for( var i = 0; i < lhsExpressions.Length; ++i ) { | ||
| var lhs = lhsExpressions[ i ]; | ||
|
|
||
| return SymbolEqualityComparer.Default.Equals( | ||
| memberSymbol, | ||
| leftSideSymbol | ||
| ); | ||
| var leftSideSymbol = semanticModel.GetSymbolInfo( lhs ) | ||
| .Symbol; | ||
|
|
||
| if( !SymbolEqualityComparer.Default.Equals( | ||
| memberSymbol, | ||
| leftSideSymbol | ||
| ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| if( lhsExpressions.Length == 1 ) { | ||
| return AssignmentInfo.Create( | ||
| model: semanticModel, | ||
| isInitializer: false, | ||
| expression: assignmentSyntax.Right | ||
| ); | ||
| } | ||
|
|
||
| if( semanticModel.GetTypeInfo( assignmentSyntax.Right ).Type is INamedTypeSymbol assignedType | ||
| && assignedType.IsTupleType | ||
| ) { | ||
|
Comment on lines
+424
to
+426
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle Value Tuples.
|
||
| if( assignedType.TypeArguments.Length != lhsExpressions.Length ) { | ||
| return AssignmentInfo.Create( | ||
| isInitializer: false, | ||
| expression: assignmentSyntax.Right, | ||
| assignedType: null | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is an "error / unknown assignment" - there's probably a better way to signal this... |
||
| ); | ||
| } | ||
|
|
||
| ExpressionSyntax rhs = assignmentSyntax.Right switch { | ||
| TupleExpressionSyntax tuple => tuple.Arguments[ i ].Expression, | ||
| _ => assignmentSyntax.Right | ||
| }; | ||
|
|
||
| return AssignmentInfo.Create( | ||
| isInitializer: false, | ||
| expression: rhs, | ||
| assignedType: assignedType.TypeArguments[ i ] | ||
| ); | ||
| } | ||
|
|
||
| DeconstructionInfo deconstruction = semanticModel.GetDeconstructionInfo( assignmentSyntax ); | ||
| if( deconstruction.Method == null ) { | ||
| return AssignmentInfo.Create( | ||
| isInitializer: false, | ||
| expression: assignmentSyntax.Right, | ||
| assignedType: null | ||
| ); | ||
| } | ||
|
|
||
| if( deconstruction.Method.Parameters.Length != lhsExpressions.Length ) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I lied there, this is what's filtering out nested deconstruction. I don't think it would be terribly hard to support, I just don't care to write the code. |
||
| return AssignmentInfo.Create( | ||
| isInitializer: false, | ||
| expression: assignmentSyntax.Right, | ||
| assignedType: null | ||
| ); | ||
| } | ||
|
|
||
| return AssignmentInfo.Create( | ||
| isInitializer: false, | ||
| expression: assignmentSyntax.Right, | ||
| assignedType: deconstruction.Method.Parameters[ i ].Type | ||
| ); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only handles simple deconstructions, not nested ones like so: class A {
public void Deconstruct( out string X, out B foo );
}
class B {
public void Deconstruct( out string Y, out string Z );
}
public void Foo() {
var (x, y, z) = new A();
} |
||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private enum AssignmentQueryKind { | ||
|
|
@@ -474,6 +549,17 @@ .Symbol is not IMethodSymbol methodSymbol | |
| break; | ||
| } | ||
|
|
||
| if( assignment.AssignedType == null | ||
| || assignment.AssignedType.TypeKind == TypeKind.Error | ||
| ) { | ||
| diagnostic = Diagnostic.Create( | ||
| Diagnostics.NonImmutableTypeHeldByImmutable, | ||
| assignment.Expression.GetLocation(), | ||
| "blarg", "blarg", "blarg" | ||
| ); | ||
| return AssignmentQueryKind.Hopeless; | ||
|
Comment on lines
+581
to
+586
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to add some sort of new "Unexpected" diagnostic. (We currently have UnexpectedTypeKind and UnexpectedMemberKind) |
||
| } | ||
|
|
||
| // If nothing above was caught, then fallback to querying. | ||
|
|
||
| if( assignment.Expression is BaseObjectCreationExpressionSyntax _ ) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the constructor just be public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah for sho. Discovering the end solution organically.