Skip to content
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

Binding source generator #21725

Merged
merged 47 commits into from
Jun 4, 2024
Merged
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7b61154
Add new public API SetBinding<TSource, TProperty>
simonrozsival Mar 4, 2024
632979b
Add source generator skeleton
simonrozsival Mar 5, 2024
403f476
Setup unit tests for binding intermediate representation
jkurdek Mar 26, 2024
f9747a3
Added basic nullability support
jkurdek Mar 29, 2024
a4b9335
Remove unnecessary Id from the binding record
simonrozsival Apr 2, 2024
62f613e
Generate casts
simonrozsival Apr 2, 2024
e5343a5
Split index and member access
simonrozsival Apr 3, 2024
5d719ca
Cleanup
simonrozsival Apr 3, 2024
dd8112f
Update test case
simonrozsival Apr 3, 2024
6b325ae
Cleanup
simonrozsival Apr 4, 2024
e9093af
Fix the semantic of conditional access
simonrozsival Apr 5, 2024
5298785
add as-cast suport to source generator
jkurdek Apr 4, 2024
d1fed61
improve nullability check
jkurdek Apr 4, 2024
c8130cf
specify params in tests only when not default
jkurdek Apr 4, 2024
c0b7da6
simplify diagnostics
jkurdek Apr 4, 2024
7e82570
move path parser to separate class
jkurdek Apr 4, 2024
7f3f2bb
small cleanup
jkurdek Apr 4, 2024
491a91e
Get nullability right in binding representation tests
jkurdek Apr 5, 2024
1bf4cad
Fix path parse to work with improved tests
jkurdek Apr 5, 2024
0355390
Integration tests (#14)
jkurdek Apr 8, 2024
d00f0d8
Implement setters (#16)
simonrozsival Apr 9, 2024
b4bf3e0
Simplify indexes (#18)
simonrozsival Apr 9, 2024
97721da
Fix overload check (#20)
jkurdek Apr 9, 2024
8617d5b
Implement detection of writable properties (#19)
simonrozsival Apr 9, 2024
1f733e2
Add TODOs to change arrays to equatable arrays
simonrozsival Apr 9, 2024
b375a60
Add projects to solutions
simonrozsival Apr 9, 2024
85d0a40
Try to run unit tests in CI
simonrozsival Apr 10, 2024
8708c17
Added custom indexer support
jkurdek Apr 11, 2024
78502ca
Fix typo
simonrozsival Apr 16, 2024
d2befa0
Avoid allocating line separators array
simonrozsival Apr 16, 2024
2039bb0
Hide the new SetBinding overload from editor suggestions for now
simonrozsival Apr 17, 2024
08f55d6
Incremental generation tests
jkurdek Apr 16, 2024
89dfa15
replaced array with equatable array
jkurdek Apr 19, 2024
df57581
Added source information + formatting
jkurdek Apr 19, 2024
6d6887e
added third party licenses
jkurdek Apr 19, 2024
4cb21c1
Add benchmark for source-generated SetBinding (#25)
simonrozsival Apr 23, 2024
19b6885
Improve diagnostic messages
simonrozsival Apr 23, 2024
f02cce5
Improved incrementality testing (#28)
jkurdek Apr 29, 2024
2f35e7e
Add C-Style casts support (#26)
jkurdek Apr 30, 2024
2cb89e8
Cleanup (#29)
simonrozsival Apr 30, 2024
0835449
Improved nullable support (#30)
jkurdek May 14, 2024
55e1aa2
Simplify building setter
simonrozsival May 14, 2024
44c0167
cleaned up methods in BindingSourceGeneratorUtilities
jkurdek May 17, 2024
296c5e0
replaced tuples with Result<T>
jkurdek May 17, 2024
1054597
simplified naming
jkurdek May 17, 2024
1f88a39
Fix and improve unit test project
simonrozsival Jun 4, 2024
afabedc
Fix bad conflict resolution in solution file
simonrozsival Jun 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
small cleanup
  • Loading branch information
jkurdek authored and simonrozsival committed Jun 4, 2024
commit 7f3f2bbfe8a47de3ff34ae5badeaea31af5e9914
85 changes: 54 additions & 31 deletions src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,51 +62,34 @@ static BindingDiagnosticsWrapper GetBindingForGeneration(GeneratorSyntaxContext

var method = (MemberAccessExpressionSyntax)invocation.Expression;

var methodSymbolInfo = context.SemanticModel.GetSymbolInfo(method, cancellationToken: t);
var sourceCodeLocation = new SourceCodeLocation(
context.Node.SyntaxTree.FilePath,
method.Name.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
method.Name.GetLocation().GetLineSpan().StartLinePosition.Character + 1
);

if (methodSymbolInfo.Symbol is not IMethodSymbol methodSymbol) //TODO: Do we need this check?
{
return ReportDiagnostics([DiagnosticsFactory.UnableToResolvePath(method.GetLocation())]);
}
var overloadDiagnostics = VerifyCorrectOverload(method, context, t);

// Check whether we are using correct overload
if (methodSymbol.Parameters.Length < 2 || methodSymbol.Parameters[1].Type.Name != "Func")
if (overloadDiagnostics.Length > 0)
{
return ReportDiagnostics([DiagnosticsFactory.SuboptimalSetBindingOverload(method.GetLocation())]);
return ReportDiagnostics(overloadDiagnostics);
}

var argumentList = invocation.ArgumentList.Arguments;
var getter = argumentList[1].Expression;

//Check if getter is a lambda
if (getter is not LambdaExpressionSyntax lambda)
var (lambdaBody, lambdaSymbol, lambdaDiagnostics) = GetLambda(invocation, context.SemanticModel);

if (lambdaBody == null || lambdaSymbol == null || lambdaDiagnostics.Length > 0)
{
return ReportDiagnostics([DiagnosticsFactory.GetterIsNotLambda(getter.GetLocation())]);
return ReportDiagnostics(lambdaDiagnostics);
}

//Check if lambda body is an expression
if (lambda.Body is not ExpressionSyntax)
{
return ReportDiagnostics([DiagnosticsFactory.GetterLambdaBodyIsNotExpression(lambda.Body.GetLocation())]);
}

var lambdaSymbol = context.SemanticModel.GetSymbolInfo(lambda, cancellationToken: t).Symbol as IMethodSymbol ?? throw new Exception("Unable to resolve lambda symbol");

var sourceCodeLocation = new SourceCodeLocation(
context.Node.SyntaxTree.FilePath,
method.Name.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
method.Name.GetLocation().GetLineSpan().StartLinePosition.Character + 1
);

NullableContext nullableContext = context.SemanticModel.GetNullableContext(context.Node.Span.Start);
var enabledNullable = (nullableContext & NullableContext.Enabled) == NullableContext.Enabled;

var parts = new List<IPathPart>();
var correctlyParsed = PathParser.ParsePath(lambda.Body, enabledNullable, context, parts);
var correctlyParsed = PathParser.ParsePath(lambdaBody, enabledNullable, context, parts);

if (!correctlyParsed)
{
return ReportDiagnostics([DiagnosticsFactory.UnableToResolvePath(lambda.Body.GetLocation())]);
return ReportDiagnostics([DiagnosticsFactory.UnableToResolvePath(lambdaBody.GetLocation())]);
}

// Sometimes analysing just the return type of the lambda is not enough. TODO: Refactor
Expand All @@ -124,6 +107,46 @@ static BindingDiagnosticsWrapper GetBindingForGeneration(GeneratorSyntaxContext
return new BindingDiagnosticsWrapper(codeWriterBinding, diagnostics.ToArray());
}

private static Diagnostic[] VerifyCorrectOverload(SyntaxNode method, GeneratorSyntaxContext context, CancellationToken t)
{
var methodSymbolInfo = context.SemanticModel.GetSymbolInfo(method, cancellationToken: t);

if (methodSymbolInfo.Symbol is not IMethodSymbol methodSymbol) //TODO: Do we need this check?
{
return [DiagnosticsFactory.UnableToResolvePath(method.GetLocation())];
}

if (methodSymbol.Parameters.Length < 2 || methodSymbol.Parameters[1].Type.Name != "Func")
{
return [DiagnosticsFactory.SuboptimalSetBindingOverload(method.GetLocation())];
}

return Array.Empty<Diagnostic>();
}

private static (ExpressionSyntax? lambdaBodyExpression, IMethodSymbol? lambdaSymbol, Diagnostic[] diagnostics) GetLambda(InvocationExpressionSyntax invocation, SemanticModel semanticModel)
{
var argumentList = invocation.ArgumentList.Arguments;
var getter = argumentList[1].Expression;

if (getter is not LambdaExpressionSyntax lambda)
{
return (null, null, [DiagnosticsFactory.GetterIsNotLambda(getter.GetLocation())]);
}

if (lambda.Body is not ExpressionSyntax lambdaBody)
{
return (null, null, [DiagnosticsFactory.GetterLambdaBodyIsNotExpression(lambda.Body.GetLocation())]);
}

if (semanticModel.GetSymbolInfo(lambda).Symbol is not IMethodSymbol lambdaSymbol)
{
return (null, null, [DiagnosticsFactory.GetterIsNotLambda(lambda.GetLocation())]);
}

return (lambdaBody, lambdaSymbol, Array.Empty<Diagnostic>());
}

private static BindingDiagnosticsWrapper ReportDiagnostics(Diagnostic[] diagnostics) => new(null, diagnostics);
}

Expand Down