diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs index fd5306bf177d6..acd6d0a30becc 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs @@ -328,7 +328,6 @@ public override void VisitMethod(IMethodSymbol symbol) case MethodKind.StaticConstructor: break; case MethodKind.Destructor: - case MethodKind.Conversion: // If we're using the metadata format, then include the return type. // Otherwise we eschew it since it is redundant in a conversion // signature. @@ -337,6 +336,18 @@ public override void VisitMethod(IMethodSymbol symbol) goto default; } + break; + + case MethodKind.Conversion: + // If we're using the metadata format, then include the return type. + // Otherwise we eschew it since it is redundant in a conversion + // signature. + if (format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) || + tryGetUserDefinedOperatorTokenKind(symbol.MetadataName) == SyntaxKind.None) + { + goto default; + } + break; default: // The display code is called by the debugger; if a developer is debugging Roslyn and attempts @@ -503,11 +514,11 @@ public override void VisitMethod(IMethodSymbol symbol) if (sourceUserDefinedOperatorSymbolBase is SourceUserDefinedConversionSymbol) { - addUserDefinedConversionName(symbol, operatorName); + addUserDefinedConversionName(symbol, tryGetUserDefinedConversionTokenKind(operatorName), operatorName); } else { - addUserDefinedOperatorName(symbol, operatorName); + addUserDefinedOperatorName(symbol, tryGetUserDefinedOperatorTokenKind(operatorName), operatorName); } break; } @@ -525,7 +536,16 @@ public override void VisitMethod(IMethodSymbol symbol) } else { - addUserDefinedOperatorName(symbol, symbol.MetadataName); + SyntaxKind operatorKind = tryGetUserDefinedOperatorTokenKind(symbol.MetadataName); + + if (operatorKind == SyntaxKind.None) + { + builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.Name)); + } + else + { + addUserDefinedOperatorName(symbol, operatorKind, symbol.MetadataName); + } } break; } @@ -537,7 +557,16 @@ public override void VisitMethod(IMethodSymbol symbol) } else { - addUserDefinedConversionName(symbol, symbol.MetadataName); + SyntaxKind conversionKind = tryGetUserDefinedConversionTokenKind(symbol.MetadataName); + + if (conversionKind == SyntaxKind.None) + { + builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.Name)); + } + else + { + addUserDefinedConversionName(symbol, conversionKind, symbol.MetadataName); + } } break; } @@ -654,16 +683,34 @@ void visitFunctionPointerSignature(IMethodSymbol symbol) AddPunctuation(SyntaxKind.GreaterThanToken); } - void addUserDefinedOperatorName(IMethodSymbol symbol, string operatorName) + static SyntaxKind tryGetUserDefinedOperatorTokenKind(string operatorName) { + if (operatorName == WellKnownMemberNames.TrueOperatorName) + { + return SyntaxKind.TrueKeyword; + } + else if (operatorName == WellKnownMemberNames.FalseOperatorName) + { + return SyntaxKind.FalseKeyword; + } + else + { + return SyntaxFacts.GetOperatorKind(operatorName); + } + } + + void addUserDefinedOperatorName(IMethodSymbol symbol, SyntaxKind operatorKind, string operatorName) + { + Debug.Assert(operatorKind != SyntaxKind.None); + AddKeyword(SyntaxKind.OperatorKeyword); AddSpace(); - if (operatorName == WellKnownMemberNames.TrueOperatorName) + if (operatorKind == SyntaxKind.TrueKeyword) { AddKeyword(SyntaxKind.TrueKeyword); } - else if (operatorName == WellKnownMemberNames.FalseOperatorName) + else if (operatorKind == SyntaxKind.FalseKeyword) { AddKeyword(SyntaxKind.FalseKeyword); } @@ -676,40 +723,38 @@ void addUserDefinedOperatorName(IMethodSymbol symbol, string operatorName) } builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, - SyntaxFacts.GetText(SyntaxFacts.GetOperatorKind(operatorName)))); + SyntaxFacts.GetText(operatorKind))); } } - void addUserDefinedConversionName(IMethodSymbol symbol, string operatorName) + static SyntaxKind tryGetUserDefinedConversionTokenKind(string operatorName) { - // "System.IntPtr.explicit operator System.IntPtr(int)" - - bool isChecked = false; - - if (operatorName == WellKnownMemberNames.ExplicitConversionName) - { - AddKeyword(SyntaxKind.ExplicitKeyword); - } - else if (operatorName == WellKnownMemberNames.CheckedExplicitConversionName) + if (operatorName is WellKnownMemberNames.ExplicitConversionName or WellKnownMemberNames.CheckedExplicitConversionName) { - isChecked = true; - AddKeyword(SyntaxKind.ExplicitKeyword); + return SyntaxKind.ExplicitKeyword; } else if (operatorName == WellKnownMemberNames.ImplicitConversionName) { - AddKeyword(SyntaxKind.ImplicitKeyword); + return SyntaxKind.ImplicitKeyword; } else { - builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, - SyntaxFacts.GetText(SyntaxFacts.GetOperatorKind(operatorName)))); + return SyntaxKind.None; } + } + + void addUserDefinedConversionName(IMethodSymbol symbol, SyntaxKind conversionKind, string operatorName) + { + // "System.IntPtr.explicit operator System.IntPtr(int)" + + Debug.Assert(conversionKind != SyntaxKind.None); + AddKeyword(conversionKind); AddSpace(); AddKeyword(SyntaxKind.OperatorKeyword); AddSpace(); - if (isChecked) + if (operatorName == WellKnownMemberNames.CheckedExplicitConversionName) { AddKeyword(SyntaxKind.CheckedKeyword); AddSpace(); diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 1a89495b86f00..f2004ff096675 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -50,6 +50,13 @@ public static string GetOperationTree(Compilation compilation, IOperation operat { var walker = new OperationTreeVerifier(compilation, operation, initialIndent); walker.Visit(operation); + + var visitor = TestOperationVisitor.Singleton; + foreach (var op in operation.DescendantsAndSelf()) + { + visitor.Visit(op); + } + return walker._builder.ToString(); } diff --git a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs index 8f6477fb773aa..f5562e1b0ff80 100644 --- a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs +++ b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs @@ -736,9 +736,7 @@ public override void VisitUnaryOperator(IUnaryOperation operation) AssertConstrainedToType(operatorMethod, operation.ConstrainedToType); Assert.Same(operation.Operand, operation.ChildOperations.Single()); - // Directly get the symbol for this operator from the semantic model. This allows us to exercise - // potentially creating synthesized intrinsic operators. - CheckBuiltInOperators(operation.SemanticModel, operation.Syntax); + CheckOperators(operation.SemanticModel, operation.Syntax); } public override void VisitBinaryOperator(IBinaryOperation operation) @@ -773,29 +771,38 @@ public override void VisitBinaryOperator(IBinaryOperation operation) AssertEx.Equal(new[] { operation.LeftOperand, operation.RightOperand }, operation.ChildOperations); - // Directly get the symbol for this operator from the semantic model. This allows us to exercise - // potentially creating synthesized intrinsic operators. - CheckBuiltInOperators(operation.SemanticModel, operation.Syntax); + CheckOperators(operation.SemanticModel, operation.Syntax); } - private static void CheckBuiltInOperators(SemanticModel semanticModel, SyntaxNode syntax) + private static void CheckOperators(SemanticModel semanticModel, SyntaxNode syntax) { + // Directly get the symbol for this operator from the semantic model. This allows us to exercise + // potentially creating synthesized intrinsic operators. var symbolInfo = semanticModel?.GetSymbolInfo(syntax) ?? default; + foreach (var symbol in symbolInfo.GetAllSymbols()) { - if (symbol is IMethodSymbol { MethodKind: MethodKind.BuiltinOperator } method) + if (symbol is IMethodSymbol method) { - switch (method.Parameters.Length) + VisualBasic.SymbolDisplay.ToDisplayString(method, SymbolDisplayFormat.TestFormat); + VisualBasic.SymbolDisplay.ToDisplayString(method); + CSharp.SymbolDisplay.ToDisplayString(method, SymbolDisplayFormat.TestFormat); + CSharp.SymbolDisplay.ToDisplayString(method); + + if (method.MethodKind == MethodKind.BuiltinOperator) { - case 1: - semanticModel.Compilation.CreateBuiltinOperator(symbol.Name, method.ReturnType, method.Parameters[0].Type); - break; - case 2: - semanticModel.Compilation.CreateBuiltinOperator(symbol.Name, method.ReturnType, method.Parameters[0].Type, method.Parameters[1].Type); - break; - default: - AssertEx.Fail($"Unexpected parameter count for built in method: {method.ToDisplayString()}"); - break; + switch (method.Parameters.Length) + { + case 1: + semanticModel.Compilation.CreateBuiltinOperator(symbol.Name, method.ReturnType, method.Parameters[0].Type); + break; + case 2: + semanticModel.Compilation.CreateBuiltinOperator(symbol.Name, method.ReturnType, method.Parameters[0].Type, method.Parameters[1].Type); + break; + default: + AssertEx.Fail($"Unexpected parameter count for built in method: {method.ToDisplayString()}"); + break; + } } } } @@ -836,6 +843,14 @@ public override void VisitConversion(IConversionOperation operation) } Assert.Same(operation.Operand, operation.ChildOperations.Single()); + + if (operatorMethod != null) + { + VisualBasic.SymbolDisplay.ToDisplayString(operatorMethod, SymbolDisplayFormat.TestFormat); + VisualBasic.SymbolDisplay.ToDisplayString(operatorMethod); + CSharp.SymbolDisplay.ToDisplayString(operatorMethod, SymbolDisplayFormat.TestFormat); + CSharp.SymbolDisplay.ToDisplayString(operatorMethod); + } } private static void AssertConstrainedToType(ISymbol member, ITypeSymbol constrainedToType) diff --git a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.vb b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.vb index 36539bdc3af4c..91d8b9f3a3a1b 100644 --- a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.vb +++ b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.vb @@ -242,24 +242,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End If Case MethodKind.Conversion - If format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then + + Dim tokenKind As SyntaxKind = TryGetConversionTokenKind(symbol) + + If tokenKind = SyntaxKind.None OrElse format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then AddKeyword(SyntaxKind.FunctionKeyword) AddSpace() Else - If CaseInsensitiveComparison.Equals(symbol.Name, WellKnownMemberNames.ImplicitConversionName) Then - AddKeyword(SyntaxKind.WideningKeyword) - AddSpace() - Else - AddKeyword(SyntaxKind.NarrowingKeyword) - AddSpace() - End If + AddKeyword(tokenKind) + AddSpace() AddKeyword(SyntaxKind.OperatorKeyword) AddSpace() End If Case MethodKind.UserDefinedOperator, MethodKind.BuiltinOperator - If format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then + Dim tokenKind As SyntaxKind = TryGetOperatorTokenKind(symbol) + + If tokenKind = SyntaxKind.None OrElse format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then AddKeyword(SyntaxKind.FunctionKeyword) AddSpace() Else @@ -343,14 +343,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End If Case MethodKind.UserDefinedOperator, MethodKind.BuiltinOperator - If format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then + + Dim tokenKind As SyntaxKind = TryGetOperatorTokenKind(symbol) + + If tokenKind = SyntaxKind.None OrElse format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.Name, visitedParents)) Else - AddKeyword(OverloadResolution.GetOperatorTokenKind(symbol.Name)) + AddKeyword(tokenKind) End If Case MethodKind.Conversion - If format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then + Dim tokenKind As SyntaxKind = TryGetConversionTokenKind(symbol) + + If tokenKind = SyntaxKind.None OrElse format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) Then builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.Name, visitedParents)) Else AddKeyword(SyntaxKind.CTypeKeyword) @@ -366,6 +371,44 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Select End Sub + Private Shared Function TryGetOperatorTokenKind(symbol As IMethodSymbol) As SyntaxKind + Dim nameToCheck As String = symbol.Name + + If symbol.MethodKind = MethodKind.BuiltinOperator Then + Select Case nameToCheck + Case WellKnownMemberNames.CheckedAdditionOperatorName + nameToCheck = WellKnownMemberNames.AdditionOperatorName + Case WellKnownMemberNames.CheckedDivisionOperatorName + nameToCheck = WellKnownMemberNames.IntegerDivisionOperatorName + Case WellKnownMemberNames.CheckedMultiplyOperatorName + nameToCheck = WellKnownMemberNames.MultiplyOperatorName + Case WellKnownMemberNames.CheckedSubtractionOperatorName + nameToCheck = WellKnownMemberNames.SubtractionOperatorName + Case WellKnownMemberNames.CheckedUnaryNegationOperatorName + nameToCheck = WellKnownMemberNames.UnaryNegationOperatorName + End Select + End If + + Dim opInfo As OverloadResolution.OperatorInfo = OverloadResolution.GetOperatorInfo(nameToCheck) + + If (opInfo.IsUnary AndAlso opInfo.UnaryOperatorKind <> UnaryOperatorKind.Error) OrElse + (opInfo.IsBinary AndAlso opInfo.BinaryOperatorKind <> BinaryOperatorKind.Error) Then + Return OverloadResolution.GetOperatorTokenKind(opInfo) + Else + Return SyntaxKind.None + End If + End Function + + Private Shared Function TryGetConversionTokenKind(symbol As IMethodSymbol) As SyntaxKind + If CaseInsensitiveComparison.Equals(symbol.Name, WellKnownMemberNames.ImplicitConversionName) Then + Return SyntaxKind.WideningKeyword + ElseIf CaseInsensitiveComparison.Equals(symbol.Name, WellKnownMemberNames.ExplicitConversionName) Then + Return SyntaxKind.NarrowingKeyword + Else + Return SyntaxKind.None + End If + End Function + Private Sub AddMethodGenericParameters(method As IMethodSymbol) If method.Arity > 0 AndAlso format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeParameters) Then AddTypeArguments(method.TypeArguments) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/BinaryOperators.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/BinaryOperators.vb index 78512b9a09f56..2de61227c2f0f 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/BinaryOperators.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/BinaryOperators.vb @@ -1208,6 +1208,21 @@ End Class returnName), symbol1.ToTestDisplayString()) + Assert.Equal(String.Format("Public Shared Operator {0}(left As {1}, right As {2}) As {3}", + SyntaxFacts.GetText(OverloadResolution.GetOperatorTokenKind( + If(op = BinaryOperatorKind.Add AndAlso resultType = SpecialType.System_String, + BinaryOperatorKind.Concatenate, + op))), + symbol1.Parameters(0).Type.ToDisplayString(), + symbol1.Parameters(1).Type.ToDisplayString(), + symbol1.ReturnType.ToDisplayString()), + symbol1.ToDisplayString()) + + If op = BinaryOperatorKind.Add AndAlso resultType = SpecialType.System_String Then + Assert.Equal("System.String System.String.op_Concatenate(System.String left, System.String right)", CSharp.SymbolDisplay.ToDisplayString(symbol1, SymbolDisplayFormat.TestFormat)) + Assert.Equal("string.op_Concatenate(string, string)", CSharp.SymbolDisplay.ToDisplayString(symbol1)) + End If + Assert.Equal(MethodKind.BuiltinOperator, symbol1.MethodKind) Assert.True(symbol1.IsImplicitlyDeclared) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/UnaryOperators.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/UnaryOperators.vb index d8844b91e6b14..7c6a2d33d90b0 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/UnaryOperators.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/UnaryOperators.vb @@ -755,6 +755,12 @@ End Class returnName), symbol1.ToTestDisplayString()) + Assert.Equal(String.Format("Public Shared Operator {0}(value As {1}) As {2}", + SyntaxFacts.GetText(OverloadResolution.GetOperatorTokenKind(op)), + symbol1.Parameters(0).Type.ToDisplayString(), + symbol1.ReturnType.ToDisplayString()), + symbol1.ToDisplayString()) + Assert.Equal(MethodKind.BuiltinOperator, symbol1.MethodKind) Assert.True(symbol1.IsImplicitlyDeclared) diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CheckedUserDefinedOperatorsTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CheckedUserDefinedOperatorsTests.vb index 117dfa3fcd2ee..db1de575cc9d0 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CheckedUserDefinedOperatorsTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CheckedUserDefinedOperatorsTests.vb @@ -186,12 +186,21 @@ End Class Dim c0_3 = comp3.GetTypeByMetadataName("C0") - For Each m In c0_3.GetMembers().OfType(Of IMethodSymbol)() - If m.MethodKind <> MethodKind.Constructor Then - Assert.Equal(MethodKind.UserDefinedOperator, m.MethodKind) - End If + Dim operators = c0_3.GetMembers().OfType(Of IMethodSymbol)().Where(Function(m) m.MethodKind <> MethodKind.Constructor).ToArray() + + Assert.Equal(3, operators.Length) + + For Each m In operators + Assert.Equal(MethodKind.UserDefinedOperator, m.MethodKind) Next + Assert.Equal("Function C0.op_CheckedUnaryNegation(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(0), SymbolDisplayFormat.TestFormat)) + Assert.Equal("Function C0.op_CheckedDecrement(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(1), SymbolDisplayFormat.TestFormat)) + Assert.Equal("Function C0.op_CheckedIncrement(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(2), SymbolDisplayFormat.TestFormat)) + + Assert.Equal("Public Shared Function op_CheckedUnaryNegation(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(0))) + Assert.Equal("Public Shared Function op_CheckedDecrement(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(1))) + Assert.Equal("Public Shared Function op_CheckedIncrement(x As C0) As C0", SymbolDisplay.ToDisplayString(operators(2))) End Sub @@ -364,12 +373,13 @@ End Class Dim c0_3 = comp3.GetTypeByMetadataName("C0") - For Each m In c0_3.GetMembers().OfType(Of IMethodSymbol)() - If m.MethodKind <> MethodKind.Constructor Then - Assert.Equal(MethodKind.UserDefinedOperator, m.MethodKind) - End If - Next + Dim operators = c0_3.GetMembers().OfType(Of IMethodSymbol)().Where(Function(m) m.MethodKind <> MethodKind.Constructor).ToArray() + + Assert.Equal(1, operators.Length) + Assert.Equal(MethodKind.UserDefinedOperator, operators(0).MethodKind) + Assert.Equal("Function C0." + metadataName + "(x As C0, y As C0) As C0", SymbolDisplay.ToDisplayString(operators(0), SymbolDisplayFormat.TestFormat)) + Assert.Equal("Public Shared Function " + metadataName + "(x As C0, y As C0) As C0", SymbolDisplay.ToDisplayString(operators(0))) End Sub @@ -543,15 +553,14 @@ End Class Dim c0_3 = comp3.GetTypeByMetadataName("C0") - For Each m In c0_3.GetMembers().OfType(Of IMethodSymbol)() - If m.MethodKind <> MethodKind.Constructor Then - Assert.Equal(MethodKind.Conversion, m.MethodKind) - End If - Next + Dim operators = c0_3.GetMembers().OfType(Of IMethodSymbol)().Where(Function(m) m.MethodKind <> MethodKind.Constructor).ToArray() - End Sub + Assert.Equal(1, operators.Length) - 'Public shared Narrowing Operator CType + Assert.Equal(MethodKind.Conversion, operators(0).MethodKind) + Assert.Equal("Function C0.op_CheckedExplicit(x As C0) As System.Int64", SymbolDisplay.ToDisplayString(operators(0), SymbolDisplayFormat.TestFormat)) + Assert.Equal("Public Shared Function op_CheckedExplicit(x As C0) As Long", SymbolDisplay.ToDisplayString(operators(0))) + End Sub