88using System . Linq ;
99using System . Threading ;
1010using System . Threading . Tasks ;
11+ using Microsoft . CodeAnalysis . Editing ;
1112using Microsoft . CodeAnalysis . Host ;
1213using Microsoft . CodeAnalysis . LanguageService ;
1314using Microsoft . CodeAnalysis . PooledObjects ;
14- using Microsoft . CodeAnalysis . Shared . Collections ;
1515using Microsoft . CodeAnalysis . Shared . Extensions ;
1616using Microsoft . CodeAnalysis . Shared . Utilities ;
1717using Roslyn . Utilities ;
@@ -20,6 +20,8 @@ namespace Microsoft.CodeAnalysis.QuickInfo;
2020
2121internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInfoProvider
2222{
23+ private static readonly SyntaxAnnotation s_annotation = new ( ) ;
24+
2325 protected override async Task < QuickInfoItem ? > BuildQuickInfoAsync (
2426 QuickInfoContext context , SyntaxToken token )
2527 {
@@ -188,7 +190,77 @@ protected static Task<QuickInfoItem> CreateContentAsync(
188190
189191 protected virtual ( NullableAnnotation , NullableFlowState ) GetNullabilityAnalysis ( SemanticModel semanticModel , ISymbol symbol , SyntaxNode node , CancellationToken cancellationToken ) => default ;
190192
191- private TokenInformation BindToken (
193+ private ( NullableAnnotation , NullableFlowState ) GetNullabilityAnalysis (
194+ SolutionServices services , SemanticModel semanticModel , ISymbol symbol , SyntaxToken token , CancellationToken cancellationToken )
195+ {
196+ var languageServices = services . GetLanguageServices ( semanticModel . Language ) ;
197+ var syntaxFacts = languageServices . GetRequiredService < ISyntaxFactsService > ( ) ;
198+
199+ var bindableParent = syntaxFacts . TryGetBindableParent ( token ) ;
200+ if ( bindableParent is null )
201+ return default ;
202+
203+ return TryGetNullabilityAnalysisForSuppressedExpression ( out var analysis )
204+ ? analysis
205+ : GetNullabilityAnalysis ( semanticModel , symbol , bindableParent , cancellationToken ) ;
206+
207+ bool TryGetNullabilityAnalysisForSuppressedExpression ( out ( NullableAnnotation , NullableFlowState ) analysis )
208+ {
209+ analysis = default ;
210+
211+ // Look to see if we're inside a suppression (e.g. `expr!`). The suppression changes the nullability analysis,
212+ // and we don't actually want that here as we want to show the original nullability prior to the suppression applying.
213+ //
214+ // In that case, actually fork the semantic model with the `!` removed and then re-bind the token, getting the
215+ // analysis results from that.
216+ var tokenParent = token . GetRequiredParent ( ) ;
217+ var parentSuppression = GetOuterSuppression ( tokenParent ) ;
218+ if ( parentSuppression is null )
219+ return false ;
220+
221+ var root = semanticModel . SyntaxTree . GetRoot ( cancellationToken ) ;
222+
223+ var editor = new SyntaxEditor ( root , services ) ;
224+ // First, mark the token, so we can find it later.
225+ editor . ReplaceNode (
226+ tokenParent , tokenParent . ReplaceToken ( token , token . WithAdditionalAnnotations ( s_annotation ) ) ) ;
227+
228+ // Now walk upwards, removing all the suppressions until we hit the top of the suppression chain.
229+ for ( var currentSuppression = parentSuppression ;
230+ currentSuppression is not null ;
231+ currentSuppression = GetOuterSuppression ( currentSuppression ) )
232+ {
233+ editor . ReplaceNode (
234+ currentSuppression ,
235+ ( current , _ ) => syntaxFacts . GetOperandOfPostfixUnaryExpression ( current ) ) ;
236+ }
237+
238+ // Now fork the semantic model with the new root that has the suppressions removed.
239+ var newRoot = editor . GetChangedRoot ( ) ;
240+
241+ var newTree = semanticModel . SyntaxTree . WithRootAndOptions ( newRoot , semanticModel . SyntaxTree . Options ) ;
242+ var newToken = newTree . GetRoot ( cancellationToken ) . GetAnnotatedTokens ( s_annotation ) . Single ( ) ;
243+
244+ var newBindableParent = syntaxFacts . TryGetBindableParent ( newToken ) ;
245+ if ( newBindableParent is null )
246+ return false ;
247+
248+ var newCompilation = semanticModel . Compilation . ReplaceSyntaxTree ( semanticModel . SyntaxTree , newTree ) ;
249+ semanticModel = newCompilation . GetSemanticModel ( newTree ) ;
250+
251+ var symbols = BindSymbols ( services , semanticModel , newToken , cancellationToken ) ;
252+ if ( symbols . IsEmpty )
253+ return false ;
254+
255+ analysis = GetNullabilityAnalysis ( semanticModel , symbols [ 0 ] , newBindableParent , cancellationToken ) ;
256+ return true ;
257+
258+ SyntaxNode ? GetOuterSuppression ( SyntaxNode node )
259+ => node . Ancestors ( ) . FirstOrDefault ( a => a . RawKind == syntaxFacts . SyntaxKinds . SuppressNullableWarningExpression ) ;
260+ }
261+ }
262+
263+ protected ImmutableArray < ISymbol > BindSymbols (
192264 SolutionServices services , SemanticModel semanticModel , SyntaxToken token , CancellationToken cancellationToken )
193265 {
194266 var languageServices = services . GetLanguageServices ( semanticModel . Language ) ;
@@ -203,14 +275,39 @@ private TokenInformation BindToken(
203275 AddSymbols ( GetSymbolsFromToken ( token , services , semanticModel , cancellationToken ) , checkAccessibility : true ) ;
204276 AddSymbols ( bindableParent != null ? semanticModel . GetMemberGroup ( bindableParent , cancellationToken ) : [ ] , checkAccessibility : false ) ;
205277
278+ return filteredSymbols . ToImmutableAndClear ( ) ;
279+
280+ void AddSymbols ( ImmutableArray < ISymbol > symbols , bool checkAccessibility )
281+ {
282+ foreach ( var symbol in symbols )
283+ {
284+ if ( ! IsOk ( symbol ) )
285+ continue ;
286+
287+ if ( checkAccessibility && ! IsAccessible ( symbol , enclosingType ) )
288+ continue ;
289+
290+ if ( symbolSet . Add ( symbol ) )
291+ filteredSymbols . Add ( symbol ) ;
292+ }
293+ }
294+ }
295+
296+ private TokenInformation BindToken (
297+ SolutionServices services , SemanticModel semanticModel , SyntaxToken token , CancellationToken cancellationToken )
298+ {
299+ var filteredSymbols = BindSymbols ( services , semanticModel , token , cancellationToken ) ;
300+
301+ var languageServices = services . GetLanguageServices ( semanticModel . Language ) ;
302+ var syntaxFacts = languageServices . GetRequiredService < ISyntaxFactsService > ( ) ;
303+
206304 if ( filteredSymbols is [ var firstSymbol , ..] )
207305 {
208306 var isAwait = syntaxFacts . IsAwaitKeyword ( token ) ;
209- var nullabilityInfo = bindableParent != null
210- ? GetNullabilityAnalysis ( semanticModel , firstSymbol , bindableParent , cancellationToken )
211- : default ;
307+ var nullabilityInfo = GetNullabilityAnalysis (
308+ services , semanticModel , firstSymbol , token , cancellationToken ) ;
212309
213- return new TokenInformation ( filteredSymbols . ToImmutableAndClear ( ) , isAwait , nullabilityInfo ) ;
310+ return new TokenInformation ( filteredSymbols , isAwait , nullabilityInfo ) ;
214311 }
215312
216313 // Couldn't bind the token to specific symbols. If it's an operator, see if we can at
@@ -223,21 +320,6 @@ private TokenInformation BindToken(
223320 }
224321
225322 return default ;
226-
227- void AddSymbols ( ImmutableArray < ISymbol > symbols , bool checkAccessibility )
228- {
229- foreach ( var symbol in symbols )
230- {
231- if ( ! IsOk ( symbol ) )
232- continue ;
233-
234- if ( checkAccessibility && ! IsAccessible ( symbol , enclosingType ) )
235- continue ;
236-
237- if ( symbolSet . Add ( symbol ) )
238- filteredSymbols . Add ( symbol ) ;
239- }
240- }
241323 }
242324
243325 private ImmutableArray < ISymbol > GetSymbolsFromToken ( SyntaxToken token , SolutionServices services , SemanticModel semanticModel , CancellationToken cancellationToken )
0 commit comments