-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[release/8.0-rc1] Use Roslyn interceptors feature in binder gen #90835
[release/8.0-rc1] Use Roslyn interceptors feature in binder gen #90835
Conversation
Tagging subscribers to this area: @dotnet/area-extensions-configuration Issue DetailsBackport of #90340 to release/8.0-rc1 /cc @layomia Customer ImpactTestingRiskIMPORTANT: If this backport is for a servicing release, please verify that:
|
@carlossanlop apologies about your comment; just filled out the template. |
I might need to manually adjust the PR pending any issues with adopting the new VS code analysis version. |
😭 What did I do to hurt you. |
74d22e3
to
4596e82
Compare
...s.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj
Outdated
Show resolved
Hide resolved
Co-authored-by: Eric StJohn <[email protected]>
Chatted with @layomia, this is ready to merge. |
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.7.0-3.23314.3" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" PrivateAssets="all" /> | ||
<!-- if MicrosoftCodeAnalysisVersion_LatestVS is still at 4.5.0 (eg: not source build) then update to a newer version. Remove this when we are able to update the value of MicrosoftCodeAnalysisVersion_LatestVS --> | ||
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Condition="'$(MicrosoftCodeAnalysisVersion_LatestVS)' == '4.5.0'" Version="4.7.0-3.23314.3" /> |
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.
This isn't going to work for source-build. This value is going to be the current version source-built during the product source-build, therefore this is going to trigger a downgrade and introduce a prebuilt.
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.
My mistake in interpreting what is going to happen here. SB is going to set MicrosoftCodeAnalysisVersion_LatestVS to the current version therefore the conditional setting of the PackageReference to 4.7.0... won't happen.
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.
I think this will work for source-build.
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.
If this feature remains in the release, we could port #91006 to RC2 or 1, as needed.
@@ -73,7 +69,11 @@ private sealed record CompilationData | |||
|
|||
public CompilationData(CSharpCompilation compilation) | |||
{ | |||
LanguageVersionIsSupported = compilation.LanguageVersion >= LanguageVersion.CSharp11; | |||
// We don't have a CSharp21 value available yet. Polyfill the value here for forward compat, rather than use the LangugeVersion.Preview enum value. |
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.
Did you mean CSharp12?
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, will fix if this feature stays in the release.
@@ -127,12 +134,11 @@ void EmitObjectInit(string objExpression, InitializationKind initKind) | |||
} | |||
else if (typeKind is StringParsableTypeKind.Enum) | |||
{ | |||
parsedValueExpr = $"ParseEnum<{type.MinimalDisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})"; | |||
parsedValueExpr = $"ParseEnum<{type.DisplayString}>({stringValueToParse_Expr}, () => {sectionPathExpr})"; |
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.
When emitting lambdas in generated code consider using fully specified signatures: makes the code less subject to interference and reduces build / IDE analysis time.
} | ||
else | ||
{ | ||
string helperMethodDisplayString = GetHelperMethodDisplayString(type.ParseMethodName); | ||
parsedValueExpr = $"{helperMethodDisplayString}({stringValueToParse_Expr}, () => {sectionPathExpr})"; | ||
parsedValueExpr = $"{type.ParseMethodName}({stringValueToParse_Expr}, () => {sectionPathExpr})"; |
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.
When emitting lambdas in generated code consider using fully specified signatures: makes the code less subject to interference and reduces build / IDE analysis time.
} | ||
} | ||
|
||
internal sealed record OverloadInterceptorInfo : IEnumerable<KeyValuePair<TypeSpec, List<InterceptorLocationInfo>>> |
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.
Not sure that record
is buying you anything here. The Dictionary<>
type has reference equality semantics and every instance news up a new instance. This means even logically equivalent instances are still going not be .Equals
resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath; | ||
} | ||
|
||
internal sealed record ConfigurationBinderInterceptorInfo |
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.
The record
usage isn't giving you anything here because the fields are not value equatable. Yes they are record
types but their implementation uses ref equality.
@@ -1,21 +1,25 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. | |||
// The .NET Foundation licenses this file to you under the MIT license. | |||
|
|||
using System; | |||
using System.Collections.Generic; | |||
|
|||
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration | |||
{ | |||
internal sealed record SourceGenerationSpec |
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.
The record
usage isn't buying you anything here because all of the fields have ref equality semantics and a new instance is created every time.
@@ -147,7 +149,7 @@ private static bool IsValidRootConfigType(ITypeSymbol? type) | |||
{ | |||
// List<string> is used in generated code as a temp holder for formatting | |||
// an error for config properties that don't map to object properties. | |||
_sourceGenSpec.TypeNamespaces.Add("System.Collections.Generic"); | |||
_sourceGenSpec.Namespaces.Add("System.Collections.Generic"); |
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.
The record
type InvocationDiagnosticInfo
at the start of this file also has broken equality semantics. Can't comment on it directly.
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.
Thanks, I have pending 9.0 work to fix this #89587.
@@ -42,9 +41,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) | |||
context.RegisterSourceOutput(inputData, (spc, source) => Execute(source.Item1, source.Item2, spc)); |
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.
The equals implementation on BinderInvocation
uses ref equality, not value equality. That means the CreateSyntaxProvider
call above is never cached. This is an issue for IDE performance.
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.
I'd say this fact pre-dates this PR, so I'll address outside of it, for RC2/GA if necessary - #83534 (comment).
We've made related perf fixes, with metrics indicating that IDE perf is at par with other generators #90746 #89266 wrt average time spent in the generator, testing with the Roslyn repo as a "large" solution. So I believe we're fine for GA.
Further constraining candidate invocations in the initial shallow check for valid calls to intercept might be impactful & we could pull it into the release if necessary - #90687.
Here's the effort to make the generator fully incremental #89587. I don't think we should try to squeeze it into this release unless it's the only effective way to mitigate user feedback that invalidates these measurements.
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.
with metrics indicating that IDE perf is at par with other generators
Do these benchmarks measure incremental performance?
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.
If the most important aspect is to exercise the parsing codepaths multiple times as a result of multiple edits, then yes.
I enabled the generator in all the projects in the Roslyn solution and made repeated edits in a few of them. The perf numbers reflect the aggregate of this activity. The generator parses all invocations but doesn't emit anything (these projects don't do binding). I consider this a sanity-check stress test (i.e in a large solution) that the enabled generator isn't generally detrimental to IDE perf. A more probable instance of this scenario is an AOT'd Web SDK app (where both RDG and config binding generators are enabled by default) that doesn't do any config binding.
The test doesn't cover the current behavior of re-emitting logic in projects that actually perform binding operations. In common cases there are only a few invocations and target config types, and I estimate that emitter logic is far less expensive than the parser's. I'll take measurements to see if we need any improvement for this scenario in RC 2.
Ultimately we've described workarounds for IDE perf issues here #83534 (comment). We can could document them in release notes if needed.
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.
Is this generator alwasy on or only conditionally on?
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.
It's conditionally on -- on by default in NativeAOT web sdk apps, otherwise off.
Backport of #90340 to release/8.0-rc1
/cc @layomia
Background
Uses the interceptors compiler feature, over the current approach of generating code in the global namespace. This is recommended by the Roslyn team. The request delegate generator has already adopted the approach and we'd like to have consistency. This would also provide good feedback for the compiler team.
Customer Impact (assuming generator is enabled, otherwise none)
If publishing with AOT in a web SDK project, the user should not perceive any change. Otherwise, the user will have to add
<Features>$(Features);InterceptorsPreview</Features>
to their project. Failing to do so would result in a compiler error if the there are binding calls that trigger generation. The error message will direct the user on how to fix it.Testing
Automated and manual tests provide coverage of the modified code-paths.
Risk
Moderate but isolated to the modified off-by-default generator. This is a feature-level change, so it's possible that we might have a regression, but we'll continue validating the scenarios for RC 2 and address any customer feedback.