/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Symbol/Symbols/UserDefinedOperatorErrorTests.cs
320 строк
18 KB
AlekseyTs
Support declaration of instance increment operators (#76991)
06 фев 2025, 21:14
Не верифицирован
06 фев 2025, 21:14
3d90c17
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public partial class CompilationErrorTests : CompilingTestBase { [Fact] public void UserDefinedOperatorCollisionErrors() { // DELIBERATE SPEC VIOLATION: // // Though the spec does not say so, the native and Roslyn compilers both enforce // signature uniqueness rules as though the user-defined operators were declared // in source as their underlying "op_whatever" methods. With one exception: // user-defined conversion operators are allowed to overload on return type. // // See the tests below for the details. var text = @" class C { // These two collide by signature public static C operator + (C c1, C c2) { return c1; } public static C op_Addition(C c1, C c2) { return c1; } // But this one does not. public static int op_Addition(int c1, int c2) { return c2; } // These two collide by name; the error is reported on the latter. public static C operator - (C c1, C c2) { return c1; } public int op_Subtraction; // These two collide by name; the error is reported on the latter. public int op_Division; public static C operator / (C c1, C c2) { return c1; } // These two collide by name; the error is reported on the operator // regardless of which one comes first. public static C operator * (C c1, C c2) { return c1; } private class op_Multiply {} // These two collide because they have different return types but // identical parameters types. The behavior is that the error // given says that they collide because of the name op_Modulus, // rather than there being a custom error message as there is for // the following scenario. public static C operator % (C c1, C c2) { return c1; } public static int operator % (C c1, C c2) { return 0; } // These collide because operators do not consider whether they // are implicit or explicit, only what the target and source types are. public static implicit operator string(C c) { return null; } public static explicit operator string(C c) { return null; } } class D { // These do *not* collide even though they have identical names // and differ in return type. public static implicit operator string(D d) { return null; } public static implicit operator int(D d) { return 0; } } class E { // However, these *do* collide because one of them is a method // and the other is treated as a method for the purpose of finding // a collision. The fact that the return types differ is relevant. public static implicit operator string(E e) { return null; } public static int op_Implicit(E e) { return 0; } // But this one does not collide, even though *as a conversion* // the two conversions would collide: public static string op_Explicit(E e) { return null; } } class F { // These do collide, same as above. Note that the error is // reported on the latter. public static int op_Implicit(F f) { return 0; } public static implicit operator string(F f) { return null; } } class G { // User-defined conversions collide with other members. public static implicit operator string(G g) { return null; } public int op_Implicit { get; set; } } class H { // User-defined conversions collide with nested types public static implicit operator string(H h) { return null; } private class op_Implicit {} } "; var comp = CreateCompilation(text); comp.VerifyDiagnostics( // (6,21): error CS0111: Type 'C' already defines a member called 'op_Addition' with the same parameter types // public static C op_Addition(C c1, C c2) { return c1; } Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "op_Addition").WithArguments("op_Addition", "C"), // (13,16): error CS0102: The type 'C' already contains a definition for 'op_Subtraction' // public int op_Subtraction; Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "op_Subtraction").WithArguments("C", "op_Subtraction"), // (17,30): error CS0102: The type 'C' already contains a definition for 'op_Division' // public static C operator / (C c1, C c2) { return c1; } Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "/").WithArguments("C", "op_Division"), // (21,30): error CS0102: The type 'C' already contains a definition for 'op_Multiply' // public static C operator * (C c1, C c2) { return c1; } Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "*").WithArguments("C", "op_Multiply"), // (30,32): error CS0111: Type 'C' already defines a member called 'op_Modulus' with the same parameter types // public static int operator % (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "%").WithArguments("op_Modulus", "C"), // (35,37): error CS0557: Duplicate user-defined conversion in type 'C' // public static explicit operator string(C c) { return null; } Diagnostic(ErrorCode.ERR_DuplicateConversionInClass, "string").WithArguments("C"), // (50,23): error CS0111: Type 'E' already defines a member called 'op_Implicit' with the same parameter types // public static int op_Implicit(E e) { return 0; } Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "op_Implicit").WithArguments("op_Implicit", "E"), // (60,37): error CS0111: Type 'F' already defines a member called 'op_Implicit' with the same parameter types // public static implicit operator string(F f) { return null; } Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "string").WithArguments("op_Implicit", "F"), // (66,16): error CS0102: The type 'G' already contains a definition for 'op_Implicit' // public int op_Implicit { get; set; } Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "op_Implicit").WithArguments("G", "op_Implicit"), // (71,37): error CS0102: The type 'H' already contains a definition for 'op_Implicit' // public static implicit operator string(H h) { return null; } Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "string").WithArguments("H", "op_Implicit"), // (13,16): warning CS0649: Field 'C.op_Subtraction' is never assigned to, and will always have its default value 0 // public int op_Subtraction; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "op_Subtraction").WithArguments("C.op_Subtraction", "0"), // (16,16): warning CS0649: Field 'C.op_Division' is never assigned to, and will always have its default value 0 // public int op_Division; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "op_Division").WithArguments("C.op_Division", "0") ); } [Fact] public void UserDefinedOperatorBodyErrors() { // User-defined operators have the same behavior as other methods; // for example, they must return a value compatible with their declared // return type and have an unreachable end point. var text = @" class C { public class D {} public static D operator + (C c1, C c2) { return c1; } public static explicit operator int (C c) { } } "; var comp = CreateCompilation(text); comp.VerifyDiagnostics( // (5,54): error CS0029: Cannot implicitly convert type 'C' to 'C.D' // public static D operator + (C c1, C c2) { return c1; } Diagnostic(ErrorCode.ERR_NoImplicitConv, "c1").WithArguments("C", "C.D"), // (6,37): error CS0161: 'C.explicit operator int(C)': not all code paths return a value // public static explicit operator int (C c) { } Diagnostic(ErrorCode.ERR_ReturnExpected, "int").WithArguments("C.explicit operator int(C)") ); } [Fact] public void UserDefinedOperatorModifierErrors() { var text = @" partial class C { partial public static int operator + (C c1, C c2) { return 0; } abstract public int operator - (C c1, C c2) { return 0; } sealed public int operator << (C c1, int c2) { return 0; } new public static int operator >> (C c1, int c2) { return 0; } readonly public static int operator * (C c1, C c2) { return 0; } volatile public static int operator % (C c1, C c2) { return 0; } virtual public int operator - (C c1) { return 0; } override public int operator ~ (C c1) { return 0; } public public public static int operator & (C c1, C c2) { return 0; } extern static public int operator ^ (C c1, C c2) { return 1; } static public int operator + (C c1); new public static int operator >>> (C c1, int c2) { return 0; } } "; // UNDONE: May be unsafe // UNDONE: Native compiler squiggles "operator +". Roslyn squiggles "operator". But perhaps // UNDONE: the better thing to actually squiggle is the offending token. var comp = CreateCompilation(text); comp.VerifyDiagnostics( // (4,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. // partial public static int operator + (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(4, 5), // (4,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type. // partial public static int operator + (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(4, 5), // (5,34): error CS0106: The modifier 'abstract' is not valid for this item // abstract public int operator - (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "-").WithArguments("abstract").WithLocation(5, 34), // (5,34): error CS0558: User-defined operator 'C.operator -(C, C)' must be declared static and public // abstract public int operator - (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_OperatorsMustBeStaticAndPublic, "-").WithArguments("C.operator -(C, C)").WithLocation(5, 34), // (6,32): error CS0106: The modifier 'sealed' is not valid for this item // sealed public int operator << (C c1, int c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "<<").WithArguments("sealed").WithLocation(6, 32), // (6,32): error CS0558: User-defined operator 'C.operator <<(C, int)' must be declared static and public // sealed public int operator << (C c1, int c2) { return 0; } Diagnostic(ErrorCode.ERR_OperatorsMustBeStaticAndPublic, "<<").WithArguments("C.operator <<(C, int)").WithLocation(6, 32), // (7,36): error CS0106: The modifier 'new' is not valid for this item // new public static int operator >> (C c1, int c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, ">>").WithArguments("new").WithLocation(7, 36), // (8,41): error CS0106: The modifier 'readonly' is not valid for this item // readonly public static int operator * (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "*").WithArguments("readonly").WithLocation(8, 41), // (9,41): error CS0106: The modifier 'volatile' is not valid for this item // volatile public static int operator % (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "%").WithArguments("volatile").WithLocation(9, 41), // (10,33): error CS0106: The modifier 'virtual' is not valid for this item // virtual public int operator - (C c1) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "-").WithArguments("virtual").WithLocation(10, 33), // (10,33): error CS0558: User-defined operator 'C.operator -(C)' must be declared static and public // virtual public int operator - (C c1) { return 0; } Diagnostic(ErrorCode.ERR_OperatorsMustBeStaticAndPublic, "-").WithArguments("C.operator -(C)").WithLocation(10, 33), // (11,34): error CS0106: The modifier 'override' is not valid for this item // override public int operator ~ (C c1) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, "~").WithArguments("override").WithLocation(11, 34), // (11,34): error CS0558: User-defined operator 'C.operator ~(C)' must be declared static and public // override public int operator ~ (C c1) { return 0; } Diagnostic(ErrorCode.ERR_OperatorsMustBeStaticAndPublic, "~").WithArguments("C.operator ~(C)").WithLocation(11, 34), // (12,12): error CS1004: Duplicate 'public' modifier // public public public static int operator & (C c1, C c2) { return 0; } Diagnostic(ErrorCode.ERR_DuplicateModifier, "public").WithArguments("public").WithLocation(12, 12), // (13,39): error CS0179: 'C.operator ^(C, C)' cannot be extern and declare a body // extern static public int operator ^ (C c1, C c2) { return 1; } Diagnostic(ErrorCode.ERR_ExternHasBody, "^").WithArguments("C.operator ^(C, C)").WithLocation(13, 39), // (14,32): error CS0501: 'C.operator +(C)' must declare a body because it is not marked abstract, extern, or partial // static public int operator + (C c1); Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "+").WithArguments("C.operator +(C)").WithLocation(14, 32), // (15,36): error CS0106: The modifier 'new' is not valid for this item // new public static int operator >>> (C c1, int c2) { return 0; } Diagnostic(ErrorCode.ERR_BadMemberFlag, ">>>").WithArguments("new").WithLocation(15, 36) ); } [Fact] public void UserDefinedOperatorAccessibilityErrors() { var text = @" public class C { private class D {} public static D operator + (C c1, C c2) { return null; } public static int operator - (C c, D d) { return 0; } public static explicit operator C(D d) { return null; } } "; // UNDONE: Roslyn squiggles just the "operator"; Native compiler squiggles the "operator +". // UNDONE: Consider matching the native compiler behavior, or, even better, squiggle the // UNDONE: offending type. var comp = CreateCompilation(text); comp.VerifyDiagnostics( // (6,30): error CS0056: Inconsistent accessibility: return type 'C.D' is less accessible than operator 'C.operator +(C, C)' // public static D operator + (C c1, C c2) { return null; } Diagnostic(ErrorCode.ERR_BadVisOpReturn, "+").WithArguments("C.operator +(C, C)", "C.D"), // (7,32): error CS0057: Inconsistent accessibility: parameter type 'C.D' is less accessible than operator 'C.operator -(C, C.D)' // public static int operator - (C c, D d) { return 0; } Diagnostic(ErrorCode.ERR_BadVisOpParam, "-").WithArguments("C.operator -(C, C.D)", "C.D"), // (8,37): error CS0057: Inconsistent accessibility: parameter type 'C.D' is less accessible than operator 'C.explicit operator C(C.D)' // public static explicit operator C(D d) { return null; } Diagnostic(ErrorCode.ERR_BadVisOpParam, "C").WithArguments("C.explicit operator C(C.D)", "C.D") ); } [Fact] public void UserDefinedOperatorConstraintViolationErrors() { var text = @" public class C { public class D<T> where T : class {} public static D<int> operator + (C c1, C c2) { return null; } public static int operator - (C c, D<double> d) { return 0; } public static explicit operator C(D<decimal> d) { return null; } } "; // UNDONE: The squiggles are not in the ideal places here. The native compiler squiggles the entire // UNDONE: name of the operator in all cases, which is bad. Roslyn squiggles the "operator" // UNDONE: token if the return type is bad and the parameter name if the parameter type is // UNDONE: bad. This seems no better; surely the right thing to squiggle is the offending type. var comp = CreateCompilation(text); comp.VerifyDiagnostics( // (5,35): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C.D<T>' // public static D<int> operator + (C c1, C c2) { return null; } Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "+").WithArguments("C.D<T>", "T", "int"), // (6,50): error CS0452: The type 'double' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C.D<T>' // public static int operator - (C c, D<double> d) { return 0; } Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "d").WithArguments("C.D<T>", "T", "double"), // (7,50): error CS0452: The type 'decimal' must be a reference type in order to use it as parameter 'T' in the generic type or method 'C.D<T>' // public static explicit operator C(D<decimal> d) { return null; } Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "d").WithArguments("C.D<T>", "T", "decimal") ); } } }