From 6994c9147e11d364404d5fece0c7b311c7e7e9e6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 29 Oct 2020 16:19:13 +1100 Subject: [PATCH 01/39] Add failing tests --- .../EditAndContinue/TopLevelEditingTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 5e5f582f99a58..1d6b1e0017634 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3849,6 +3849,51 @@ public void MethodUpdate_LambdaParameterRefnessInBody() "Update [public void M(int a) { f((ref int b) => b = 1); }]@10 -> [public void M(int a) { f((out int b) => b = 1); }]@10"); } + [Fact] + public void MethodUpdate_LocalFunctionAddAttribute() + { + var src1 = "class C { void M() { void L() { } } }"; + var src2 = "class C { void M() { [A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionRemoveAttribute() + { + var src1 = "class C { void M() { [A]void L() { } } }"; + var src2 = "class C { void M() { void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionReorderAttribute() + { + var src1 = "class C { void M() { [A, B]void L() { } } }"; + var src2 = "class C { void M() { [B, A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + } + [Fact] public void Method_ReadOnlyRef_Parameter_InsertWhole() { From 92b44bd0eb7ef81225be556906ce7fd07429dde6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 29 Oct 2020 16:19:44 +1100 Subject: [PATCH 02/39] Add rude edit kind and string resources --- .../EditAndContinue/EditAndContinueDiagnosticDescriptors.cs | 1 + src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs | 2 ++ src/Features/Core/Portable/FeaturesResources.resx | 3 +++ src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.de.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.es.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.it.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf | 5 +++++ src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf | 5 +++++ 16 files changed, 71 insertions(+) diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 0de45612b0b61..ef546e70b58b1 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -117,6 +117,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.ChangingCapturedVariableScope, nameof(FeaturesResources.Changing_the_declaration_scope_of_a_captured_variable_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingLambdaParameters, nameof(FeaturesResources.Changing_the_parameters_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingLambdaReturnType, nameof(FeaturesResources.Changing_the_return_type_of_0_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ChangingLambdaAttributes, nameof(FeaturesResources.Changing_the_attributes_defined_on_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_type_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.AccessingCapturedVariableInLambda, nameof(FeaturesResources.Accessing_captured_variable_0_that_hasn_t_been_accessed_before_in_1_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.NotAccessingCapturedVariableInLambda, nameof(FeaturesResources.Ceasing_to_access_captured_variable_0_in_1_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 065ad8bcfc229..17f39286bb379 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -116,5 +116,7 @@ internal enum RudeEditKind : ushort MemberBodyInternalError = 88, SourceFileTooBig = 89, MemberBodyTooBig = 90, + + ChangingLambdaAttributes = 91, } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index c1b0d7effde7f..5cf6f45980001 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2810,4 +2810,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 7d1f3ec2fd18c..6b7c16d0611fe 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -205,6 +205,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Pokud se změní {0} na {1}, relace ladění nebude moct pokračovat, protože dojde ke změně tvaru stavového počítače. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Nakonfigurovat styl kódu {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 4df30eb58a8b7..0761a51f39848 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -205,6 +205,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Die Änderung von "{0}" in "{1}" verhindert, dass die Debugsitzung fortgesetzt wird, weil sich dadurch die Form des Zustandsautomaten verändert. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Codeformat "{0}" konfigurieren diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index c7ca094d767a7..6b370a32c59bb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -205,6 +205,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Si se cambia "{0}" a "{1}", la sesión de depuración no podrá continuar porque cambia la forma de la máquina de estados. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Configurar el estilo de código de {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 7faeeaa9ef1c9..e818de6fe2350 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -205,6 +205,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Le remplacement de '{0}' par '{1}' va empêcher la session de débogage de se poursuivre, car cela change la forme de la machine à états. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Configurer le style de code {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 612c22ab46d56..5d206397550cb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -205,6 +205,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modifica '{0}' in '{1}', la sessione di debug non potrà continuare perché modifica la forma della macchina a stati. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Configura lo stile del codice di {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index f4e04b04f2948..34405ac2f1b99 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -205,6 +205,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' から '{1}' に変更すると、ステート マシンの形状が変わるため、デバッグ セッションは続行されません。 + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style {0} コード スタイルの構成 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 11188757f9caa..601110ee25597 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -205,6 +205,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) '{1}'(으)로 변경하면 상태 시스템의 셰이프가 변경되므로 디버그 세션을 계속할 수 없습니다. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style {0} 코드 스타일 구성 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index e157598ea0b41..cdd51ede0cb5e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -205,6 +205,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana elementu „{0}” na „{1}” uniemożliwi kontynuowanie sesji debugowania, ponieważ powoduje zmianę kształtu automatu stanów. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Konfiguruj styl kodu {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 948e9fc3bb445..8b1ab6153815e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -205,6 +205,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar '{0}' para '{1}' impedirá a continuação da sessão de depuração porque altera a forma da máquina de estado. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Configurar estilo de código de {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index d993d82c8bebf..9214cece100af 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -205,6 +205,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Изменение "{0}" на "{1}" сделает продолжение сеанса отладки невозможным, так как меняет форму конечной машины. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style Настройка стиля кода {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 34eac921bec8f..9efeb84a705e2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -205,6 +205,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' öğesini '{1}' olarak değiştirmek, durum makinesinin şeklini değiştirdiğinden hata ayıklama oturumunun devam etmesini engeller. + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style {0} kod stilini yapılandır diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index c8e98d8c221ea..a3cbac0bb37f1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -205,6 +205,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将“{0}”更改为“{1}”会阻止调试会话继续执行,因为它会更改状态机的形状。 + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style 配置 {0} 代码样式 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 06f7b1b7d68f7..86184258ef1c1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -205,6 +205,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將 '{0}' 變更為 '{1}' 會變更狀態機器的圖形,進而使偵錯工作階段無法繼續。 + + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + Changing the attributes defined on '{0}' will prevent the debug session from continuing. + + Configure {0} code style 設定 {0} 程式碼樣式 From f821ebb2765df55d97d9edea329ad466f607df2e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 29 Oct 2020 16:19:53 +1100 Subject: [PATCH 03/39] Compare local function attributes --- .../EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index e1c68dc154a15..cbd129f878f97 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3742,6 +3742,10 @@ protected virtual void ReportLambdaSignatureRudeEdits( { rudeEdit = RudeEditKind.ChangingLambdaReturnType; } + else if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes())) + { + rudeEdit = RudeEditKind.ChangingLambdaAttributes; + } else { hasErrors = false; From ac100d6517d42508d24ba8a3c617cd75683f6f63 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 2 Nov 2020 15:37:55 +1100 Subject: [PATCH 04/39] Revert "Add rude edit kind and string resources" This reverts commit 92b44bd0eb7ef81225be556906ce7fd07429dde6. --- .../EditAndContinue/EditAndContinueDiagnosticDescriptors.cs | 1 - src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs | 2 -- src/Features/Core/Portable/FeaturesResources.resx | 3 --- src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.de.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.es.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.it.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf | 5 ----- src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf | 5 ----- 16 files changed, 71 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index ef546e70b58b1..0de45612b0b61 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -117,7 +117,6 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.ChangingCapturedVariableScope, nameof(FeaturesResources.Changing_the_declaration_scope_of_a_captured_variable_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingLambdaParameters, nameof(FeaturesResources.Changing_the_parameters_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingLambdaReturnType, nameof(FeaturesResources.Changing_the_return_type_of_0_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.ChangingLambdaAttributes, nameof(FeaturesResources.Changing_the_attributes_defined_on_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_type_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.AccessingCapturedVariableInLambda, nameof(FeaturesResources.Accessing_captured_variable_0_that_hasn_t_been_accessed_before_in_1_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.NotAccessingCapturedVariableInLambda, nameof(FeaturesResources.Ceasing_to_access_captured_variable_0_in_1_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 17f39286bb379..065ad8bcfc229 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -116,7 +116,5 @@ internal enum RudeEditKind : ushort MemberBodyInternalError = 88, SourceFileTooBig = 89, MemberBodyTooBig = 90, - - ChangingLambdaAttributes = 91, } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 5cf6f45980001..c1b0d7effde7f 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2810,7 +2810,4 @@ Zero-width positive lookbehind assertions are typically used at the beginning of The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 6b7c16d0611fe..7d1f3ec2fd18c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -205,11 +205,6 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Pokud se změní {0} na {1}, relace ladění nebude moct pokračovat, protože dojde ke změně tvaru stavového počítače. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Nakonfigurovat styl kódu {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 0761a51f39848..4df30eb58a8b7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -205,11 +205,6 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Die Änderung von "{0}" in "{1}" verhindert, dass die Debugsitzung fortgesetzt wird, weil sich dadurch die Form des Zustandsautomaten verändert. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Codeformat "{0}" konfigurieren diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 6b370a32c59bb..c7ca094d767a7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -205,11 +205,6 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Si se cambia "{0}" a "{1}", la sesión de depuración no podrá continuar porque cambia la forma de la máquina de estados. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Configurar el estilo de código de {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index e818de6fe2350..7faeeaa9ef1c9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -205,11 +205,6 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Le remplacement de '{0}' par '{1}' va empêcher la session de débogage de se poursuivre, car cela change la forme de la machine à états. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Configurer le style de code {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 5d206397550cb..612c22ab46d56 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -205,11 +205,6 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modifica '{0}' in '{1}', la sessione di debug non potrà continuare perché modifica la forma della macchina a stati. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Configura lo stile del codice di {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 34405ac2f1b99..f4e04b04f2948 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -205,11 +205,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' から '{1}' に変更すると、ステート マシンの形状が変わるため、デバッグ セッションは続行されません。 - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style {0} コード スタイルの構成 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 601110ee25597..11188757f9caa 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -205,11 +205,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) '{1}'(으)로 변경하면 상태 시스템의 셰이프가 변경되므로 디버그 세션을 계속할 수 없습니다. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style {0} 코드 스타일 구성 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index cdd51ede0cb5e..e157598ea0b41 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -205,11 +205,6 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana elementu „{0}” na „{1}” uniemożliwi kontynuowanie sesji debugowania, ponieważ powoduje zmianę kształtu automatu stanów. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Konfiguruj styl kodu {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 8b1ab6153815e..948e9fc3bb445 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -205,11 +205,6 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar '{0}' para '{1}' impedirá a continuação da sessão de depuração porque altera a forma da máquina de estado. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Configurar estilo de código de {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 9214cece100af..d993d82c8bebf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -205,11 +205,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Изменение "{0}" на "{1}" сделает продолжение сеанса отладки невозможным, так как меняет форму конечной машины. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style Настройка стиля кода {0} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 9efeb84a705e2..34eac921bec8f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -205,11 +205,6 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' öğesini '{1}' olarak değiştirmek, durum makinesinin şeklini değiştirdiğinden hata ayıklama oturumunun devam etmesini engeller. - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style {0} kod stilini yapılandır diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index a3cbac0bb37f1..c8e98d8c221ea 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -205,11 +205,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将“{0}”更改为“{1}”会阻止调试会话继续执行,因为它会更改状态机的形状。 - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style 配置 {0} 代码样式 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 86184258ef1c1..06f7b1b7d68f7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -205,11 +205,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將 '{0}' 變更為 '{1}' 會變更狀態機器的圖形,進而使偵錯工作階段無法繼續。 - - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - Changing the attributes defined on '{0}' will prevent the debug session from continuing. - - Configure {0} code style 設定 {0} 程式碼樣式 From 1b346363b722109ef3e217479cb3cad6bc0cd1f4 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 2 Nov 2020 15:43:48 +1100 Subject: [PATCH 05/39] Extract to separate method --- .../AbstractEditAndContinueAnalyzer.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index cbd129f878f97..3d80894c55018 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3067,6 +3067,8 @@ private void ReportLambdaAndClosureRudeEdits( // a rude edit would be reported in syntax analysis phase. RoslynDebug.Assert(newLambdaInfo.Match != null && newLambdaInfo.NewBody != null); + ReportLambdaAttributeRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, cancellationToken); + ReportLambdaSignatureRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, out var hasErrors, cancellationToken); anySignatureErrors |= hasErrors; } @@ -3708,6 +3710,37 @@ private void CalculateCapturedVariablesMaps( } } + protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, + SyntaxNode oldLambdaBody, + SemanticModel newModel, + SyntaxNode newLambdaBody, + List diagnostics, + CancellationToken cancellationToken) + { + var newLambda = GetLambda(newLambdaBody); + var oldLambda = GetLambda(oldLambdaBody); + + Debug.Assert(IsNestedFunction(newLambda) == IsNestedFunction(oldLambda)); + + // queries are analyzed separately + if (!IsNestedFunction(newLambda)) + { + return; + } + + var oldLambdaSymbol = GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken); + var newLambdaSymbol = GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken); + + if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes())) + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ChangingLambdaAttributes, + GetDiagnosticSpan(newLambda, EditKind.Update), + newLambda, + new[] { GetDisplayName(newLambda) })); + } + } + protected virtual void ReportLambdaSignatureRudeEdits( SemanticModel oldModel, SyntaxNode oldLambdaBody, @@ -3742,10 +3775,6 @@ protected virtual void ReportLambdaSignatureRudeEdits( { rudeEdit = RudeEditKind.ChangingLambdaReturnType; } - else if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes())) - { - rudeEdit = RudeEditKind.ChangingLambdaAttributes; - } else { hasErrors = false; From 84350fd0af2f895e0a4ec7d30e9ebbce33098de9 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 2 Nov 2020 15:44:03 +1100 Subject: [PATCH 06/39] Use Update edit klnd instead of a new one --- .../CSharpTest/EditAndContinue/TopLevelEditingTests.cs | 6 +++--- .../EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1d6b1e0017634..98cc12d2c339d 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3861,7 +3861,7 @@ public void MethodUpdate_LocalFunctionAddAttribute() "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); } [Fact] @@ -3876,7 +3876,7 @@ public void MethodUpdate_LocalFunctionRemoveAttribute() "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); } [Fact] @@ -3891,7 +3891,7 @@ public void MethodUpdate_LocalFunctionReorderAttribute() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingLambdaAttributes, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); } [Fact] diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 3d80894c55018..2afe3b7afe166 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3734,7 +3734,7 @@ protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes())) { diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ChangingLambdaAttributes, + RudeEditKind.Update, GetDiagnosticSpan(newLambda, EditKind.Update), newLambda, new[] { GetDisplayName(newLambda) })); From c20471eee1fdd821b92f9906e7968088b8f58da6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 2 Nov 2020 16:31:54 +1100 Subject: [PATCH 07/39] Add rude edits for local function return attributes --- .../EditAndContinue/TopLevelEditingTests.cs | 45 +++++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 3 +- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 98cc12d2c339d..8675110d389af 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3894,6 +3894,51 @@ public void MethodUpdate_LocalFunctionReorderAttribute() expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); } + [Fact] + public void MethodUpdate_LocalFunctionReturnType_AddAttribute() + { + var src1 = "class C { void M() { int L() { return 1; } } }"; + var src2 = "class C { void M() { [return: A]int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionReturnType_RemoveAttribute() + { + var src1 = "class C { void M() { [return: A]int L() { return 1; } } }"; + var src2 = "class C { void M() { int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [return: A]int L() { return 1; } }]@10 -> [void M() { int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionReturnType_ReorderAttribute() + { + var src1 = "class C { void M() { [return: A, B]int L() { return 1; } } }"; + var src2 = "class C { void M() { [return: B, A]int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + [Fact] public void Method_ReadOnlyRef_Parameter_InsertWhole() { diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 2afe3b7afe166..0ac05e7037960 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3731,7 +3731,8 @@ protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, var oldLambdaSymbol = GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken); var newLambdaSymbol = GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken); - if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes())) + if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes()) || + !oldLambdaSymbol.GetReturnTypeAttributes().SequenceEqual(newLambdaSymbol.GetReturnTypeAttributes())) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.Update, From 1d76be7e67e0e77f6567fd831d3dc8f661b6277c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 2 Nov 2020 16:41:21 +1100 Subject: [PATCH 08/39] Check local function parameter attributes --- .../EditAndContinue/TopLevelEditingTests.cs | 45 +++++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 24 +++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 8675110d389af..0a37b405bef50 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3939,6 +3939,51 @@ public void MethodUpdate_LocalFunctionReturnType_ReorderAttribute() expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); } + [Fact] + public void MethodUpdate_LocalFunctionParameter_AddAttribute() + { + var src1 = "class C { void M() { void L(int i) { } } }"; + var src2 = "class C { void M() { void L([A]int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionParameter_RemoveAttribute() + { + var src1 = "class C { void M() { void L([A]int i) { } } }"; + var src2 = "class C { void M() { void L(int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + + [Fact] + public void MethodUpdate_LocalFunctionParameter_ReorderAttribute() + { + var src1 = "class C { void M() { void L([A, B]int i) { } } }"; + var src2 = "class C { void M() { void L([B, A]int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + } + [Fact] public void Method_ReadOnlyRef_Parameter_InsertWhole() { diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 0ac05e7037960..464ecd99ad3c8 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3067,10 +3067,10 @@ private void ReportLambdaAndClosureRudeEdits( // a rude edit would be reported in syntax analysis phase. RoslynDebug.Assert(newLambdaInfo.Match != null && newLambdaInfo.NewBody != null); - ReportLambdaAttributeRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, cancellationToken); - ReportLambdaSignatureRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, out var hasErrors, cancellationToken); anySignatureErrors |= hasErrors; + + ReportLambdaAttributeRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, hasErrors, cancellationToken); } ArrayBuilder? lazyNewErroneousClauses = null; @@ -3715,6 +3715,7 @@ protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SemanticModel newModel, SyntaxNode newLambdaBody, List diagnostics, + bool hadSignatureEdits, CancellationToken cancellationToken) { var newLambda = GetLambda(newLambdaBody); @@ -3731,8 +3732,27 @@ protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, var oldLambdaSymbol = GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken); var newLambdaSymbol = GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken); + var reportDiagnostic = false; if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes()) || !oldLambdaSymbol.GetReturnTypeAttributes().SequenceEqual(newLambdaSymbol.GetReturnTypeAttributes())) + { + reportDiagnostic = true; + } + + // If the old and new signatures are the same then we are safe to compare parameter attributes more easily + if (!hadSignatureEdits) + { + for (var i = 0; i < oldLambdaSymbol.Parameters.Length; i++) + { + if (!oldLambdaSymbol.Parameters[i].GetAttributes().SequenceEqual(newLambdaSymbol.Parameters[i].GetAttributes())) + { + reportDiagnostic = true; + break; + } + } + } + + if (reportDiagnostic) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.Update, From 70306a4d173ab9716af00f1827256ed4ac28a8ba Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 4 Nov 2020 12:04:09 +1100 Subject: [PATCH 09/39] Globalize --- .../EditAndContinue/TopLevelEditingTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 0a37b405bef50..c11e03aeb28f4 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3861,7 +3861,7 @@ public void MethodUpdate_LocalFunctionAddAttribute() "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3876,7 +3876,7 @@ public void MethodUpdate_LocalFunctionRemoveAttribute() "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3891,7 +3891,7 @@ public void MethodUpdate_LocalFunctionReorderAttribute() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3906,7 +3906,7 @@ public void MethodUpdate_LocalFunctionReturnType_AddAttribute() "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3921,7 +3921,7 @@ public void MethodUpdate_LocalFunctionReturnType_RemoveAttribute() "Update [void M() { [return: A]int L() { return 1; } }]@10 -> [void M() { int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3936,7 +3936,7 @@ public void MethodUpdate_LocalFunctionReturnType_ReorderAttribute() "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3951,7 +3951,7 @@ public void MethodUpdate_LocalFunctionParameter_AddAttribute() "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3966,7 +3966,7 @@ public void MethodUpdate_LocalFunctionParameter_RemoveAttribute() "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] @@ -3981,7 +3981,7 @@ public void MethodUpdate_LocalFunctionParameter_ReorderAttribute() "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", "local function") }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } [Fact] From 7774e96d30fe1fc9abad51cb9cf7c273ad895f9d Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:15:04 +1100 Subject: [PATCH 10/39] Move tests and rename --- .../EditAndContinue/StatementEditingTests.cs | 135 ++++++++++++++++++ .../EditAndContinue/TopLevelEditingTests.cs | 135 ------------------ 2 files changed, 135 insertions(+), 135 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index f475067993d6e..795514bdf1395 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6933,6 +6933,141 @@ public void LocalFunction_RemoveAsync() Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "local", FeaturesResources.local_function)); } + [Fact] + public void LocalFunction_AddAttribute() + { + var src1 = "class C { void M() { void L() { } } }"; + var src2 = "class C { void M() { [A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_RemoveAttribute() + { + var src1 = "class C { void M() { [A]void L() { } } }"; + var src2 = "class C { void M() { void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ReorderAttribute() + { + var src1 = "class C { void M() { [A, B]void L() { } } }"; + var src2 = "class C { void M() { [B, A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ReturnType_AddAttribute() + { + var src1 = "class C { void M() { int L() { return 1; } } }"; + var src2 = "class C { void M() { [return: A]int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ReturnType_RemoveAttribute() + { + var src1 = "class C { void M() { [return: A]int L() { return 1; } } }"; + var src2 = "class C { void M() { int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [return: A]int L() { return 1; } }]@10 -> [void M() { int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ReturnType_ReorderAttribute() + { + var src1 = "class C { void M() { [return: A, B]int L() { return 1; } } }"; + var src2 = "class C { void M() { [return: B, A]int L() { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_Parameter_AddAttribute() + { + var src1 = "class C { void M() { void L(int i) { } } }"; + var src2 = "class C { void M() { void L([A]int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_Parameter_RemoveAttribute() + { + var src1 = "class C { void M() { void L([A]int i) { } } }"; + var src2 = "class C { void M() { void L(int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_Parameter_ReorderAttribute() + { + var src1 = "class C { void M() { void L([A, B]int i) { } } }"; + var src2 = "class C { void M() { void L([B, A]int i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + #endregion #region Queries diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index c11e03aeb28f4..5e5f582f99a58 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3849,141 +3849,6 @@ public void MethodUpdate_LambdaParameterRefnessInBody() "Update [public void M(int a) { f((ref int b) => b = 1); }]@10 -> [public void M(int a) { f((out int b) => b = 1); }]@10"); } - [Fact] - public void MethodUpdate_LocalFunctionAddAttribute() - { - var src1 = "class C { void M() { void L() { } } }"; - var src2 = "class C { void M() { [A]void L() { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionRemoveAttribute() - { - var src1 = "class C { void M() { [A]void L() { } } }"; - var src2 = "class C { void M() { void L() { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionReorderAttribute() - { - var src1 = "class C { void M() { [A, B]void L() { } } }"; - var src2 = "class C { void M() { [B, A]void L() { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionReturnType_AddAttribute() - { - var src1 = "class C { void M() { int L() { return 1; } } }"; - var src2 = "class C { void M() { [return: A]int L() { return 1; } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionReturnType_RemoveAttribute() - { - var src1 = "class C { void M() { [return: A]int L() { return 1; } } }"; - var src2 = "class C { void M() { int L() { return 1; } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [return: A]int L() { return 1; } }]@10 -> [void M() { int L() { return 1; } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionReturnType_ReorderAttribute() - { - var src1 = "class C { void M() { [return: A, B]int L() { return 1; } } }"; - var src2 = "class C { void M() { [return: B, A]int L() { return 1; } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionParameter_AddAttribute() - { - var src1 = "class C { void M() { void L(int i) { } } }"; - var src2 = "class C { void M() { void L([A]int i) { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionParameter_RemoveAttribute() - { - var src1 = "class C { void M() { void L([A]int i) { } } }"; - var src2 = "class C { void M() { void L(int i) { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - - [Fact] - public void MethodUpdate_LocalFunctionParameter_ReorderAttribute() - { - var src1 = "class C { void M() { void L([A, B]int i) { } } }"; - var src2 = "class C { void M() { void L([B, A]int i) { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); - - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); - } - [Fact] public void Method_ReadOnlyRef_Parameter_InsertWhole() { From 79e3583066c00131de15893ec67241a2f72a48b8 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:24:19 +1100 Subject: [PATCH 11/39] Add attribute list tests --- .../EditAndContinue/StatementEditingTests.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 795514bdf1395..151d635cdc0b7 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6978,6 +6978,66 @@ public void LocalFunction_ReorderAttribute() expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); } + [Fact] + public void LocalFunction_CombineAttributeLists() + { + var src1 = "class C { void M() { [A][B]void L() { } } }"; + var src2 = "class C { void M() { [A, B]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A][B]void L() { } }]@10 -> [void M() { [A, B]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_SplitAttributeLists() + { + var src1 = "class C { void M() { [A, B]void L() { } } }"; + var src2 = "class C { void M() { [A][B]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [A][B]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ChangeAttributeListTarget1() + { + var src1 = "class C { void M() { [return: A]void L() { } } }"; + var src2 = "class C { void M() { [A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [return: A]void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_ChangeAttributeListTarget2() + { + var src1 = "class C { void M() { [A]void L() { } } }"; + var src2 = "class C { void M() { [return: A]void L() { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { [A]void L() { } }]@10 -> [void M() { [return: A]void L() { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + } + [Fact] public void LocalFunction_ReturnType_AddAttribute() { From 7dd36d8ba2e657f1b931e527fbf98f8216062014 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:24:28 +1100 Subject: [PATCH 12/39] Reword --- .../EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 464ecd99ad3c8..0b7ea308180bf 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3739,7 +3739,8 @@ protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, reportDiagnostic = true; } - // If the old and new signatures are the same then we are safe to compare parameter attributes more easily + // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters + // so we can skip this bit if (!hadSignatureEdits) { for (var i = 0; i < oldLambdaSymbol.Parameters.Length; i++) From d3df67d048e7384fbb381e601048b46964decf01 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:25:37 +1100 Subject: [PATCH 13/39] Change visibility --- .../Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 0b7ea308180bf..d3a32ff2b8d2d 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3710,7 +3710,7 @@ private void CalculateCapturedVariablesMaps( } } - protected virtual void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, + private void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, From c864484c44740940fff842a79d4a1cdb20baab4d Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:45:12 +1100 Subject: [PATCH 14/39] Make method abstract so we can switch to syntax --- .../AbstractEditAndContinueAnalyzer.cs | 49 +------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index d3a32ff2b8d2d..772aaa4c8425d 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3710,58 +3710,13 @@ private void CalculateCapturedVariablesMaps( } } - private void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, + protected abstract void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, List diagnostics, bool hadSignatureEdits, - CancellationToken cancellationToken) - { - var newLambda = GetLambda(newLambdaBody); - var oldLambda = GetLambda(oldLambdaBody); - - Debug.Assert(IsNestedFunction(newLambda) == IsNestedFunction(oldLambda)); - - // queries are analyzed separately - if (!IsNestedFunction(newLambda)) - { - return; - } - - var oldLambdaSymbol = GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken); - var newLambdaSymbol = GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken); - - var reportDiagnostic = false; - if (!oldLambdaSymbol.GetAttributes().SequenceEqual(newLambdaSymbol.GetAttributes()) || - !oldLambdaSymbol.GetReturnTypeAttributes().SequenceEqual(newLambdaSymbol.GetReturnTypeAttributes())) - { - reportDiagnostic = true; - } - - // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters - // so we can skip this bit - if (!hadSignatureEdits) - { - for (var i = 0; i < oldLambdaSymbol.Parameters.Length; i++) - { - if (!oldLambdaSymbol.Parameters[i].GetAttributes().SequenceEqual(newLambdaSymbol.Parameters[i].GetAttributes())) - { - reportDiagnostic = true; - break; - } - } - } - - if (reportDiagnostic) - { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.Update, - GetDiagnosticSpan(newLambda, EditKind.Update), - newLambda, - new[] { GetDisplayName(newLambda) })); - } - } + CancellationToken cancellationToken); protected virtual void ReportLambdaSignatureRudeEdits( SemanticModel oldModel, From 8b8384a84097df144f3e762ac0a19ea635c6c276 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:45:43 +1100 Subject: [PATCH 15/39] Compare attribute lists for local functions and their parameters --- .../CSharpEditAndContinueAnalyzer.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index f4d5c0bf08a94..baf994f0e6c5b 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1162,6 +1162,44 @@ protected override void ReportLambdaSignatureRudeEdits( } } + protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, List diagnostics, bool hadSignatureEdits, CancellationToken cancellationToken) + { + if (IsLocalFunctionBody(oldLambdaBody) && IsLocalFunctionBody(newLambdaBody)) + { + var newLambda = (LocalFunctionStatementSyntax)GetLambda(newLambdaBody); + var oldLambda = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); + + var reportDiagnostic = false; + if (!oldLambda.AttributeLists.SequenceEqual(newLambda.AttributeLists)) + { + reportDiagnostic = true; + } + + // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters + // so we can skip this bit + if (!hadSignatureEdits) + { + for (var i = 0; i < oldLambda.ParameterList.Parameters.Count; i++) + { + if (!oldLambda.ParameterList.Parameters[i].AttributeLists.SequenceEqual(newLambda.ParameterList.Parameters[i].AttributeLists)) + { + reportDiagnostic = true; + break; + } + } + } + + if (reportDiagnostic) + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.Update, + GetDiagnosticSpan(newLambda, EditKind.Update), + newLambda, + new[] { GetDisplayName(newLambda) })); + } + } + } + private static bool IsLocalFunctionBody(SyntaxNode lambdaBody) { var lambda = LambdaUtilities.GetLambda(lambdaBody); From c4448b318cf86eeb2b97bcfae98565eb41c1172f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:45:53 +1100 Subject: [PATCH 16/39] Implement abstract method --- .../EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 7373576ea4009..e4988d1590978 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1157,6 +1157,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return True End Select End Function + + Protected Overrides Sub ReportLambdaAttributeRudeEdits(oldModel As SemanticModel, oldLambdaBody As SyntaxNode, newModel As SemanticModel, newLambdaBody As SyntaxNode, diagnostics As List(Of RudeEditDiagnostic), hadSignatureEdits As Boolean, cancellationToken As CancellationToken) + ' VB doesn't have local functions, so this is not applicable + End Sub #End Region #Region "Diagnostic Info" From 7a09aee71ac4cdfc97feebff4028c1e971a07055 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 1 Dec 2020 21:49:42 +1100 Subject: [PATCH 17/39] Doc and simplify --- .../CSharpEditAndContinueAnalyzer.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index baf994f0e6c5b..42c0b14a4351a 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1164,39 +1164,43 @@ protected override void ReportLambdaSignatureRudeEdits( protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, List diagnostics, bool hadSignatureEdits, CancellationToken cancellationToken) { - if (IsLocalFunctionBody(oldLambdaBody) && IsLocalFunctionBody(newLambdaBody)) + // Changes from lambda to local are covered by the method above, so we can just bail out here + // if both old and new aren't local functions + if (!IsLocalFunctionBody(oldLambdaBody) || !IsLocalFunctionBody(newLambdaBody)) { - var newLambda = (LocalFunctionStatementSyntax)GetLambda(newLambdaBody); - var oldLambda = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); + return; + } - var reportDiagnostic = false; - if (!oldLambda.AttributeLists.SequenceEqual(newLambda.AttributeLists)) - { - reportDiagnostic = true; - } + var newLocalFunction = (LocalFunctionStatementSyntax)GetLambda(newLambdaBody); + var oldLocalFunction = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); - // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters - // so we can skip this bit - if (!hadSignatureEdits) + var reportDiagnostic = false; + if (!oldLocalFunction.AttributeLists.SequenceEqual(newLocalFunction.AttributeLists)) + { + reportDiagnostic = true; + } + + // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters + // so we can skip this bit + if (!hadSignatureEdits) + { + for (var i = 0; i < oldLocalFunction.ParameterList.Parameters.Count; i++) { - for (var i = 0; i < oldLambda.ParameterList.Parameters.Count; i++) + if (!oldLocalFunction.ParameterList.Parameters[i].AttributeLists.SequenceEqual(newLocalFunction.ParameterList.Parameters[i].AttributeLists)) { - if (!oldLambda.ParameterList.Parameters[i].AttributeLists.SequenceEqual(newLambda.ParameterList.Parameters[i].AttributeLists)) - { - reportDiagnostic = true; - break; - } + reportDiagnostic = true; + break; } } + } - if (reportDiagnostic) - { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.Update, - GetDiagnosticSpan(newLambda, EditKind.Update), - newLambda, - new[] { GetDisplayName(newLambda) })); - } + if (reportDiagnostic) + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.Update, + GetDiagnosticSpan(newLocalFunction, EditKind.Update), + newLocalFunction, + new[] { GetDisplayName(newLocalFunction) })); } } From a21cfe98de5002d649a859051591ec611ba44c08 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 2 Dec 2020 13:05:37 +1100 Subject: [PATCH 18/39] Highlight the best changed span for the user. --- .../EditAndContinue/StatementEditingTests.cs | 22 ++--- .../CSharpEditAndContinueAnalyzer.cs | 86 ++++++++++++++++--- 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 151d635cdc0b7..495f9c546433b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6945,7 +6945,7 @@ public void LocalFunction_AddAttribute() "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -6975,7 +6975,7 @@ public void LocalFunction_ReorderAttribute() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); } [Fact] @@ -6990,7 +6990,7 @@ public void LocalFunction_CombineAttributeLists() "Update [void M() { [A][B]void L() { } }]@10 -> [void M() { [A, B]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A, B]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7005,7 +7005,7 @@ public void LocalFunction_SplitAttributeLists() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [A][B]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7020,7 +7020,7 @@ public void LocalFunction_ChangeAttributeListTarget1() "Update [void M() { [return: A]void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7035,7 +7035,7 @@ public void LocalFunction_ChangeAttributeListTarget2() "Update [void M() { [A]void L() { } }]@10 -> [void M() { [return: A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7050,7 +7050,7 @@ public void LocalFunction_ReturnType_AddAttribute() "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[return: A]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7080,7 +7080,7 @@ public void LocalFunction_ReturnType_ReorderAttribute() "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7095,7 +7095,7 @@ public void LocalFunction_Parameter_AddAttribute() "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7110,7 +7110,7 @@ public void LocalFunction_Parameter_RemoveAttribute() "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "int i", CSharpFeaturesResources.local_function) }); } [Fact] @@ -7125,7 +7125,7 @@ public void LocalFunction_Parameter_ReorderAttribute() "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); } #endregion diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 42c0b14a4351a..f0af66e47bda3 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1175,7 +1175,7 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S var oldLocalFunction = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); var reportDiagnostic = false; - if (!oldLocalFunction.AttributeLists.SequenceEqual(newLocalFunction.AttributeLists)) + if (!AreEquivalentAttributeLists(oldLocalFunction.AttributeLists, newLocalFunction.AttributeLists, out var diagnosticNode)) { reportDiagnostic = true; } @@ -1186,8 +1186,10 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S { for (var i = 0; i < oldLocalFunction.ParameterList.Parameters.Count; i++) { - if (!oldLocalFunction.ParameterList.Parameters[i].AttributeLists.SequenceEqual(newLocalFunction.ParameterList.Parameters[i].AttributeLists)) + if (!AreEquivalentAttributeLists(oldLocalFunction.ParameterList.Parameters[i].AttributeLists, newLocalFunction.ParameterList.Parameters[i].AttributeLists, out var squiggleNode)) { + // if the comparison didn't give us a node to highlight, then highlight the parameter + diagnosticNode = squiggleNode ?? newLocalFunction.ParameterList.Parameters[i]; reportDiagnostic = true; break; } @@ -1198,10 +1200,77 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.Update, - GetDiagnosticSpan(newLocalFunction, EditKind.Update), + GetDiagnosticSpan(diagnosticNode ?? newLocalFunction, EditKind.Update), newLocalFunction, new[] { GetDisplayName(newLocalFunction) })); } + + } + + private static bool AreEquivalentAttributeLists(SyntaxList oldAttributeLists, SyntaxList newAttributeLists, out SyntaxNode? squiggleNode) + { + if (oldAttributeLists.Count != newAttributeLists.Count) + { + if (newAttributeLists.Count > 0) + { + squiggleNode = newAttributeLists[0]; + } + else + { + // If they removed the attributes just let the lambda itself be highlighted + squiggleNode = null; + } + + return false; + } + + for (var i = 0; i < oldAttributeLists.Count; i++) + { + if (!AreEquivalentAttributeList(oldAttributeLists[i], newAttributeLists[i], out squiggleNode)) + { + return false; + } + } + + squiggleNode = null; + return true; + } + + private static bool AreEquivalentAttributeList(AttributeListSyntax oldAttributeList, AttributeListSyntax newAttributeList, out SyntaxNode? squiggleNode) + { + if (!SyntaxFactory.AreEquivalent(oldAttributeList.Target, newAttributeList.Target)) + { + // if they just added a target, then highlight only that + squiggleNode = ((SyntaxNode)newAttributeList.Target) ?? newAttributeList; + + return false; + } + + if (oldAttributeList.Attributes.Count != newAttributeList.Attributes.Count) + { + if (newAttributeList.Attributes.Count > 0) + { + squiggleNode = newAttributeList.Attributes[0]; + } + else + { + // If they removed the attributes highlight the whole list + squiggleNode = newAttributeList; + } + return false; + } + + for (var i = 0; i < oldAttributeList.Attributes.Count; i++) + { + if (!SyntaxFactory.AreEquivalent(oldAttributeList.Attributes[i], newAttributeList.Attributes[i])) + { + squiggleNode = newAttributeList.Attributes[i]; + return false; + } + } + + squiggleNode = null; + return true; } private static bool IsLocalFunctionBody(SyntaxNode lambdaBody) @@ -1385,14 +1454,7 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.AttributeList: var attributeList = (AttributeListSyntax)node; - if (editKind == EditKind.Update) - { - return (attributeList.Target != null) ? attributeList.Target.Span : attributeList.Span; - } - else - { - return attributeList.Span; - } + return attributeList.Span; case SyntaxKind.Attribute: return node.Span; @@ -2911,7 +2973,7 @@ private void ClassifyUpdate(AttributeListSyntax oldNode, AttributeListSyntax new { if (!SyntaxFactory.AreEquivalent(oldNode.Target, newNode.Target)) { - ReportError(RudeEditKind.Update); + ReportError(RudeEditKind.Update, spanNode: ((SyntaxNode)newNode.Target) ?? newNode); return; } From 2e6fdebbd2213ed4cbbe2723bfb7909b4d61198f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 2 Dec 2020 16:34:42 +1100 Subject: [PATCH 19/39] Fix correctness issues --- .../EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index f0af66e47bda3..176251818fe44 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1204,7 +1204,6 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S newLocalFunction, new[] { GetDisplayName(newLocalFunction) })); } - } private static bool AreEquivalentAttributeLists(SyntaxList oldAttributeLists, SyntaxList newAttributeLists, out SyntaxNode? squiggleNode) @@ -1241,7 +1240,7 @@ private static bool AreEquivalentAttributeList(AttributeListSyntax oldAttributeL if (!SyntaxFactory.AreEquivalent(oldAttributeList.Target, newAttributeList.Target)) { // if they just added a target, then highlight only that - squiggleNode = ((SyntaxNode)newAttributeList.Target) ?? newAttributeList; + squiggleNode = ((SyntaxNode?)newAttributeList.Target) ?? newAttributeList; return false; } @@ -2973,7 +2972,7 @@ private void ClassifyUpdate(AttributeListSyntax oldNode, AttributeListSyntax new { if (!SyntaxFactory.AreEquivalent(oldNode.Target, newNode.Target)) { - ReportError(RudeEditKind.Update, spanNode: ((SyntaxNode)newNode.Target) ?? newNode); + ReportError(RudeEditKind.Update, spanNode: ((SyntaxNode?)newNode.Target) ?? newNode); return; } From da8c371e27f37b205f545152ec751f95c6b1e9fc Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 7 Dec 2020 09:27:56 +1100 Subject: [PATCH 20/39] Add failing tests for local function type parameter attributes --- .../EditAndContinue/StatementEditingTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 495f9c546433b..dadb51cda13b3 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -7128,6 +7128,51 @@ public void LocalFunction_Parameter_ReorderAttribute() expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); } + [Fact] + public void LocalFunction_TypeParameter_AddAttribute() + { + var src1 = "class C { void M() { void L(T i) { } } }"; + var src2 = "class C { void M() { void L<[A] T>(T i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L(T i) { } }]@10 -> [void M() { void L<[A] T>(T i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_TypeParameter_RemoveAttribute() + { + var src1 = "class C { void M() { void L<[A] T>(T i) { } } }"; + var src2 = "class C { void M() { void L(T i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L<[A] T>(T i) { } }]@10 -> [void M() { void L(T i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "T", CSharpFeaturesResources.local_function) }); + } + + [Fact] + public void LocalFunction_TypeParameter_ReorderAttribute() + { + var src1 = "class C { void M() { void L<[A, B] T>(T i) { } } }"; + var src2 = "class C { void M() { void L<[B, A] T>(T i) { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void M() { void L<[A, B] T>(T i) { } }]@10 -> [void M() { void L<[B, A] T>(T i) { } }]@10"); + + edits.VerifySemantics( + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); + } + #endregion #region Queries From 71fbac0d398cf7274a5a471ee20b05c5aa4c7e80 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 7 Dec 2020 09:44:04 +1100 Subject: [PATCH 21/39] Flag local function type parameter attribute changes --- .../CSharpEditAndContinueAnalyzer.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 176251818fe44..ae8867ee33e52 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1184,6 +1184,22 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S // so we can skip this bit if (!hadSignatureEdits) { + if (oldLocalFunction.TypeParameterList is not null && + // TODO: This check can be removed when https://github.com/dotnet/roslyn/issues/49822 is fixed, as the hadSignatureEdits check should fail + oldLocalFunction.TypeParameterList.Parameters.Count == newLocalFunction.TypeParameterList?.Parameters.Count) + { + for (var i = 0; i < oldLocalFunction.TypeParameterList.Parameters.Count; i++) + { + if (!AreEquivalentAttributeLists(oldLocalFunction.TypeParameterList.Parameters[i].AttributeLists, newLocalFunction.TypeParameterList.Parameters[i].AttributeLists, out var squiggleNode)) + { + // if the comparison didn't give us a node to highlight, then highlight the parameter + diagnosticNode = squiggleNode ?? newLocalFunction.TypeParameterList.Parameters[i]; + reportDiagnostic = true; + break; + } + } + } + for (var i = 0; i < oldLocalFunction.ParameterList.Parameters.Count; i++) { if (!AreEquivalentAttributeLists(oldLocalFunction.ParameterList.Parameters[i].AttributeLists, newLocalFunction.ParameterList.Parameters[i].AttributeLists, out var squiggleNode)) From 839e6fa9aca3e05c5389d8d7db88fccf343b0bad Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 7 Dec 2020 09:44:57 +1100 Subject: [PATCH 22/39] Cleanup --- .../EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index ae8867ee33e52..40532b58790ee 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1174,11 +1174,7 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S var newLocalFunction = (LocalFunctionStatementSyntax)GetLambda(newLambdaBody); var oldLocalFunction = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); - var reportDiagnostic = false; - if (!AreEquivalentAttributeLists(oldLocalFunction.AttributeLists, newLocalFunction.AttributeLists, out var diagnosticNode)) - { - reportDiagnostic = true; - } + var reportDiagnostic = !AreEquivalentAttributeLists(oldLocalFunction.AttributeLists, newLocalFunction.AttributeLists, out var diagnosticNode); // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters // so we can skip this bit From 7de67828c2699b143fd125e12d4fab0f28646cb1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 7 Dec 2020 10:05:50 +1100 Subject: [PATCH 23/39] Report better error messages on diagnostics --- .../EditAndContinue/StatementEditingTests.cs | 28 +++++++++---------- .../CSharpEditAndContinueAnalyzer.cs | 14 +++++++--- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index dadb51cda13b3..553ca6c37e067 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6945,7 +6945,7 @@ public void LocalFunction_AddAttribute() "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); } [Fact] @@ -6975,7 +6975,7 @@ public void LocalFunction_ReorderAttribute() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); } [Fact] @@ -6990,7 +6990,7 @@ public void LocalFunction_CombineAttributeLists() "Update [void M() { [A][B]void L() { } }]@10 -> [void M() { [A, B]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A, B]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A, B]", FeaturesResources.attribute) }); } [Fact] @@ -7005,7 +7005,7 @@ public void LocalFunction_SplitAttributeLists() "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [A][B]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); } [Fact] @@ -7020,7 +7020,7 @@ public void LocalFunction_ChangeAttributeListTarget1() "Update [void M() { [return: A]void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); } [Fact] @@ -7035,7 +7035,7 @@ public void LocalFunction_ChangeAttributeListTarget2() "Update [void M() { [A]void L() { } }]@10 -> [void M() { [return: A]void L() { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.attribute_target) }); } [Fact] @@ -7050,7 +7050,7 @@ public void LocalFunction_ReturnType_AddAttribute() "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[return: A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[return: A]", FeaturesResources.attribute) }); } [Fact] @@ -7080,7 +7080,7 @@ public void LocalFunction_ReturnType_ReorderAttribute() "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); } [Fact] @@ -7095,7 +7095,7 @@ public void LocalFunction_Parameter_AddAttribute() "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); } [Fact] @@ -7110,7 +7110,7 @@ public void LocalFunction_Parameter_RemoveAttribute() "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "int i", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "int i", FeaturesResources.parameter) }); } [Fact] @@ -7125,7 +7125,7 @@ public void LocalFunction_Parameter_ReorderAttribute() "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); } [Fact] @@ -7140,7 +7140,7 @@ public void LocalFunction_TypeParameter_AddAttribute() "Update [void M() { void L(T i) { } }]@10 -> [void M() { void L<[A] T>(T i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); } [Fact] @@ -7155,7 +7155,7 @@ public void LocalFunction_TypeParameter_RemoveAttribute() "Update [void M() { void L<[A] T>(T i) { } }]@10 -> [void M() { void L(T i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "T", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "T", FeaturesResources.type_parameter) }); } [Fact] @@ -7170,7 +7170,7 @@ public void LocalFunction_TypeParameter_ReorderAttribute() "Update [void M() { void L<[A, B] T>(T i) { } }]@10 -> [void M() { void L<[B, A] T>(T i) { } }]@10"); edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", CSharpFeaturesResources.local_function) }); + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); } #endregion diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 40532b58790ee..234ee7324e073 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1210,11 +1210,13 @@ protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, S if (reportDiagnostic) { + diagnosticNode ??= newLocalFunction; + diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.Update, - GetDiagnosticSpan(diagnosticNode ?? newLocalFunction, EditKind.Update), + GetDiagnosticSpan(diagnosticNode, EditKind.Update), newLocalFunction, - new[] { GetDisplayName(newLocalFunction) })); + new[] { GetDisplayName(diagnosticNode) })); } } @@ -1795,11 +1797,14 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i return FeaturesResources.parameter; case SyntaxKind.AttributeList: - return (editKind == EditKind.Update) ? CSharpFeaturesResources.attribute_target : FeaturesResources.attribute; + return FeaturesResources.attribute; case SyntaxKind.Attribute: return FeaturesResources.attribute; + case SyntaxKind.AttributeTargetSpecifier: + return CSharpFeaturesResources.attribute_target; + // statement: case SyntaxKind.TryStatement: @@ -2984,7 +2989,8 @@ private void ClassifyUpdate(AttributeListSyntax oldNode, AttributeListSyntax new { if (!SyntaxFactory.AreEquivalent(oldNode.Target, newNode.Target)) { - ReportError(RudeEditKind.Update, spanNode: ((SyntaxNode?)newNode.Target) ?? newNode); + var spanNode = ((SyntaxNode?)newNode.Target) ?? newNode; + ReportError(RudeEditKind.Update, spanNode: spanNode, displayNode: spanNode); return; } From b3798e598ab66bb391d2cc1adc6ebd5d7c138a0e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 8 Dec 2020 10:28:50 +1100 Subject: [PATCH 24/39] Add attributes and parameters to the statement comparer --- .../StatementSyntaxComparer.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs index 94393744bd7e4..a6ef7449aad11 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs @@ -221,6 +221,15 @@ internal enum Label GroupClauseLambda, // tied to parent QueryContinuation, // tied to parent + TypeParameterList, // tied to parent + TypeParameterConstraintClause, // tied to parent + TypeParameter, // tied to parent + ParameterList, // tied to parent + BracketedParameterList, // tied to parent + Parameter, // tied to parent + AttributeList, // tied to parent + Attribute, // tied to parent + // helpers: Count, Ignored = IgnoredNode @@ -255,6 +264,14 @@ private static int TiedToAncestor(Label label) case Label.CasePatternSwitchLabel: case Label.WhenClause: case Label.SwitchExpressionArm: + case Label.TypeParameterList: + case Label.TypeParameter: + case Label.TypeParameterConstraintClause: + case Label.ParameterList: + case Label.BracketedParameterList: + case Label.Parameter: + case Label.AttributeList: + case Label.Attribute: return 1; default: @@ -503,6 +520,38 @@ internal static Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLea case SyntaxKind.AwaitExpression: return Label.AwaitExpression; + case SyntaxKind.TypeParameterList: + isLeaf = false; + return Label.TypeParameterList; + + case SyntaxKind.TypeParameterConstraintClause: + isLeaf = false; + return Label.TypeParameterConstraintClause; + + case SyntaxKind.TypeParameter: + isLeaf = false; // children: attributes + return Label.TypeParameter; + + case SyntaxKind.ParameterList: + isLeaf = false; + return Label.ParameterList; + + case SyntaxKind.BracketedParameterList: + isLeaf = false; + return Label.BracketedParameterList; + + case SyntaxKind.Parameter: + isLeaf = false; // children: attributes + return Label.Parameter; + + case SyntaxKind.AttributeList: + isLeaf = false; + return Label.AttributeList; + + case SyntaxKind.Attribute: + isLeaf = true; + return Label.Attribute; + default: // any other node may contain a lambda: return Label.Ignored; @@ -685,6 +734,26 @@ protected override bool TryComputeWeightedDistance(SyntaxNode leftNode, SyntaxNo distance = ComputeWeightedDistance((SingleVariableDesignationSyntax)leftNode, (SingleVariableDesignationSyntax)rightNode); return true; + case SyntaxKind.TypeParameterConstraintClause: + distance = ComputeDistance((TypeParameterConstraintClauseSyntax)leftNode, (TypeParameterConstraintClauseSyntax)rightNode); + return true; + + case SyntaxKind.TypeParameter: + distance = ComputeDistance((TypeParameterSyntax)leftNode, (TypeParameterSyntax)rightNode); + return true; + + case SyntaxKind.Parameter: + distance = ComputeDistance((ParameterSyntax)leftNode, (ParameterSyntax)rightNode); + return true; + + case SyntaxKind.AttributeList: + distance = ComputeDistance((AttributeListSyntax)leftNode, (AttributeListSyntax)rightNode); + return true; + + case SyntaxKind.Attribute: + distance = ComputeDistance((AttributeSyntax)leftNode, (AttributeSyntax)rightNode); + return true; + default: distance = 0; return false; From 7af49ef82f6dff53db273318f5389b2574921b3e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 8 Dec 2020 10:29:15 +1100 Subject: [PATCH 25/39] Use statement comparer for local function attribute tests --- .../EditAndContinue/StatementEditingTests.cs | 192 ++++++------------ 1 file changed, 64 insertions(+), 128 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 553ca6c37e067..c0c78294ebd79 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6936,241 +6936,177 @@ public void LocalFunction_RemoveAsync() [Fact] public void LocalFunction_AddAttribute() { - var src1 = "class C { void M() { void L() { } } }"; - var src2 = "class C { void M() { [A]void L() { } } }"; + var src1 = "void L() { }"; + var src2 = "[A]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Insert [[A]]@2", "Insert [A]@3"); } [Fact] public void LocalFunction_RemoveAttribute() { - var src1 = "class C { void M() { [A]void L() { } } }"; - var src2 = "class C { void M() { void L() { } } }"; + var src1 = "[A]void L() { }"; + var src2 = "void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A]void L() { } }]@10 -> [void M() { void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + edits.VerifyEdits("Delete [[A]]@2", "Delete [A]@3"); } [Fact] public void LocalFunction_ReorderAttribute() { - var src1 = "class C { void M() { [A, B]void L() { } } }"; - var src2 = "class C { void M() { [B, A]void L() { } } }"; + var src1 = "[A, B]void L() { }"; + var src2 = "[B, A]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [B, A]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); + edits.VerifyEdits("Reorder [B]@6 -> @3"); } [Fact] public void LocalFunction_CombineAttributeLists() { - var src1 = "class C { void M() { [A][B]void L() { } } }"; - var src2 = "class C { void M() { [A, B]void L() { } } }"; + var src1 = "[A][B]void L() { }"; + var src2 = "[A, B]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A][B]void L() { } }]@10 -> [void M() { [A, B]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A, B]", FeaturesResources.attribute) }); + edits.VerifyEdits("Update [[A]]@2 -> [[A, B]]@2", "Insert [B]@6", "Delete [[B]]@5", "Delete [B]@6"); } [Fact] public void LocalFunction_SplitAttributeLists() { - var src1 = "class C { void M() { [A, B]void L() { } } }"; - var src2 = "class C { void M() { [A][B]void L() { } } }"; + var src1 = "[A, B]void L() { }"; + var src2 = "[A][B]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A, B]void L() { } }]@10 -> [void M() { [A][B]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Update [[A, B]]@2 -> [[A]]@2", "Insert [[B]]@5", "Insert [B]@6", "Delete [B]@6"); } [Fact] public void LocalFunction_ChangeAttributeListTarget1() { - var src1 = "class C { void M() { [return: A]void L() { } } }"; - var src2 = "class C { void M() { [A]void L() { } } }"; + var src1 = "[return: A]void L() { }"; + var src2 = "[A]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [return: A]void L() { } }]@10 -> [void M() { [A]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Update [[return: A]]@2 -> [[A]]@2"); } [Fact] public void LocalFunction_ChangeAttributeListTarget2() { - var src1 = "class C { void M() { [A]void L() { } } }"; - var src2 = "class C { void M() { [return: A]void L() { } } }"; + var src1 = "[A]void L() { }"; + var src2 = "[return: A]void L() { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [A]void L() { } }]@10 -> [void M() { [return: A]void L() { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.attribute_target) }); + edits.VerifyEdits("Update [[A]]@2 -> [[return: A]]@2"); } [Fact] public void LocalFunction_ReturnType_AddAttribute() { - var src1 = "class C { void M() { int L() { return 1; } } }"; - var src2 = "class C { void M() { [return: A]int L() { return 1; } } }"; + var src1 = "int L() { return 1; }"; + var src2 = "[return: A]int L() { return 1; }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { int L() { return 1; } }]@10 -> [void M() { [return: A]int L() { return 1; } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[return: A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Insert [[return: A]]@2", "Insert [A]@11"); } [Fact] public void LocalFunction_ReturnType_RemoveAttribute() { - var src1 = "class C { void M() { [return: A]int L() { return 1; } } }"; - var src2 = "class C { void M() { int L() { return 1; } } }"; + var src1 = "[return: A]int L() { return 1; }"; + var src2 = "int L() { return 1; }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [return: A]int L() { return 1; } }]@10 -> [void M() { int L() { return 1; } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "L", CSharpFeaturesResources.local_function) }); + edits.VerifyEdits("Delete [[return: A]]@2", "Delete [A]@11"); } [Fact] public void LocalFunction_ReturnType_ReorderAttribute() { - var src1 = "class C { void M() { [return: A, B]int L() { return 1; } } }"; - var src2 = "class C { void M() { [return: B, A]int L() { return 1; } } }"; + var src1 = "[return: A, B]int L() { return 1; }"; + var src2 = "[return: B, A]int L() { return 1; }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { [return: A, B]int L() { return 1; } }]@10 -> [void M() { [return: B, A]int L() { return 1; } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); + edits.VerifyEdits("Reorder [B]@14 -> @11"); } [Fact] public void LocalFunction_Parameter_AddAttribute() { - var src1 = "class C { void M() { void L(int i) { } } }"; - var src2 = "class C { void M() { void L([A]int i) { } } }"; + var src1 = "void L(int i) { }"; + var src2 = "void L([A]int i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L(int i) { } }]@10 -> [void M() { void L([A]int i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); } [Fact] public void LocalFunction_Parameter_RemoveAttribute() { - var src1 = "class C { void M() { void L([A]int i) { } } }"; - var src2 = "class C { void M() { void L(int i) { } } }"; + var src1 = "void L([A]int i) { }"; + var src2 = "void L(int i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L([A]int i) { } }]@10 -> [void M() { void L(int i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "int i", FeaturesResources.parameter) }); + edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); } [Fact] public void LocalFunction_Parameter_ReorderAttribute() { - var src1 = "class C { void M() { void L([A, B]int i) { } } }"; - var src2 = "class C { void M() { void L([B, A]int i) { } } }"; + var src1 = "void L([A, B]int i) { }"; + var src2 = "void L([B, A]int i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L([A, B]int i) { } }]@10 -> [void M() { void L([B, A]int i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); + edits.VerifyEdits("Reorder [B]@13 -> @10"); } [Fact] public void LocalFunction_TypeParameter_AddAttribute() { - var src1 = "class C { void M() { void L(T i) { } } }"; - var src2 = "class C { void M() { void L<[A] T>(T i) { } } }"; + var src1 = "void L(T i) { }"; + var src2 = "void L<[A] T>(T i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L(T i) { } }]@10 -> [void M() { void L<[A] T>(T i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute) }); + edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); } [Fact] public void LocalFunction_TypeParameter_RemoveAttribute() { - var src1 = "class C { void M() { void L<[A] T>(T i) { } } }"; - var src2 = "class C { void M() { void L(T i) { } } }"; + var src1 = "void L<[A] T>(T i) { }"; + var src2 = "void L(T i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L<[A] T>(T i) { } }]@10 -> [void M() { void L(T i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "T", FeaturesResources.type_parameter) }); + edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); } [Fact] public void LocalFunction_TypeParameter_ReorderAttribute() { - var src1 = "class C { void M() { void L<[A, B] T>(T i) { } } }"; - var src2 = "class C { void M() { void L<[B, A] T>(T i) { } } }"; + var src1 = "void L<[A, B] T>(T i) { }"; + var src2 = "void L<[B, A] T>(T i) { }"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [void M() { void L<[A, B] T>(T i) { } }]@10 -> [void M() { void L<[B, A] T>(T i) { } }]@10"); + var edits = GetMethodEdits(src1, src2); - edits.VerifySemantics( - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Update, "B", FeaturesResources.attribute) }); + edits.VerifyEdits("Reorder [B]@13 -> @10"); } #endregion From c89638f35eeadf0b0f77dd75d885756fa34b5643 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 21 Dec 2020 10:22:51 +1100 Subject: [PATCH 26/39] Change assert to if, so map is only updated when necessary --- .../AbstractEditAndContinueAnalyzer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 772aaa4c8425d..2fd96eb20bade 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -1299,12 +1299,12 @@ private BidirectionalMap ComputeMap( { // Body match of a lambda whose body is an expression has the lambda as a root. // The lambda has already been included when enumerating parent body matches. - Debug.Assert( - !map.ContainsKey(pair.Key) || - pair.Key == lambdaBodyMatch.OldRoot && pair.Value == lambdaBodyMatch.NewRoot && IsLambda(pair.Key) && IsLambda(pair.Value)); - - map[pair.Key] = pair.Value; - reverseMap[pair.Value] = pair.Key; + if (!map.ContainsKey(pair.Key) || + pair.Key == lambdaBodyMatch.OldRoot && pair.Value == lambdaBodyMatch.NewRoot && IsLambda(pair.Key) && IsLambda(pair.Value)) + { + map[pair.Key] = pair.Value; + reverseMap[pair.Value] = pair.Key; + } } } From 1022a0e6c95159809e294be06deb0320be15e8d7 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 21 Dec 2020 10:24:36 +1100 Subject: [PATCH 27/39] Update statement editing tests for new edits --- .../EditAndContinue/StatementEditingTests.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index c0c78294ebd79..e90a4e822d138 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -1841,7 +1841,10 @@ public void Lambdas_InVariableDeclarator() edits.VerifyEdits( "Update [a => a]@13 -> [(a) => a]@13", - "Update [b => b]@25 -> [b => b + 1]@27"); + "Update [b => b]@25 -> [b => b + 1]@27", + "Insert [(a)]@13", + "Insert [a]@14", + "Delete [a]@13"); } [Fact] @@ -1894,7 +1897,7 @@ public void Lambdas_InLambda_ChangeInLambdaSignature() // changes were made to the outer lambda signature: edits.VerifyEdits( - "Update [() => { G(x => y); }]@4 -> [q => { G(() => y); }]@4"); + "Update [() => { G(x => y); }]@4 -> [q => { G(() => y); }]@4", "Insert [q]@4", "Delete [()]@4"); } [Fact] @@ -1918,7 +1921,7 @@ public void Lambdas_Update_ParameterRefness_NoBodyChange() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - "Update [(ref int a) => a = 1]@4 -> [(out int a) => a = 1]@4"); + "Update [ref int a]@5 -> [out int a]@5"); } [Fact] @@ -4745,7 +4748,10 @@ public void LocalFunctions_InExpressionStatement() edits.VerifyEdits( "Update [a => a]@4 -> [int x(int a) => a + 1;]@2", "Move [a => a]@4 -> @2", - "Update [F(a => a, b => b);]@2 -> [F(b => b, x);]@25"); + "Update [F(a => a, b => b);]@2 -> [F(b => b, x);]@25", + "Insert [(int a)]@7", + "Insert [int a]@8", + "Delete [a]@4"); } [Fact] @@ -4774,7 +4780,10 @@ public void LocalFunctions_InWhile() "Update [int x(int a) => a + 1;]@28 -> [a => a]@11", "Move [int x(int a) => a + 1;]@28 -> @11", "Move [{ /*1*/ }]@5 -> @20", - "Delete [do { /*1*/ } while (F(x));]@2"); + "Insert [a]@11", + "Delete [do { /*1*/ } while (F(x));]@2", + "Delete [(int a)]@33", + "Delete [int a]@34"); } [Fact] @@ -4796,8 +4805,7 @@ public void LocalFunctions_InLocalFunction_ChangeInSignature() var edits = GetMethodEdits(src1, src2); // changes were made to the outer local function signature: - edits.VerifyEdits( - "Update [int x() { int y(int a) => a; return y(b); }]@2 -> [int x(int z) { int y() => c; return y(); }]@2"); + edits.VerifyEdits("Insert [int z]@8"); } [Fact] @@ -4809,7 +4817,9 @@ public void LocalFunctions_InLambda() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - "Update [() => { int y(int a) => a; G(y); }]@4 -> [q => { G(() => y); }]@4"); + "Update [() => { int y(int a) => a; G(y); }]@4 -> [q => { G(() => y); }]@4", + "Insert [q]@4", + "Delete [()]@4"); } [Fact] @@ -4821,7 +4831,7 @@ public void LocalFunctions_Update_ParameterRefness_NoBodyChange() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - "Update [void f(ref int a) => a = 1;]@2 -> [void f(out int a) => a = 1;]@2"); + "Update [ref int a]@9 -> [out int a]@9"); } [Fact] From ef94988eaec2606ac2347013f52da7d55d3bf5dd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 21 Dec 2020 10:41:33 +1100 Subject: [PATCH 28/39] Update statement matching tests for new matched spans --- .../EditAndContinue/StatementMatchingTests.cs | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs index 7dccdd9a83861..2ee6e85fbc390 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs @@ -225,6 +225,7 @@ public void Tuple() { "return (10, e, 22);", "return (10, e);" }, { "return (2, () => { int a = 6; return 1; });", "return (2, () => { int a = 6; return 5; });" }, { "() => { int a = 6; return 1; }", "() => { int a = 6; return 5; }" }, + { "()", "()" }, { "{ int a = 6; return 1; }", "{ int a = 6; return 5; }" }, { "int a = 6;", "int a = 6;" }, { "int a = 6", "int a = 6" }, @@ -521,6 +522,7 @@ public void ParenthesizedVariable_Update() { "a1", "a1" }, { "a2", "a3" }, { "() => { return 7; }", "() => { return 8; }" }, + { "()", "()" }, { "{ return 7; }", "{ return 8; }" }, { "return 7;", "return 8;" } }; @@ -594,6 +596,8 @@ ref int G1(int[] p) { "ref int a = ref G(new int[] { 1, 2 })", "ref int32 a = ref G1(new int[] { 1, 2 })" }, { "a = ref G(new int[] { 1, 2 })", "a = ref G1(new int[] { 1, 2 })" }, { "ref int G(int[] p) { return ref p[1]; }", "ref int G1(int[] p) { return ref p[2]; }" }, + { "(int[] p)", "(int[] p)" }, + { "int[] p", "int[] p" }, { "{ return ref p[1]; }", "{ return ref p[2]; }" }, { "return ref p[1];", "return ref p[2];" } }; @@ -642,9 +646,13 @@ public void Lambdas2a() { { "F(x => x + 1, 1, y => y + 1, delegate(int x) { return x; }, async u => u);", "F(y => y + 1, G(), x => x + 1, (int x) => x, u => u, async (u, v) => u + v);" }, { "x => x + 1", "x => x + 1" }, + { "x", "x" }, { "y => y + 1", "y => y + 1" }, + { "y", "y" }, { "delegate(int x) { return x; }", "(int x) => x" }, - { "async u => u", "async (u, v) => u + v" }, + { "(int x)", "(int x)" }, + { "int x", "int x" }, + { "async u => u", "async (u, v) => u + v" } }; expected.AssertEqual(actual); @@ -688,7 +696,8 @@ public void Lambdas3() var expected = new MatchingPairs { { "a += async u => u;", "a += u => u;" }, - { "async u => u", "u => u" } + { "async u => u", "u => u" }, + { "u", "u" } }; expected.AssertEqual(actual); @@ -722,6 +731,7 @@ public void Lambdas4() { "e = from q in a.Where(l => l > 10) select q + 1", "e = from q in a.Where(l => l < 0) select q + 1" }, { "from q in a.Where(l => l > 10)", "from q in a.Where(l => l < 0)" }, { "l => l > 10", "l => l < 0" }, + { "l", "l" }, { "select q + 1", "select q + 1" }, // select clause { "select q + 1", "select q + 1" } // query body }; @@ -746,8 +756,11 @@ public void Lambdas5() { { "F(a => b => c => d);", "F(a => b => c => d);" }, { "a => b => c => d", "a => b => c => d" }, + { "a", "a" }, { "b => c => d", "b => c => d" }, - { "c => d", "c => d" } + { "b", "b" }, + { "c => d", "c => d" }, + { "c", "c" } }; expected.AssertEqual(actual); @@ -770,8 +783,11 @@ public void Lambdas6() { { "F(a => b => c => d);", "F(a => G(b => H(c => I(d))));" }, { "a => b => c => d", "a => G(b => H(c => I(d)))" }, + { "a", "a" }, { "b => c => d", "b => H(c => I(d))" }, - { "c => d", "c => I(d)" } + { "b", "b" }, + { "c => d", "c => I(d)" }, + { "c", "c" } }; expected.AssertEqual(actual); @@ -812,18 +828,27 @@ public void Lambdas7() "F(a => { F(c => /*1*/d + 1); F((u, v) => { F((w) => c => /*2*/d + 1); F(p => p*2); }); });" }, { "a => { F(c => /*1*/d); F((u, v) => { F((w) => c => /*2*/d); F(p => p); }); }", "a => { F(c => /*1*/d + 1); F((u, v) => { F((w) => c => /*2*/d + 1); F(p => p*2); }); }" }, + { "a", "a" }, { "{ F(c => /*1*/d); F((u, v) => { F((w) => c => /*2*/d); F(p => p); }); }", "{ F(c => /*1*/d + 1); F((u, v) => { F((w) => c => /*2*/d + 1); F(p => p*2); }); }" }, { "F(c => /*1*/d);", "F(c => /*1*/d + 1);" }, { "c => /*1*/d", "c => /*1*/d + 1" }, + { "c", "c" }, { "F((u, v) => { F((w) => c => /*2*/d); F(p => p); });", "F((u, v) => { F((w) => c => /*2*/d + 1); F(p => p*2); });" }, { "(u, v) => { F((w) => c => /*2*/d); F(p => p); }", "(u, v) => { F((w) => c => /*2*/d + 1); F(p => p*2); }" }, + { "(u, v)", "(u, v)" }, + { "u", "u" }, + { "v", "v" }, { "{ F((w) => c => /*2*/d); F(p => p); }", "{ F((w) => c => /*2*/d + 1); F(p => p*2); }" }, { "F((w) => c => /*2*/d);", "F((w) => c => /*2*/d + 1);" }, { "(w) => c => /*2*/d", "(w) => c => /*2*/d + 1" }, + { "(w)", "(w)" }, + { "w", "w" }, { "c => /*2*/d", "c => /*2*/d + 1" }, + { "c", "c" }, { "F(p => p);", "F(p => p*2);" }, - { "p => p", "p => p*2" } + { "p => p", "p => p*2" }, + { "p", "p" } }; expected.AssertEqual(actual); @@ -843,7 +868,8 @@ public void LambdasInArrayType() { "var x = new int[F(a => 1)];", "var x = new int[F(a => 2)];" }, { "var x = new int[F(a => 1)]", "var x = new int[F(a => 2)]" }, { "x = new int[F(a => 1)]", "x = new int[F(a => 2)]" }, - { "a => 1", "a => 2" } + { "a => 1", "a => 2" }, + { "a", "a" } }; expected.AssertEqual(actual); @@ -863,7 +889,8 @@ public void LambdasInArrayInitializer() { "var x = new int[] { F(a => 1) };", "var x = new int[] { F(a => 2) };" }, { "var x = new int[] { F(a => 1) }", "var x = new int[] { F(a => 2) }" }, { "x = new int[] { F(a => 1) }", "x = new int[] { F(a => 2) }" }, - { "a => 1", "a => 2" } + { "a => 1", "a => 2" }, + { "a", "a" } }; expected.AssertEqual(actual); @@ -883,7 +910,8 @@ public void LambdasInStackalloc() { "var x = stackalloc int[F(a => 1)];", "var x = stackalloc int[F(a => 2)];" }, { "var x = stackalloc int[F(a => 1)]", "var x = stackalloc int[F(a => 2)]" }, { "x = stackalloc int[F(a => 1)]", "x = stackalloc int[F(a => 2)]" }, - { "a => 1", "a => 2" } + { "a => 1", "a => 2" }, + { "a", "a" } }; expected.AssertEqual(actual); @@ -904,6 +932,7 @@ public void LambdasInStackalloc_Initializer() { "var x = stackalloc[] { F(a => 1) }", "var x = stackalloc[] { F(a => 2) }" }, { "x = stackalloc[] { F(a => 1) }", "x = stackalloc[] { F(a => 2) }" }, { "a => 1", "a => 2" }, + { "a", "a" } }; expected.AssertEqual(actual); @@ -934,12 +963,18 @@ public void LocalFunctionDefinitions() var expected = new MatchingPairs { { "(int a, string c) F1(int i) { return null; }", "(int a, int b) F1(int i) { return null; }" }, + { "(int i)", "(int i)" }, + { "int i", "int i" }, { "{ return null; }", "{ return null; }" }, { "return null;", "return null;" }, { "(int a, int b) F2(int i) { return null; }", "(int a, int b, string c) F2(int i) { return null; }" }, + { "(int i)", "(int i)" }, + { "int i", "int i" }, { "{ return null; }", "{ return null; }" }, { "return null;", "return null;" }, { "(int a, int b, int c) F3(int i) { return null; }", "(int a, int b) F3(int i) { return null; }" }, + { "(int i)", "(int i)" }, + { "int i", "int i" }, { "{ return null; }", "{ return null; }" }, { "return null;", "return null;" } }; @@ -989,6 +1024,8 @@ public void LocalFunctions2a() { "x => x + 1", "int localF2(int x) => x + 1;" }, { "y => y + 1", "int localF1(int y) => y + 1;" }, { "delegate(int x) { return x; }", "int localF3(int x) => x;" }, + { "(int x)", "(int x)" }, + { "int x", "int x" }, { "async u => u", "int localF4(int u) => u;" } }; @@ -1054,15 +1091,19 @@ public void LocalFunctions4() { { "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }", "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }" }, + { "()", "()" }, { "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }", "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }" }, { "int b() { int c() { int d() { return 0; } } return c(); }", "int b() { int c() { int d() { return 0; } } return c(); }" }, + { "()", "()" }, { "{ int c() { int d() { return 0; } } return c(); }", "{ int c() { int d() { return 0; } } return c(); }" }, { "int c() { int d() { return 0; } }", "int c() { int d() { return 0; } }" }, + { "()", "()" }, { "{ int d() { return 0; } }", "{ int d() { return 0; } }" }, { "int d() { return 0; }", "int d() { return 0; }" }, + { "()", "()" }, { "{ return 0; }", "{ return 0; }" }, { "return 0;", "return 0;" }, { "return c();", "return c();" }, @@ -1120,19 +1161,30 @@ int G3(int w) { { "void G6(int a) { int G5(int c) => /*1*/d; F(G5); void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }; F(G4); }", "void G6(int a) { int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); } F(G4); }" }, + { "(int a)", "(int a)" }, + { "int a", "int a" }, { "{ int G5(int c) => /*1*/d; F(G5); void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }; F(G4); }", "{ int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); } F(G4); }" }, { "int G5(int c) => /*1*/d;", "int G5(int c) => /*1*/d + 1;" }, + { "(int c)", "(int c)" }, + { "int c", "int c" }, { "F(G5);", "F(G5);" }, { "void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }", "void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); }" }, + { "()", "()" }, { "{ void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }", "{ int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); }" }, { "void G1(int x) => x;", "int G6(int p) => p *2;" }, + { "(int x)", "(int p)" }, + { "int x", "int p" }, { "int G3(int w) { int G2(int c) => /*2*/d; return G2(w); }", "int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); }" }, + { "(int w)", "(int w)" }, + { "int w", "int w" }, { "{ int G2(int c) => /*2*/d; return G2(w); }", "{ int G2(int c) => /*2*/d + 1; return G2(w); }" }, { "int G2(int c) => /*2*/d;", "int G2(int c) => /*2*/d + 1;" }, + { "(int c)", "(int c)" }, + { "int c", "int c" }, { "return G2(w);", "return G2(w);" }, { "F(G3);", "F(G3);" }, { "F(G1);", "F(G1);" }, @@ -1154,9 +1206,11 @@ public void LocalFunctions6() var expected = new MatchingPairs { { "int f() { return local(); int local() { return 1; }}", "int f() { return local(); int local() => 2; }" }, + { "()", "()" }, { "{ return local(); int local() { return 1; }}", "{ return local(); int local() => 2; }" }, { "return local();", "return local();" }, - { "int local() { return 1; }", "int local() => 2;" } + { "int local() { return 1; }", "int local() => 2;" }, + { "()", "()" }, }; expected.AssertEqual(actual); @@ -1174,9 +1228,11 @@ public void LocalFunctions6Reverse() var expected = new MatchingPairs { { "int f() { return local(); int local() => 2; }", "int f() { return local(); int local() { return 1; }}" }, + { "()", "()" }, { "{ return local(); int local() => 2; }", "{ return local(); int local() { return 1; }}" }, { "return local();", "return local();" }, - { "int local() => 2;", "int local() { return 1; }" } + { "int local() => 2;", "int local() { return 1; }" }, + { "()", "()" }, }; expected.AssertEqual(actual); @@ -1323,12 +1379,16 @@ join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2 { "join c in await seq2 on F(u => u) equals G(s => s) into g1", "join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1" }, { "await seq2", "await seq2" }, { "u => u", "u => u + 1" }, + { "u", "u" }, { "s => s", "s => s + 3" }, + { "s", "s" }, { "into g1", "into g1" }, { "join l in await seq3 on F(v => v) equals G(t => t) into g2", "join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2" }, { "await seq3", "await seq3" }, { "v => v", "vv => vv + 2" }, + { "v", "vv" }, { "t => t", "tt => tt + 4" }, + { "t", "tt" }, { "into g2", "into g2" }, { "select a", "select a + 1" } }; @@ -1438,7 +1498,10 @@ public void ConstructorWithInitializer1() var expected = new MatchingPairs { + { "(int x = 1)", "(int x = 1)" }, + { "int x = 1", "int x = 1" }, { "a => a + 1", "a => a + 1" }, + { "a", "a" }, { "{ Console.WriteLine(1); }", "{ Console.WriteLine(1); }" }, { "Console.WriteLine(1);", "Console.WriteLine(1);" } }; @@ -1461,6 +1524,7 @@ public void ConstructorWithInitializer2() var expected = new MatchingPairs { + { "()", "()" }, { "{ Console.WriteLine(1); }", "{ Console.WriteLine(1); }" }, { "Console.WriteLine(1);", "Console.WriteLine(1);" } }; From a6002ad1b452dcea3bbee4f0db3dc6fdcff6c78e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 21 Dec 2020 11:05:35 +1100 Subject: [PATCH 29/39] Update error spans test to remove spans that are now labeled --- .../EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index d45049796eac7..28f19d104907b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -216,7 +216,6 @@ void M() /**/expr;/**/ /**/int a;/**/ F(/**/(x)/**/ => x); - F(/**/x/**/ => x); F(/**/delegate/**/(x) { }); F(from a in b /**/select/**/ a.x); F(from a in b /**/let/**/ x = expr select expr); From fd14c5a4e09fe014f243be6879c3d88752157238 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Jan 2021 16:00:36 +1100 Subject: [PATCH 30/39] Add rude edit validation to statement editing tests --- .../Helpers/EditingTestBase.cs | 17 ++-- .../EditAndContinue/StatementEditingTests.cs | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 58ba8e1aeeea3..cb5c59a20c74f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -106,13 +106,7 @@ internal static BlockSyntax MakeMethodBody( string bodySource, MethodKind kind = MethodKind.Regular) { - var source = kind switch - { - MethodKind.Iterator => "class C { IEnumerable F() { " + bodySource + " } }", - MethodKind.Async => "class C { async Task F() { " + bodySource + " } }", - MethodKind.ConstructorWithParameters => "class C { C" + bodySource + " }", - _ => "class C { void F() { " + bodySource + " } }", - }; + var source = WrapMethodBodyWithClass(bodySource, kind); var tree = ParseSource(source); var root = tree.GetRoot(); @@ -131,6 +125,15 @@ internal static BlockSyntax MakeMethodBody( return (BlockSyntax)SyntaxFactory.SyntaxTree(declaration.Body).GetRoot(); } + internal static string WrapMethodBodyWithClass(string bodySource, MethodKind kind = MethodKind.Regular) + => kind switch + { + MethodKind.Iterator => "class C { IEnumerable F() { " + bodySource + " } }", + MethodKind.Async => "class C { async Task F() { " + bodySource + " } }", + MethodKind.ConstructorWithParameters => "class C { C" + bodySource + " }", + _ => "class C { void F() { " + bodySource + " } }", + }; + internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource) => new ActiveStatementsDescription(oldSource, newSource); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index e90a4e822d138..330d084f261fd 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6952,6 +6952,11 @@ public void LocalFunction_AddAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Insert [[A]]@2", "Insert [A]@3"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -6963,6 +6968,11 @@ public void LocalFunction_RemoveAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Delete [[A]]@2", "Delete [A]@3"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } [Fact] @@ -6974,6 +6984,11 @@ public void LocalFunction_ReorderAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Reorder [B]@6 -> @3"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(); } [Fact] @@ -6985,6 +7000,13 @@ public void LocalFunction_CombineAttributeLists() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Update [[A]]@2 -> [[A, B]]@2", "Insert [B]@6", "Delete [[B]]@5", "Delete [B]@6"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "B", FeaturesResources.attribute), + Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } [Fact] @@ -6996,6 +7018,11 @@ public void LocalFunction_SplitAttributeLists() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Update [[A, B]]@2 -> [[A]]@2", "Insert [[B]]@5", "Insert [B]@6", "Delete [B]@6"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[B]", FeaturesResources.attribute)); } [Fact] @@ -7007,6 +7034,11 @@ public void LocalFunction_ChangeAttributeListTarget1() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Update [[return: A]]@2 -> [[A]]@2"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7018,6 +7050,11 @@ public void LocalFunction_ChangeAttributeListTarget2() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Update [[A]]@2 -> [[return: A]]@2"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.attribute_target)); } [Fact] @@ -7029,6 +7066,11 @@ public void LocalFunction_ReturnType_AddAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Insert [[return: A]]@2", "Insert [A]@11"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[return: A]", FeaturesResources.attribute)); } [Fact] @@ -7040,6 +7082,11 @@ public void LocalFunction_ReturnType_RemoveAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Delete [[return: A]]@2", "Delete [A]@11"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } [Fact] @@ -7051,6 +7098,11 @@ public void LocalFunction_ReturnType_ReorderAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Reorder [B]@14 -> @11"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(); } [Fact] @@ -7062,6 +7114,11 @@ public void LocalFunction_Parameter_AddAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7073,6 +7130,11 @@ public void LocalFunction_Parameter_RemoveAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "int i", FeaturesResources.attribute)); } [Fact] @@ -7084,6 +7146,11 @@ public void LocalFunction_Parameter_ReorderAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Reorder [B]@13 -> @10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(); } [Fact] @@ -7095,6 +7162,11 @@ public void LocalFunction_TypeParameter_AddAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7106,6 +7178,11 @@ public void LocalFunction_TypeParameter_RemoveAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "T", FeaturesResources.attribute)); } [Fact] @@ -7117,6 +7194,11 @@ public void LocalFunction_TypeParameter_ReorderAttribute() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits("Reorder [B]@13 -> @10"); + + // Get top edits so we can validate rude edits + edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); + + edits.VerifyRudeDiagnostics(); } #endregion From c890dca95c8973eb0e0a17ebbc01277930d8ecd9 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Jan 2021 16:29:30 +1100 Subject: [PATCH 31/39] Remove old syntax analysis code --- .../CSharpEditAndContinueAnalyzer.cs | 124 ------------------ .../AbstractEditAndContinueAnalyzer.cs | 10 -- .../VisualBasicEditAndContinueAnalyzer.vb | 3 - 3 files changed, 137 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 234ee7324e073..3cbbce842e28e 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1162,130 +1162,6 @@ protected override void ReportLambdaSignatureRudeEdits( } } - protected override void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, List diagnostics, bool hadSignatureEdits, CancellationToken cancellationToken) - { - // Changes from lambda to local are covered by the method above, so we can just bail out here - // if both old and new aren't local functions - if (!IsLocalFunctionBody(oldLambdaBody) || !IsLocalFunctionBody(newLambdaBody)) - { - return; - } - - var newLocalFunction = (LocalFunctionStatementSyntax)GetLambda(newLambdaBody); - var oldLocalFunction = (LocalFunctionStatementSyntax)GetLambda(oldLambdaBody); - - var reportDiagnostic = !AreEquivalentAttributeLists(oldLocalFunction.AttributeLists, newLocalFunction.AttributeLists, out var diagnosticNode); - - // If the old and new signatures have changed then we don't need to report rude edits for attributes on parameters - // so we can skip this bit - if (!hadSignatureEdits) - { - if (oldLocalFunction.TypeParameterList is not null && - // TODO: This check can be removed when https://github.com/dotnet/roslyn/issues/49822 is fixed, as the hadSignatureEdits check should fail - oldLocalFunction.TypeParameterList.Parameters.Count == newLocalFunction.TypeParameterList?.Parameters.Count) - { - for (var i = 0; i < oldLocalFunction.TypeParameterList.Parameters.Count; i++) - { - if (!AreEquivalentAttributeLists(oldLocalFunction.TypeParameterList.Parameters[i].AttributeLists, newLocalFunction.TypeParameterList.Parameters[i].AttributeLists, out var squiggleNode)) - { - // if the comparison didn't give us a node to highlight, then highlight the parameter - diagnosticNode = squiggleNode ?? newLocalFunction.TypeParameterList.Parameters[i]; - reportDiagnostic = true; - break; - } - } - } - - for (var i = 0; i < oldLocalFunction.ParameterList.Parameters.Count; i++) - { - if (!AreEquivalentAttributeLists(oldLocalFunction.ParameterList.Parameters[i].AttributeLists, newLocalFunction.ParameterList.Parameters[i].AttributeLists, out var squiggleNode)) - { - // if the comparison didn't give us a node to highlight, then highlight the parameter - diagnosticNode = squiggleNode ?? newLocalFunction.ParameterList.Parameters[i]; - reportDiagnostic = true; - break; - } - } - } - - if (reportDiagnostic) - { - diagnosticNode ??= newLocalFunction; - - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.Update, - GetDiagnosticSpan(diagnosticNode, EditKind.Update), - newLocalFunction, - new[] { GetDisplayName(diagnosticNode) })); - } - } - - private static bool AreEquivalentAttributeLists(SyntaxList oldAttributeLists, SyntaxList newAttributeLists, out SyntaxNode? squiggleNode) - { - if (oldAttributeLists.Count != newAttributeLists.Count) - { - if (newAttributeLists.Count > 0) - { - squiggleNode = newAttributeLists[0]; - } - else - { - // If they removed the attributes just let the lambda itself be highlighted - squiggleNode = null; - } - - return false; - } - - for (var i = 0; i < oldAttributeLists.Count; i++) - { - if (!AreEquivalentAttributeList(oldAttributeLists[i], newAttributeLists[i], out squiggleNode)) - { - return false; - } - } - - squiggleNode = null; - return true; - } - - private static bool AreEquivalentAttributeList(AttributeListSyntax oldAttributeList, AttributeListSyntax newAttributeList, out SyntaxNode? squiggleNode) - { - if (!SyntaxFactory.AreEquivalent(oldAttributeList.Target, newAttributeList.Target)) - { - // if they just added a target, then highlight only that - squiggleNode = ((SyntaxNode?)newAttributeList.Target) ?? newAttributeList; - - return false; - } - - if (oldAttributeList.Attributes.Count != newAttributeList.Attributes.Count) - { - if (newAttributeList.Attributes.Count > 0) - { - squiggleNode = newAttributeList.Attributes[0]; - } - else - { - // If they removed the attributes highlight the whole list - squiggleNode = newAttributeList; - } - return false; - } - - for (var i = 0; i < oldAttributeList.Attributes.Count; i++) - { - if (!SyntaxFactory.AreEquivalent(oldAttributeList.Attributes[i], newAttributeList.Attributes[i])) - { - squiggleNode = newAttributeList.Attributes[i]; - return false; - } - } - - squiggleNode = null; - return true; - } - private static bool IsLocalFunctionBody(SyntaxNode lambdaBody) { var lambda = LambdaUtilities.GetLambda(lambdaBody); diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 2fd96eb20bade..d2a66b2f9ff59 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3069,8 +3069,6 @@ private void ReportLambdaAndClosureRudeEdits( ReportLambdaSignatureRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, out var hasErrors, cancellationToken); anySignatureErrors |= hasErrors; - - ReportLambdaAttributeRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, hasErrors, cancellationToken); } ArrayBuilder? lazyNewErroneousClauses = null; @@ -3710,14 +3708,6 @@ private void CalculateCapturedVariablesMaps( } } - protected abstract void ReportLambdaAttributeRudeEdits(SemanticModel oldModel, - SyntaxNode oldLambdaBody, - SemanticModel newModel, - SyntaxNode newLambdaBody, - List diagnostics, - bool hadSignatureEdits, - CancellationToken cancellationToken); - protected virtual void ReportLambdaSignatureRudeEdits( SemanticModel oldModel, SyntaxNode oldLambdaBody, diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index e4988d1590978..d9a047804ba2e 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1158,9 +1158,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function - Protected Overrides Sub ReportLambdaAttributeRudeEdits(oldModel As SemanticModel, oldLambdaBody As SyntaxNode, newModel As SemanticModel, newLambdaBody As SyntaxNode, diagnostics As List(Of RudeEditDiagnostic), hadSignatureEdits As Boolean, cancellationToken As CancellationToken) - ' VB doesn't have local functions, so this is not applicable - End Sub #End Region #Region "Diagnostic Info" From b4a420ba7a365c6f4f9c76fec5201e95a538ccaa Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Jan 2021 16:30:42 +1100 Subject: [PATCH 32/39] PR feedback --- .../EditAndContinue/StatementSyntaxComparer.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs index a6ef7449aad11..ed84f8e2b635d 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs @@ -221,6 +221,7 @@ internal enum Label GroupClauseLambda, // tied to parent QueryContinuation, // tied to parent + // Below are children of local function declarations TypeParameterList, // tied to parent TypeParameterConstraintClause, // tied to parent TypeParameter, // tied to parent @@ -521,35 +522,29 @@ internal static Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLea return Label.AwaitExpression; case SyntaxKind.TypeParameterList: - isLeaf = false; return Label.TypeParameterList; case SyntaxKind.TypeParameterConstraintClause: - isLeaf = false; return Label.TypeParameterConstraintClause; case SyntaxKind.TypeParameter: - isLeaf = false; // children: attributes + // children: attributes return Label.TypeParameter; case SyntaxKind.ParameterList: - isLeaf = false; return Label.ParameterList; case SyntaxKind.BracketedParameterList: - isLeaf = false; return Label.BracketedParameterList; case SyntaxKind.Parameter: - isLeaf = false; // children: attributes + // children: attributes return Label.Parameter; case SyntaxKind.AttributeList: - isLeaf = false; return Label.AttributeList; case SyntaxKind.Attribute: - isLeaf = true; return Label.Attribute; default: From 9e2f0e41db25c1de3f97d775a6a35057411eb552 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Jan 2021 16:33:03 +1100 Subject: [PATCH 33/39] Classify top level edits and statement edits separately where necessary --- .../CSharpEditAndContinueAnalyzer.cs | 541 ++++++++++-------- 1 file changed, 294 insertions(+), 247 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 3cbbce842e28e..50a5ea64cfd6c 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1834,6 +1834,7 @@ private readonly struct EditClassifier private readonly SyntaxNode? _newNode; private readonly EditKind _kind; private readonly TextSpan? _span; + private readonly bool _isTopLevelEdit; public EditClassifier( CSharpEditAndContinueAnalyzer analyzer, @@ -1842,7 +1843,8 @@ public EditClassifier( SyntaxNode? newNode, EditKind kind, Match? match = null, - TextSpan? span = null) + TextSpan? span = null, + bool isTopLevelEdit = true) { RoslynDebug.Assert(oldNode != null || newNode != null); @@ -1856,6 +1858,7 @@ public EditClassifier( _kind = kind; _span = span; _match = match; + _isTopLevelEdit = isTopLevelEdit; } private void ReportError(RudeEditKind kind, SyntaxNode? spanNode = null, SyntaxNode? displayNode = null) @@ -1915,6 +1918,11 @@ public void ClassifyEdit() private void ClassifyMove() { + if (!_isTopLevelEdit) + { + return; + } + // We could perhaps allow moving a type declaration to a different namespace syntax node // as long as it represents semantically the same namespace as the one of the original type declaration. ReportError(RudeEditKind.Move); @@ -1922,6 +1930,11 @@ private void ClassifyMove() private void ClassifyReorder(SyntaxNode newNode) { + if (!_isTopLevelEdit) + { + return; + } + switch (newNode.Kind()) { case SyntaxKind.GlobalStatement: @@ -1985,113 +1998,125 @@ private void ClassifyReorder(SyntaxNode newNode) private void ClassifyInsert(SyntaxNode node) { + // Check edits that are rude as top level or statement edits switch (node.Kind()) { - case SyntaxKind.GlobalStatement: - // TODO: + // These are rude when added to methods, or local functions + case SyntaxKind.TypeParameter: + case SyntaxKind.TypeParameterConstraintClause: + case SyntaxKind.TypeParameterList: + case SyntaxKind.Attribute: + case SyntaxKind.AttributeList: ReportError(RudeEditKind.Insert); return; + } - case SyntaxKind.ExternAliasDirective: - case SyntaxKind.UsingDirective: - case SyntaxKind.NamespaceDeclaration: - case SyntaxKind.DestructorDeclaration: - ReportError(RudeEditKind.Insert); - return; + // Check edits that are rude specifically for top level edits + if (_isTopLevelEdit) + { + switch (node.Kind()) + { + case SyntaxKind.GlobalStatement: + // TODO: + ReportError(RudeEditKind.Insert); + return; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.StructDeclaration: - var typeDeclaration = (TypeDeclarationSyntax)node; - ClassifyTypeWithPossibleExternMembersInsert(typeDeclaration); - return; + case SyntaxKind.ExternAliasDirective: + case SyntaxKind.UsingDirective: + case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.DestructorDeclaration: + ReportError(RudeEditKind.Insert); + return; - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.DelegateDeclaration: - return; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.StructDeclaration: + var typeDeclaration = (TypeDeclarationSyntax)node; + ClassifyTypeWithPossibleExternMembersInsert(typeDeclaration); + return; - case SyntaxKind.PropertyDeclaration: - var propertyDeclaration = (PropertyDeclarationSyntax)node; - ClassifyModifiedMemberInsert(propertyDeclaration, propertyDeclaration.Modifiers); - return; + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.DelegateDeclaration: + return; - case SyntaxKind.EventDeclaration: - var eventDeclaration = (EventDeclarationSyntax)node; - ClassifyModifiedMemberInsert(eventDeclaration, eventDeclaration.Modifiers); - return; + case SyntaxKind.PropertyDeclaration: + var propertyDeclaration = (PropertyDeclarationSyntax)node; + ClassifyModifiedMemberInsert(propertyDeclaration, propertyDeclaration.Modifiers); + return; - case SyntaxKind.IndexerDeclaration: - var indexerDeclaration = (IndexerDeclarationSyntax)node; - ClassifyModifiedMemberInsert(indexerDeclaration, indexerDeclaration.Modifiers); - return; + case SyntaxKind.EventDeclaration: + var eventDeclaration = (EventDeclarationSyntax)node; + ClassifyModifiedMemberInsert(eventDeclaration, eventDeclaration.Modifiers); + return; - case SyntaxKind.ConversionOperatorDeclaration: - case SyntaxKind.OperatorDeclaration: - ReportError(RudeEditKind.InsertOperator); - return; + case SyntaxKind.IndexerDeclaration: + var indexerDeclaration = (IndexerDeclarationSyntax)node; + ClassifyModifiedMemberInsert(indexerDeclaration, indexerDeclaration.Modifiers); + return; - case SyntaxKind.MethodDeclaration: - ClassifyMethodInsert((MethodDeclarationSyntax)node); - return; + case SyntaxKind.ConversionOperatorDeclaration: + case SyntaxKind.OperatorDeclaration: + ReportError(RudeEditKind.InsertOperator); + return; - case SyntaxKind.ConstructorDeclaration: - // Allow adding parameterless constructor. - // Semantic analysis will determine if it's an actual addition or - // just an update of an existing implicit constructor. - var method = (BaseMethodDeclarationSyntax)node; - if (SyntaxUtilities.IsParameterlessConstructor(method)) - { - // Disallow adding an extern constructor - if (method.Modifiers.Any(SyntaxKind.ExternKeyword)) + case SyntaxKind.MethodDeclaration: + ClassifyMethodInsert((MethodDeclarationSyntax)node); + return; + + case SyntaxKind.ConstructorDeclaration: + // Allow adding parameterless constructor. + // Semantic analysis will determine if it's an actual addition or + // just an update of an existing implicit constructor. + var method = (BaseMethodDeclarationSyntax)node; + if (SyntaxUtilities.IsParameterlessConstructor(method)) { - ReportError(RudeEditKind.InsertExtern); + // Disallow adding an extern constructor + if (method.Modifiers.Any(SyntaxKind.ExternKeyword)) + { + ReportError(RudeEditKind.InsertExtern); + } + + return; } + ClassifyModifiedMemberInsert(method, method.Modifiers); return; - } - - ClassifyModifiedMemberInsert(method, method.Modifiers); - return; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - ClassifyAccessorInsert((AccessorDeclarationSyntax)node); - return; + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + ClassifyAccessorInsert((AccessorDeclarationSyntax)node); + return; - case SyntaxKind.AccessorList: - // an error will be reported for each accessor - return; + case SyntaxKind.AccessorList: + // an error will be reported for each accessor + return; - case SyntaxKind.FieldDeclaration: - case SyntaxKind.EventFieldDeclaration: - // allowed: private fields in classes - ClassifyFieldInsert((BaseFieldDeclarationSyntax)node); - return; + case SyntaxKind.FieldDeclaration: + case SyntaxKind.EventFieldDeclaration: + // allowed: private fields in classes + ClassifyFieldInsert((BaseFieldDeclarationSyntax)node); + return; - case SyntaxKind.VariableDeclarator: - // allowed: private fields in classes - ClassifyFieldInsert((VariableDeclaratorSyntax)node); - return; + case SyntaxKind.VariableDeclarator: + // allowed: private fields in classes + ClassifyFieldInsert((VariableDeclaratorSyntax)node); + return; - case SyntaxKind.VariableDeclaration: - // allowed: private fields in classes - ClassifyFieldInsert((VariableDeclarationSyntax)node); - return; + case SyntaxKind.VariableDeclaration: + // allowed: private fields in classes + ClassifyFieldInsert((VariableDeclarationSyntax)node); + return; - case SyntaxKind.EnumMemberDeclaration: - case SyntaxKind.TypeParameter: - case SyntaxKind.TypeParameterConstraintClause: - case SyntaxKind.TypeParameterList: - case SyntaxKind.Parameter: - case SyntaxKind.Attribute: - case SyntaxKind.AttributeList: - ReportError(RudeEditKind.Insert); - return; + case SyntaxKind.EnumMemberDeclaration: + case SyntaxKind.Parameter: + ReportError(RudeEditKind.Insert); + return; - default: - throw ExceptionUtilities.UnexpectedValue(node.Kind()); + default: + throw ExceptionUtilities.UnexpectedValue(node.Kind()); + } } } @@ -2195,98 +2220,110 @@ private void ClassifyFieldInsert(VariableDeclarationSyntax fieldVariable) private void ClassifyDelete(SyntaxNode oldNode) { + // Check edits that are rude as top level or statement edits switch (oldNode.Kind()) { - case SyntaxKind.GlobalStatement: - // TODO: + // These are rude when added to methods, or local functions + case SyntaxKind.AttributeList: + case SyntaxKind.Attribute: + // To allow removal of attributes we would need to check if the removed attribute + // is a pseudo-custom attribute that CLR allows us to change, or if it is a compiler well-know attribute + // that affects the generated IL. ReportError(RudeEditKind.Delete); return; - case SyntaxKind.ExternAliasDirective: - case SyntaxKind.UsingDirective: - case SyntaxKind.NamespaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.DelegateDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ConversionOperatorDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.DestructorDeclaration: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.IndexerDeclaration: - case SyntaxKind.EventDeclaration: - case SyntaxKind.FieldDeclaration: - case SyntaxKind.EventFieldDeclaration: - case SyntaxKind.VariableDeclarator: - case SyntaxKind.VariableDeclaration: - // To allow removal of declarations we would need to update method bodies that - // were previously binding to them but now are binding to another symbol that was previously hidden. + case SyntaxKind.TypeParameter: + case SyntaxKind.TypeParameterList: + case SyntaxKind.TypeParameterConstraintClause: ReportError(RudeEditKind.Delete); return; + } - case SyntaxKind.ConstructorDeclaration: - // Allow deletion of a parameterless constructor. - // Semantic analysis reports an error if the parameterless ctor isn't replaced by a default ctor. - if (!SyntaxUtilities.IsParameterlessConstructor(oldNode)) - { + // Check edits that are rude specifically for top level edits + if (_isTopLevelEdit) + { + switch (oldNode.Kind()) + { + case SyntaxKind.GlobalStatement: + // TODO: ReportError(RudeEditKind.Delete); - } + return; - return; + case SyntaxKind.ExternAliasDirective: + case SyntaxKind.UsingDirective: + case SyntaxKind.NamespaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.DelegateDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ConversionOperatorDeclaration: + case SyntaxKind.OperatorDeclaration: + case SyntaxKind.DestructorDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.IndexerDeclaration: + case SyntaxKind.EventDeclaration: + case SyntaxKind.FieldDeclaration: + case SyntaxKind.EventFieldDeclaration: + case SyntaxKind.VariableDeclarator: + case SyntaxKind.VariableDeclaration: + // To allow removal of declarations we would need to update method bodies that + // were previously binding to them but now are binding to another symbol that was previously hidden. + ReportError(RudeEditKind.Delete); + return; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - // An accessor can be removed. Accessors are not hiding other symbols. - // If the new compilation still uses the removed accessor a semantic error will be reported. - // For simplicity though we disallow deletion of accessors for now. - // The compiler would need to remember that the accessor has been deleted, - // so that its addition back is interpreted as an update. - // Additional issues might involve changing accessibility of the accessor. - ReportError(RudeEditKind.Delete); - return; + case SyntaxKind.ConstructorDeclaration: + // Allow deletion of a parameterless constructor. + // Semantic analysis reports an error if the parameterless ctor isn't replaced by a default ctor. + if (!SyntaxUtilities.IsParameterlessConstructor(oldNode)) + { + ReportError(RudeEditKind.Delete); + } - case SyntaxKind.AccessorList: - Debug.Assert( - oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration) || - oldNode.Parent.IsKind(SyntaxKind.IndexerDeclaration)); + return; - var accessorList = (AccessorListSyntax)oldNode; - var setter = accessorList.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)); - if (setter != null) - { - ReportError(RudeEditKind.Delete, accessorList.Parent, setter); - } + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + // An accessor can be removed. Accessors are not hiding other symbols. + // If the new compilation still uses the removed accessor a semantic error will be reported. + // For simplicity though we disallow deletion of accessors for now. + // The compiler would need to remember that the accessor has been deleted, + // so that its addition back is interpreted as an update. + // Additional issues might involve changing accessibility of the accessor. + ReportError(RudeEditKind.Delete); + return; - return; + case SyntaxKind.AccessorList: + Debug.Assert( + oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration) || + oldNode.Parent.IsKind(SyntaxKind.IndexerDeclaration)); - case SyntaxKind.AttributeList: - case SyntaxKind.Attribute: - // To allow removal of attributes we would need to check if the removed attribute - // is a pseudo-custom attribute that CLR allows us to change, or if it is a compiler well-know attribute - // that affects the generated IL. - ReportError(RudeEditKind.Delete); - return; + var accessorList = (AccessorListSyntax)oldNode; + var setter = accessorList.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)); + if (setter != null) + { + ReportError(RudeEditKind.Delete, accessorList.Parent, setter); + } - case SyntaxKind.EnumMemberDeclaration: - // We could allow removing enum member if it didn't affect the values of other enum members. - // If the updated compilation binds without errors it means that the enum value wasn't used. - ReportError(RudeEditKind.Delete); - return; + return; - case SyntaxKind.TypeParameter: - case SyntaxKind.TypeParameterList: - case SyntaxKind.Parameter: - case SyntaxKind.ParameterList: - case SyntaxKind.TypeParameterConstraintClause: - ReportError(RudeEditKind.Delete); - return; + case SyntaxKind.EnumMemberDeclaration: + // We could allow removing enum member if it didn't affect the values of other enum members. + // If the updated compilation binds without errors it means that the enum value wasn't used. + ReportError(RudeEditKind.Delete); + return; - default: - throw ExceptionUtilities.UnexpectedValue(oldNode.Kind()); + case SyntaxKind.Parameter: + case SyntaxKind.ParameterList: + ReportError(RudeEditKind.Delete); + return; + + default: + throw ExceptionUtilities.UnexpectedValue(oldNode.Kind()); + } } } @@ -2296,128 +2333,138 @@ private void ClassifyDelete(SyntaxNode oldNode) private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) { - switch (newNode.Kind()) + // Check edits that are rude as top level or statement edits + switch (oldNode.Kind()) { - case SyntaxKind.GlobalStatement: - ReportError(RudeEditKind.Update); - return; + // These are rude when added to methods, or local functions - case SyntaxKind.ExternAliasDirective: - ReportError(RudeEditKind.Update); + case SyntaxKind.AttributeList: + ClassifyUpdate((AttributeListSyntax)oldNode, (AttributeListSyntax)newNode); return; - case SyntaxKind.UsingDirective: - // Dev12 distinguishes using alias from using namespace and reports different errors for removing alias. - // None of these changes are allowed anyways, so let's keep it simple. + case SyntaxKind.Attribute: + // Dev12 reports "Rename" if the attribute type name is changed. + // But such update is actually not renaming the attribute, it's changing what attribute is applied. ReportError(RudeEditKind.Update); return; - case SyntaxKind.NamespaceDeclaration: - ClassifyUpdate((NamespaceDeclarationSyntax)oldNode, (NamespaceDeclarationSyntax)newNode); + case SyntaxKind.TypeParameterConstraintClause: + ClassifyUpdate((TypeParameterConstraintClauseSyntax)oldNode, (TypeParameterConstraintClauseSyntax)newNode); return; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.StructDeclaration: - case SyntaxKind.InterfaceDeclaration: - ClassifyUpdate((TypeDeclarationSyntax)oldNode, (TypeDeclarationSyntax)newNode); + case SyntaxKind.TypeParameter: + ClassifyUpdate((TypeParameterSyntax)oldNode, (TypeParameterSyntax)newNode); return; + } - case SyntaxKind.EnumDeclaration: - ClassifyUpdate((EnumDeclarationSyntax)oldNode, (EnumDeclarationSyntax)newNode); - return; + // Check edits that are rude specifically for top level edits + if (_isTopLevelEdit) + { + switch (newNode.Kind()) + { + case SyntaxKind.GlobalStatement: + ReportError(RudeEditKind.Update); + return; - case SyntaxKind.DelegateDeclaration: - ClassifyUpdate((DelegateDeclarationSyntax)oldNode, (DelegateDeclarationSyntax)newNode); - return; + case SyntaxKind.ExternAliasDirective: + ReportError(RudeEditKind.Update); + return; - case SyntaxKind.FieldDeclaration: - ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode); - return; + case SyntaxKind.UsingDirective: + // Dev12 distinguishes using alias from using namespace and reports different errors for removing alias. + // None of these changes are allowed anyways, so let's keep it simple. + ReportError(RudeEditKind.Update); + return; - case SyntaxKind.EventFieldDeclaration: - ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode); - return; + case SyntaxKind.NamespaceDeclaration: + ClassifyUpdate((NamespaceDeclarationSyntax)oldNode, (NamespaceDeclarationSyntax)newNode); + return; - case SyntaxKind.VariableDeclaration: - ClassifyUpdate((VariableDeclarationSyntax)oldNode, (VariableDeclarationSyntax)newNode); - return; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.StructDeclaration: + case SyntaxKind.InterfaceDeclaration: + ClassifyUpdate((TypeDeclarationSyntax)oldNode, (TypeDeclarationSyntax)newNode); + return; - case SyntaxKind.VariableDeclarator: - ClassifyUpdate((VariableDeclaratorSyntax)oldNode, (VariableDeclaratorSyntax)newNode); - return; + case SyntaxKind.EnumDeclaration: + ClassifyUpdate((EnumDeclarationSyntax)oldNode, (EnumDeclarationSyntax)newNode); + return; - case SyntaxKind.MethodDeclaration: - ClassifyUpdate((MethodDeclarationSyntax)oldNode, (MethodDeclarationSyntax)newNode); - return; + case SyntaxKind.DelegateDeclaration: + ClassifyUpdate((DelegateDeclarationSyntax)oldNode, (DelegateDeclarationSyntax)newNode); + return; - case SyntaxKind.ConversionOperatorDeclaration: - ClassifyUpdate((ConversionOperatorDeclarationSyntax)oldNode, (ConversionOperatorDeclarationSyntax)newNode); - return; + case SyntaxKind.FieldDeclaration: + ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode); + return; - case SyntaxKind.OperatorDeclaration: - ClassifyUpdate((OperatorDeclarationSyntax)oldNode, (OperatorDeclarationSyntax)newNode); - return; + case SyntaxKind.EventFieldDeclaration: + ClassifyUpdate((BaseFieldDeclarationSyntax)oldNode, (BaseFieldDeclarationSyntax)newNode); + return; - case SyntaxKind.ConstructorDeclaration: - ClassifyUpdate((ConstructorDeclarationSyntax)oldNode, (ConstructorDeclarationSyntax)newNode); - return; + case SyntaxKind.VariableDeclaration: + ClassifyUpdate((VariableDeclarationSyntax)oldNode, (VariableDeclarationSyntax)newNode); + return; - case SyntaxKind.DestructorDeclaration: - ClassifyUpdate((DestructorDeclarationSyntax)oldNode, (DestructorDeclarationSyntax)newNode); - return; + case SyntaxKind.VariableDeclarator: + ClassifyUpdate((VariableDeclaratorSyntax)oldNode, (VariableDeclaratorSyntax)newNode); + return; - case SyntaxKind.PropertyDeclaration: - ClassifyUpdate((PropertyDeclarationSyntax)oldNode, (PropertyDeclarationSyntax)newNode); - return; + case SyntaxKind.MethodDeclaration: + ClassifyUpdate((MethodDeclarationSyntax)oldNode, (MethodDeclarationSyntax)newNode); + return; - case SyntaxKind.IndexerDeclaration: - ClassifyUpdate((IndexerDeclarationSyntax)oldNode, (IndexerDeclarationSyntax)newNode); - return; + case SyntaxKind.ConversionOperatorDeclaration: + ClassifyUpdate((ConversionOperatorDeclarationSyntax)oldNode, (ConversionOperatorDeclarationSyntax)newNode); + return; - case SyntaxKind.EventDeclaration: - return; + case SyntaxKind.OperatorDeclaration: + ClassifyUpdate((OperatorDeclarationSyntax)oldNode, (OperatorDeclarationSyntax)newNode); + return; - case SyntaxKind.EnumMemberDeclaration: - ClassifyUpdate((EnumMemberDeclarationSyntax)oldNode, (EnumMemberDeclarationSyntax)newNode); - return; + case SyntaxKind.ConstructorDeclaration: + ClassifyUpdate((ConstructorDeclarationSyntax)oldNode, (ConstructorDeclarationSyntax)newNode); + return; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - case SyntaxKind.AddAccessorDeclaration: - case SyntaxKind.RemoveAccessorDeclaration: - ClassifyUpdate((AccessorDeclarationSyntax)oldNode, (AccessorDeclarationSyntax)newNode); - return; + case SyntaxKind.DestructorDeclaration: + ClassifyUpdate((DestructorDeclarationSyntax)oldNode, (DestructorDeclarationSyntax)newNode); + return; - case SyntaxKind.TypeParameterConstraintClause: - ClassifyUpdate((TypeParameterConstraintClauseSyntax)oldNode, (TypeParameterConstraintClauseSyntax)newNode); - return; + case SyntaxKind.PropertyDeclaration: + ClassifyUpdate((PropertyDeclarationSyntax)oldNode, (PropertyDeclarationSyntax)newNode); + return; - case SyntaxKind.TypeParameter: - ClassifyUpdate((TypeParameterSyntax)oldNode, (TypeParameterSyntax)newNode); - return; + case SyntaxKind.IndexerDeclaration: + ClassifyUpdate((IndexerDeclarationSyntax)oldNode, (IndexerDeclarationSyntax)newNode); + return; - case SyntaxKind.Parameter: - ClassifyUpdate((ParameterSyntax)oldNode, (ParameterSyntax)newNode); - return; + case SyntaxKind.EventDeclaration: + return; - case SyntaxKind.AttributeList: - ClassifyUpdate((AttributeListSyntax)oldNode, (AttributeListSyntax)newNode); - return; + case SyntaxKind.EnumMemberDeclaration: + ClassifyUpdate((EnumMemberDeclarationSyntax)oldNode, (EnumMemberDeclarationSyntax)newNode); + return; - case SyntaxKind.Attribute: - // Dev12 reports "Rename" if the attribute type name is changed. - // But such update is actually not renaming the attribute, it's changing what attribute is applied. - ReportError(RudeEditKind.Update); - return; + case SyntaxKind.GetAccessorDeclaration: + case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.AddAccessorDeclaration: + case SyntaxKind.RemoveAccessorDeclaration: + ClassifyUpdate((AccessorDeclarationSyntax)oldNode, (AccessorDeclarationSyntax)newNode); + return; - case SyntaxKind.TypeParameterList: - case SyntaxKind.ParameterList: - case SyntaxKind.BracketedParameterList: - case SyntaxKind.AccessorList: - return; + case SyntaxKind.Parameter: + ClassifyUpdate((ParameterSyntax)oldNode, (ParameterSyntax)newNode); + return; - default: - throw ExceptionUtilities.UnexpectedValue(newNode.Kind()); + case SyntaxKind.TypeParameterList: + case SyntaxKind.ParameterList: + case SyntaxKind.BracketedParameterList: + case SyntaxKind.AccessorList: + return; + + default: + throw ExceptionUtilities.UnexpectedValue(newNode.Kind()); + } } } From ebea62b73427a865d4322551614354126bb11ee7 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Jan 2021 16:33:29 +1100 Subject: [PATCH 34/39] Report rude edits for method body syntax related to local functions --- .../CSharpEditAndContinueAnalyzer.cs | 30 +++++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 6 ++++ .../VisualBasicEditAndContinueAnalyzer.vb | 4 +++ 3 files changed, 40 insertions(+) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 50a5ea64cfd6c..f721d7571cdef 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1139,6 +1139,36 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, } } + protected override void ReportMethodBodySyntaxRudeEditsForLambda(SyntaxNode oldLambda, SyntaxNode newLambda, Match bodyMatch, List diagnostics) + { + // We're only interested in local functions here + if (!IsLocalFunction(oldLambda)) + { + return; + } + + var bodyEditsForLambda = bodyMatch.GetTreeEdits(); + var editMap = BuildEditMap(bodyEditsForLambda); + foreach (var edit in bodyEditsForLambda.Edits) + { + // We are processing edits on the method body that contains the lambda as + // things like local function attributes are actually edits in the containing method, not the actual + // lambda body. + // We only want to consider the edits that are related to the lambda (ie, descendants). + if ((edit.OldNode != null && oldLambda.Contains(edit.OldNode)) || + (edit.NewNode != null && newLambda.Contains(edit.NewNode))) + { + if (HasParentEdit(editMap, edit)) + { + return; + } + + var classifier = new EditClassifier(this, diagnostics, edit.OldNode, edit.NewNode, edit.Kind, bodyMatch, isTopLevelEdit: false); + classifier.ClassifyEdit(); + } + } + } + protected override void ReportLambdaSignatureRudeEdits( SemanticModel oldModel, SyntaxNode oldLambdaBody, diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index d2a66b2f9ff59..9dbc4567f99dc 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -305,6 +305,8 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind protected abstract void GetStateMachineInfo(SyntaxNode body, out ImmutableArray suspensionPoints, out StateMachineKinds kinds); protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren); + protected abstract void ReportMethodBodySyntaxRudeEditsForLambda(SyntaxNode oldLambda, SyntaxNode newLambda, Match bodyMatch, List diagnostics); + internal abstract void ReportSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); internal abstract void ReportEnclosingExceptionHandlingRudeEdits(List diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); internal abstract void ReportOtherRudeEditsAroundActiveStatement(List diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isNonLeaf); @@ -1259,6 +1261,8 @@ private BidirectionalMap ComputeMap( if (newLambdaBody1 != null) { lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody1, newLambdaBody1, activeNodes, lazyActiveOrMatchedLambdas, diagnostics)); + + ReportMethodBodySyntaxRudeEditsForLambda(GetLambda(oldLambdaBody1), GetLambda(newLambdaBody1), bodyMatch, diagnostics); } if (oldLambdaBody2 != null) @@ -1267,6 +1271,8 @@ private BidirectionalMap ComputeMap( if (newLambdaBody2 != null) { lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody2, newLambdaBody2, activeNodes, lazyActiveOrMatchedLambdas, diagnostics)); + + ReportMethodBodySyntaxRudeEditsForLambda(GetLambda(oldLambdaBody2), GetLambda(newLambdaBody2), bodyMatch, diagnostics); } } } diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index d9a047804ba2e..52e9273c7b7bb 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1158,6 +1158,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function + Protected Overrides Sub ReportMethodBodySyntaxRudeEditsForLambda(oldLambda As SyntaxNode, newLambda As SyntaxNode, bodyMatch As Match(Of SyntaxNode), diagnostics As List(Of RudeEditDiagnostic)) + ' VB has no local functions so we don't have anything to check here + End Sub + #End Region #Region "Diagnostic Info" From 52dc819d07ba6f3c908ed8c11110ab114e7833ff Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 25 Jan 2021 08:50:51 +1100 Subject: [PATCH 35/39] Analyze local functions by their declaraions --- .../CSharpEditAndContinueAnalyzer.cs | 32 +++++++------------ .../AbstractEditAndContinueAnalyzer.cs | 11 ++++--- .../VisualBasicEditAndContinueAnalyzer.vb | 4 +-- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index f721d7571cdef..eaf540ae04202 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -554,6 +554,12 @@ protected override Match ComputeBodyMatch(SyntaxNode oldBody, Syntax SyntaxUtilities.AssertIsBody(oldBody, allowLambda: true); SyntaxUtilities.AssertIsBody(newBody, allowLambda: true); + if (oldBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement) && newBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement)) + { + // For local functions we use the parent local function decalration so we can see edits to parameters and attributes + return StatementSyntaxComparer.Default.ComputeMatch(oldBody.Parent, newBody.Parent, knownMatches); + } + if (oldBody is ExpressionSyntax || newBody is ExpressionSyntax) { Debug.Assert(oldBody is ExpressionSyntax || oldBody is BlockSyntax); @@ -1139,33 +1145,19 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, } } - protected override void ReportMethodBodySyntaxRudeEditsForLambda(SyntaxNode oldLambda, SyntaxNode newLambda, Match bodyMatch, List diagnostics) + protected override void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics) { - // We're only interested in local functions here - if (!IsLocalFunction(oldLambda)) - { - return; - } - var bodyEditsForLambda = bodyMatch.GetTreeEdits(); var editMap = BuildEditMap(bodyEditsForLambda); foreach (var edit in bodyEditsForLambda.Edits) { - // We are processing edits on the method body that contains the lambda as - // things like local function attributes are actually edits in the containing method, not the actual - // lambda body. - // We only want to consider the edits that are related to the lambda (ie, descendants). - if ((edit.OldNode != null && oldLambda.Contains(edit.OldNode)) || - (edit.NewNode != null && newLambda.Contains(edit.NewNode))) + if (HasParentEdit(editMap, edit)) { - if (HasParentEdit(editMap, edit)) - { - return; - } - - var classifier = new EditClassifier(this, diagnostics, edit.OldNode, edit.NewNode, edit.Kind, bodyMatch, isTopLevelEdit: false); - classifier.ClassifyEdit(); + return; } + + var classifier = new EditClassifier(this, diagnostics, edit.OldNode, edit.NewNode, edit.Kind, bodyMatch, isTopLevelEdit: false); + classifier.ClassifyEdit(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 9dbc4567f99dc..313fc186cf984 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -305,7 +305,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind protected abstract void GetStateMachineInfo(SyntaxNode body, out ImmutableArray suspensionPoints, out StateMachineKinds kinds); protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren); - protected abstract void ReportMethodBodySyntaxRudeEditsForLambda(SyntaxNode oldLambda, SyntaxNode newLambda, Match bodyMatch, List diagnostics); + protected abstract void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics); internal abstract void ReportSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); internal abstract void ReportEnclosingExceptionHandlingRudeEdits(List diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); @@ -1261,8 +1261,6 @@ private BidirectionalMap ComputeMap( if (newLambdaBody1 != null) { lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody1, newLambdaBody1, activeNodes, lazyActiveOrMatchedLambdas, diagnostics)); - - ReportMethodBodySyntaxRudeEditsForLambda(GetLambda(oldLambdaBody1), GetLambda(newLambdaBody1), bodyMatch, diagnostics); } if (oldLambdaBody2 != null) @@ -1271,8 +1269,6 @@ private BidirectionalMap ComputeMap( if (newLambdaBody2 != null) { lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody2, newLambdaBody2, activeNodes, lazyActiveOrMatchedLambdas, diagnostics)); - - ReportMethodBodySyntaxRudeEditsForLambda(GetLambda(oldLambdaBody2), GetLambda(newLambdaBody2), bodyMatch, diagnostics); } } } @@ -1386,6 +1382,11 @@ private Match ComputeBodyMatch( var match = ComputeBodyMatch(oldBody, newBody, lazyKnownMatches); + if (IsLocalFunction(match.OldRoot) && IsLocalFunction(match.NewRoot)) + { + ReportLocalFunctionsDeclarationRudeEdits(match, diagnostics); + } + if (lazyRudeEdits != null) { foreach (var rudeEdit in lazyRudeEdits) diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 52e9273c7b7bb..4de55b1be036a 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1158,8 +1158,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function - Protected Overrides Sub ReportMethodBodySyntaxRudeEditsForLambda(oldLambda As SyntaxNode, newLambda As SyntaxNode, bodyMatch As Match(Of SyntaxNode), diagnostics As List(Of RudeEditDiagnostic)) - ' VB has no local functions so we don't have anything to check here + Protected Overrides Sub ReportLocalFunctionsDeclarationRudeEdits(bodyMatch As Match(Of SyntaxNode), diagnostics As List(Of RudeEditDiagnostic)) + ' VB has no local functions so we don't have anything to report End Sub #End Region From 792c0deaa3651aaea2bb939a86f7580db050687d Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 29 Jan 2021 11:40:12 +1100 Subject: [PATCH 36/39] Allow multiple interesting child nodes to be included for local functions --- .../CSharpEditAndContinueAnalyzer.cs | 8 +- .../StatementSyntaxComparer.cs | 80 +++++++++++++------ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index eaf540ae04202..9d372f68139f2 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -554,13 +554,7 @@ protected override Match ComputeBodyMatch(SyntaxNode oldBody, Syntax SyntaxUtilities.AssertIsBody(oldBody, allowLambda: true); SyntaxUtilities.AssertIsBody(newBody, allowLambda: true); - if (oldBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement) && newBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement)) - { - // For local functions we use the parent local function decalration so we can see edits to parameters and attributes - return StatementSyntaxComparer.Default.ComputeMatch(oldBody.Parent, newBody.Parent, knownMatches); - } - - if (oldBody is ExpressionSyntax || newBody is ExpressionSyntax) + if (oldBody is ExpressionSyntax || newBody is ExpressionSyntax || (oldBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement) && newBody.Parent.IsKind(SyntaxKind.LocalFunctionStatement))) { Debug.Assert(oldBody is ExpressionSyntax || oldBody is BlockSyntax); Debug.Assert(newBody is ExpressionSyntax || newBody is BlockSyntax); diff --git a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs index ed84f8e2b635d..a9f0b1ad610f6 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; + namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue { internal sealed class StatementSyntaxComparer : SyntaxComparer @@ -89,24 +90,59 @@ private IEnumerable EnumerateRootChildren(SyntaxNode root) { RoslynDebug.Assert(_oldRoot != null); RoslynDebug.Assert(_newRoot != null); - RoslynDebug.Assert(_oldRootChild != null); - RoslynDebug.Assert(_newRootChild != null); - - var child = (root == _oldRoot) ? _oldRootChild : _newRootChild; - if (GetLabelImpl(child) != Label.Ignored) + foreach (var child in GetRootChildren(root)) { - yield return child; + if (GetLabelImpl(child) != Label.Ignored) + { + yield return child; + } + else + { + foreach (var descendant in child.DescendantNodes(DescendIntoChildren)) + { + if (HasLabel(descendant)) + { + yield return descendant; + } + } + } } - else + } + + private IEnumerable GetRootChildren(SyntaxNode node) + { + if (node == _oldRoot || node == _newRoot) { - foreach (var descendant in child.DescendantNodes(DescendIntoChildren)) + RoslynDebug.Assert(_oldRootChild != null); + RoslynDebug.Assert(_newRootChild != null); + + if (node is LocalFunctionStatementSyntax localFunc) { - if (HasLabel(descendant)) + // local functions have multiple children we need to process for matches, but we won't automatically + // descend into them, assuming they're nested, so we override the default behaviour and return + // multiple children + foreach (var attributeList in localFunc.AttributeLists) { - yield return descendant; + yield return attributeList; + } + + yield return localFunc.ReturnType; + + if (localFunc.TypeParameterList is not null) + { + yield return localFunc.TypeParameterList; } + + yield return localFunc.ParameterList; } + + yield return (node == _oldRoot) ? _oldRootChild : _newRootChild; + } + else + { + // If no root was specified then just return the node itself, no root means no special root children + yield return node; } } @@ -115,29 +151,20 @@ private bool DescendIntoChildren(SyntaxNode node) protected internal sealed override IEnumerable GetDescendants(SyntaxNode node) { - if (node == _oldRoot || node == _newRoot) + foreach (var rootChild in GetRootChildren(node)) { - RoslynDebug.Assert(_oldRoot != null); - RoslynDebug.Assert(_newRoot != null); - RoslynDebug.Assert(_oldRootChild != null); - RoslynDebug.Assert(_newRootChild != null); - - var rootChild = (node == _oldRoot) ? _oldRootChild : _newRootChild; - if (HasLabel(rootChild)) { yield return rootChild; } - node = rootChild; - } - - // TODO: avoid allocation of closure - foreach (var descendant in node.DescendantNodes(descendIntoChildren: c => !IsLeaf(c) && (c == node || !LambdaUtilities.IsLambdaBodyStatementOrExpression(c)))) - { - if (!LambdaUtilities.IsLambdaBodyStatementOrExpression(descendant) && HasLabel(descendant)) + // TODO: avoid allocation of closure + foreach (var descendant in rootChild.DescendantNodes(descendIntoChildren: c => !IsLeaf(c) && (c == rootChild || !LambdaUtilities.IsLambdaBodyStatementOrExpression(c)))) { - yield return descendant; + if (!LambdaUtilities.IsLambdaBodyStatementOrExpression(descendant) && HasLabel(descendant)) + { + yield return descendant; + } } } } @@ -882,6 +909,7 @@ private bool TryComputeWeightedDistance(BlockSyntax leftBlock, BlockSyntax right case SyntaxKind.LockStatement: case SyntaxKind.UsingStatement: case SyntaxKind.SwitchSection: + case SyntaxKind.LocalFunctionStatement: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.AnonymousMethodExpression: From 8770aede58d689a95c730551e6ee54833f945731 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 29 Jan 2021 15:46:04 +1100 Subject: [PATCH 37/39] Wrap arguments --- .../EditAndContinue/StatementEditingTests.cs | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 330d084f261fd..7e0b5ee0403d0 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6951,7 +6951,9 @@ public void LocalFunction_AddAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Insert [[A]]@2", "Insert [A]@3"); + edits.VerifyEdits( + "Insert [[A]]@2", + "Insert [A]@3"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -6967,7 +6969,9 @@ public void LocalFunction_RemoveAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Delete [[A]]@2", "Delete [A]@3"); + edits.VerifyEdits( + "Delete [[A]]@2", + "Delete [A]@3"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -6999,7 +7003,11 @@ public void LocalFunction_CombineAttributeLists() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Update [[A]]@2 -> [[A, B]]@2", "Insert [B]@6", "Delete [[B]]@5", "Delete [B]@6"); + edits.VerifyEdits( + "Update [[A]]@2 -> [[A, B]]@2", + "Insert [B]@6", + "Delete [[B]]@5", + "Delete [B]@6"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7017,7 +7025,11 @@ public void LocalFunction_SplitAttributeLists() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Update [[A, B]]@2 -> [[A]]@2", "Insert [[B]]@5", "Insert [B]@6", "Delete [B]@6"); + edits.VerifyEdits( + "Update [[A, B]]@2 -> [[A]]@2", + "Insert [[B]]@5", + "Insert [B]@6", + "Delete [B]@6"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7065,7 +7077,9 @@ public void LocalFunction_ReturnType_AddAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Insert [[return: A]]@2", "Insert [A]@11"); + edits.VerifyEdits( + "Insert [[return: A]]@2", + "Insert [A]@11"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7081,7 +7095,9 @@ public void LocalFunction_ReturnType_RemoveAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Delete [[return: A]]@2", "Delete [A]@11"); + edits.VerifyEdits( + "Delete [[return: A]]@2", + "Delete [A]@11"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7113,7 +7129,9 @@ public void LocalFunction_Parameter_AddAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); + edits.VerifyEdits( + "Insert [[A]]@9", + "Insert [A]@10"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7129,7 +7147,9 @@ public void LocalFunction_Parameter_RemoveAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); + edits.VerifyEdits( + "Delete [[A]]@9", + "Delete [A]@10"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7161,7 +7181,9 @@ public void LocalFunction_TypeParameter_AddAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Insert [[A]]@9", "Insert [A]@10"); + edits.VerifyEdits( + "Insert [[A]]@9", + "Insert [A]@10"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); @@ -7177,7 +7199,9 @@ public void LocalFunction_TypeParameter_RemoveAttribute() var edits = GetMethodEdits(src1, src2); - edits.VerifyEdits("Delete [[A]]@9", "Delete [A]@10"); + edits.VerifyEdits( + "Delete [[A]]@9", + "Delete [A]@10"); // Get top edits so we can validate rude edits edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); From 3569e2b3684cc85e9e32b817b8b4a21a0f73bb7d Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 1 Feb 2021 16:38:51 +1100 Subject: [PATCH 38/39] Handle null parameter type in lambda parameters --- .../EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs | 1 + .../EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 54e1b6cf18127..a0c74f12f90dd 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -217,6 +217,7 @@ void M() /**/expr;/**/ /**/int a;/**/ F(/**/(x)/**/ => x); + F(/**/x/**/ => x); F(/**/delegate/**/(x) { }); F(from a in b /**/select/**/ a.x); F(from a in b /**/let/**/ x = expr select expr); diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 9d372f68139f2..bd90a5250d7f2 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1352,10 +1352,10 @@ private static bool GroupBySignatureComparer(ImmutableArray ol } case SyntaxKind.Parameter: - // We ignore anonymous methods and lambdas, - // we only care about parameters of member declarations. var parameter = (ParameterSyntax)node; - return GetDiagnosticSpan(parameter.Modifiers, parameter.Type, parameter); + // Lambda parameters don't have types or modifiers, so the parameter is the only node + var startNode = parameter.Type ?? (SyntaxNode)parameter; + return GetDiagnosticSpan(parameter.Modifiers, startNode, parameter); case SyntaxKind.AttributeList: var attributeList = (AttributeListSyntax)node; From 14838f67b399ce7de09bef3a6b287af122e20866 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 1 Feb 2021 18:16:56 +1100 Subject: [PATCH 39/39] Don't use the top level edit flag in one place --- .../EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index bd90a5250d7f2..be0cceac49ef7 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1914,7 +1914,7 @@ public void ClassifyEdit() return; case EditKind.Move: - ClassifyMove(); + ClassifyMove(_newNode); return; case EditKind.Insert: @@ -1932,9 +1932,9 @@ public void ClassifyEdit() #region Move and Reorder - private void ClassifyMove() + private void ClassifyMove(SyntaxNode newNode) { - if (!_isTopLevelEdit) + if (newNode.IsKind(SyntaxKind.LocalFunctionStatement)) { return; }