diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Resources.resx b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Resources.resx
index 781c94f35366..f9f0ed9668e6 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Resources.resx
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Resources.resx
@@ -186,8 +186,8 @@
Cannot add virtual keyword to member '{0}'.
-
- Cannot remove virtual keyword from member '{0}'.
+
+ Cannot remove '{0}' keyword from member '{1}'.API compatibility errors between '{0}' ({2}) and '{1}' ({3}):
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Rules/CannotAddOrRemoveVirtualKeyword.cs b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Rules/CannotAddOrRemoveVirtualKeyword.cs
index 7f7f3dec8a08..3194cc83941e 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Rules/CannotAddOrRemoveVirtualKeyword.cs
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/Rules/CannotAddOrRemoveVirtualKeyword.cs
@@ -63,7 +63,7 @@ private void RunOnMemberSymbol(ISymbol? left, ISymbol? right, ITypeSymbol leftCo
leftMetadata,
rightMetadata,
DiagnosticIds.CannotRemoveVirtualFromMember,
- string.Format(Resources.CannotRemoveVirtualFromMember, left),
+ string.Format(Resources.CannotRemoveVirtualOrAbstractFromMember, "virtual", left),
DifferenceType.Removed,
right));
}
@@ -86,6 +86,21 @@ private void RunOnMemberSymbol(ISymbol? left, ISymbol? right, ITypeSymbol leftCo
right));
}
}
+
+ if (left.IsAbstract)
+ {
+ if (!right.IsAbstract && !right.IsVirtual)
+ {
+ // abstract can be made virtual but cannot remove abstract.
+ differences.Add(new CompatDifference(
+ leftMetadata,
+ rightMetadata,
+ DiagnosticIds.CannotRemoveVirtualFromMember,
+ string.Format(Resources.CannotRemoveVirtualOrAbstractFromMember, "abstract", left),
+ DifferenceType.Removed,
+ right));
+ }
+ }
}
}
}
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.cs.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.cs.xlf
index 5872df4520eb..7f39c5e79e0f 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.cs.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.cs.xlf
@@ -122,9 +122,9 @@
U parametru typu {1} z(e) {2} nelze odebrat omezení {0}.
-
- Cannot remove virtual keyword from member '{0}'.
- Nelze odebrat virtuální klíčové slovo ze člena {0}.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.de.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.de.xlf
index a1c7eb6bc0ff..75cf2421a55c 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.de.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.de.xlf
@@ -122,9 +122,9 @@
Die Einschränkung „{0}“ für den Typparameter „{1}“ von „{2}“ kann nicht entfernt werden.
-
- Cannot remove virtual keyword from member '{0}'.
- Das virtuelle Schlüsselwort kann nicht aus dem Member „{0}“ entfernt werden.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.es.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.es.xlf
index 4588e5e99dc8..b0b37efef4bf 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.es.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.es.xlf
@@ -122,9 +122,9 @@
No se pueden quitar la restricción '{0}' en el parámetro de tipo '{1}' de '{2}'.
-
- Cannot remove virtual keyword from member '{0}'.
- No se puede quitar la palabra clave virtual del miembro "{0}".
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.fr.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.fr.xlf
index 2f1d893ea7a8..5a7563d9d51b 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.fr.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.fr.xlf
@@ -122,9 +122,9 @@
Impossible de supprimer la contrainte « {0} » sur le paramètre de type « {1} » de « {2} ».
-
- Cannot remove virtual keyword from member '{0}'.
- Impossible de supprimer le mot-clé virtuel du membre '{0}'.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.it.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.it.xlf
index dd4468415175..cd156bd23d14 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.it.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.it.xlf
@@ -122,9 +122,9 @@
Impossibile rimuovere il vincolo '{0}' nel parametro di tipo '{1}' di '{2}'.
-
- Cannot remove virtual keyword from member '{0}'.
- Non è possibile rimuovere la parola chiave virtuale dal membro '{0}'.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ja.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ja.xlf
index 41eec2342b28..00205f06ebee 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ja.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ja.xlf
@@ -122,9 +122,9 @@
'{2}' の型パラメーター '{1}' の制約 '{0}' を削除できません。
-
- Cannot remove virtual keyword from member '{0}'.
- メンバー '{0}' から仮想キーワードを削除できません。
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ko.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ko.xlf
index b18330f14056..6e134b556e74 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ko.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ko.xlf
@@ -122,9 +122,9 @@
형식 매개 변수 '{2}'의 '{1}'에서 제약 조건 '{0}'을(를) 제거할 수 없습니다.
-
- Cannot remove virtual keyword from member '{0}'.
- 구성원 '{0}'에서 가상 키워드를 제거할 수 없습니다.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pl.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pl.xlf
index 6aefc58b20a5..6f85a13f676a 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pl.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pl.xlf
@@ -122,9 +122,9 @@
Nie można usunąć ograniczenia „{0}” w parametrze typu „{1}” z „{2}”.
-
- Cannot remove virtual keyword from member '{0}'.
- Nie można usunąć wirtualnego słowa kluczowego z składowej „{0}”.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pt-BR.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pt-BR.xlf
index c52b5f528fb2..4db8cc3804c6 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pt-BR.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.pt-BR.xlf
@@ -122,9 +122,9 @@
Não é possível remover a restrição '{0}' no parâmetro de tipo '{1}' de '{2}'.
-
- Cannot remove virtual keyword from member '{0}'.
- Não é possível remover a palavra-chave virtual do membro '{0}'.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ru.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ru.xlf
index 7254edc7e52a..be95d93acfbb 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ru.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.ru.xlf
@@ -122,9 +122,9 @@
Не удалось удалить ограничение "{0}" для параметра типа "{1}" из "{2}".
-
- Cannot remove virtual keyword from member '{0}'.
- Не удается удалить виртуальное ключевое слово из элемента "{0}".
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.tr.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.tr.xlf
index 8f5b2a486612..7d0d45789ce3 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.tr.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.tr.xlf
@@ -122,9 +122,9 @@
'{1}'/'{2}' tür parametresindeki '{0}' kısıtlaması kaldırılamıyor.
-
- Cannot remove virtual keyword from member '{0}'.
- Sanal anahtar sözcük, '{0}' üyesinden kaldırılamıyor.
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hans.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hans.xlf
index cb556448476d..719984883329 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hans.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hans.xlf
@@ -122,9 +122,9 @@
无法移除 "{2}" 的类型参数 "{1}" 上的约束 "{0}"。
-
- Cannot remove virtual keyword from member '{0}'.
- 无法从成员“{0}”中删除虚拟关键字。
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hant.xlf b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hant.xlf
index bcd1eb5ac9e6..2785bcd3f8e3 100644
--- a/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hant.xlf
+++ b/src/Compatibility/ApiCompat/Microsoft.DotNet.ApiCompatibility/xlf/Resources.zh-Hant.xlf
@@ -122,9 +122,9 @@
無法移除 '{2}' 的類型參數 '{1}' 上的限制式 '{0}'。
-
- Cannot remove virtual keyword from member '{0}'.
- 無法從成員 '{0}' 移除虛擬關鍵字。
+
+ Cannot remove '{0}' keyword from member '{1}'.
+ Cannot remove '{0}' keyword from member '{1}'.
diff --git a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/CannotAddOrRemoveVirtualKeywordTests.cs b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/CannotAddOrRemoveVirtualKeywordTests.cs
index f6eb2a8ba5e8..3a45ecba122b 100644
--- a/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/CannotAddOrRemoveVirtualKeywordTests.cs
+++ b/test/Microsoft.DotNet.ApiCompatibility.Tests/Rules/CannotAddOrRemoveVirtualKeywordTests.cs
@@ -43,6 +43,20 @@ public static IEnumerable