/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Test/Utilities/CSharp/CompilationTestUtils.cs
566 строк
25 KB
Rikki Gibson
Handle extension property reinference in object initializers (#79739)
02 сен 2025, 00:10
Не верифицирован
02 сен 2025, 00:10
bffc4a2
Код
Авторство
О чём код?
// 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 Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Xunit; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public static class CompilationUtils { internal static void CheckISymbols<TSymbol>(ImmutableArray<TSymbol> symbols, params string[] descriptions) where TSymbol : ISymbol { Assert.Equal(descriptions.Length, symbols.Length); string[] symbolDescriptions = (from s in symbols select s.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).ToArray(); Array.Sort(descriptions); Array.Sort(symbolDescriptions); for (int i = 0; i < descriptions.Length; i++) { Assert.Equal(symbolDescriptions[i], descriptions[i]); } } internal static void CheckSymbols<TSymbol>(ImmutableArray<TSymbol> symbols, params string[] descriptions) where TSymbol : Symbol { Assert.Equal(descriptions.Length, symbols.Length); string[] symbolDescriptions = (from s in symbols select s.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).ToArray(); Array.Sort(descriptions); Array.Sort(symbolDescriptions); for (int i = 0; i < descriptions.Length; i++) { Assert.Equal(symbolDescriptions[i], descriptions[i]); } } public static void CheckSymbolsUnordered<TSymbol>(ImmutableArray<TSymbol> symbols, params string[] descriptions) where TSymbol : ISymbol { Assert.Equal(descriptions.Length, symbols.Length); AssertEx.SetEqual(symbols.Select(s => s.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)), descriptions); } public static void CheckSymbols<TSymbol>(TSymbol[] symbols, params string[] descriptions) where TSymbol : ISymbol { CheckISymbols(symbols.AsImmutableOrNull(), descriptions); } public static void CheckSymbol(ISymbol symbol, string description) { Assert.Equal(symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), description); } internal static void CheckSymbol(Symbol symbol, string description) { Assert.Equal(symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), description); } internal static void CheckConstraints(ITypeParameterSymbol symbol, TypeParameterConstraintKind constraints, params string[] constraintTypes) { Assert.Equal(constraints, GetTypeParameterConstraints(symbol)); CheckISymbols(symbol.ConstraintTypes, constraintTypes); } internal static void CheckReducedExtensionMethod( MethodSymbol reducedMethod, string reducedMethodDescription, string reducedFromDescription, string constructedFromDescription, string reducedAndConstructedFromDescription) { var reducedFrom = reducedMethod.ReducedFrom; CheckReducedExtensionMethod(reducedMethod, reducedFrom); Assert.Equal(reducedMethod.CallsiteReducedFromMethod.Parameters[0].Type, reducedMethod.ReceiverType); var constructedFrom = reducedMethod.ConstructedFrom; CheckConstructedMethod(reducedMethod, constructedFrom); var reducedAndConstructedFrom = constructedFrom.ReducedFrom; CheckReducedExtensionMethod(constructedFrom, reducedAndConstructedFrom); Assert.Same(reducedFrom, reducedAndConstructedFrom); var constructedAndExtendedFrom = reducedFrom.ConstructedFrom; CheckConstructedMethod(reducedFrom, constructedAndExtendedFrom); CheckSymbol(reducedMethod, reducedMethodDescription); CheckSymbol(reducedFrom, reducedFromDescription); CheckSymbol(constructedFrom, constructedFromDescription); CheckSymbol(reducedAndConstructedFrom, reducedAndConstructedFromDescription); CheckSymbol(constructedAndExtendedFrom, reducedAndConstructedFromDescription); } public static void CheckReducedExtensionMethod(IMethodSymbol reducedMethod, IMethodSymbol reducedFrom) { Assert.NotNull(reducedFrom); Assert.Equal(reducedMethod.ReducedFrom, reducedFrom); Assert.Null(reducedFrom.ReducedFrom); Assert.True(reducedFrom.IsExtensionMethod); Assert.True(reducedMethod.IsExtensionMethod); Assert.Equal(reducedMethod.IsImplicitlyDeclared, reducedFrom.IsImplicitlyDeclared); Assert.Equal(reducedMethod.CanBeReferencedByName, reducedFrom.CanBeReferencedByName); int n = reducedMethod.Parameters.Count(); Assert.Equal(reducedFrom.Parameters.Count(), n + 1); CheckTypeParameters(reducedMethod); CheckTypeParameters(reducedFrom); } internal static void CheckReducedExtensionMethod(MethodSymbol reducedMethod, MethodSymbol reducedFrom) { CheckReducedExtensionMethod(reducedMethod.GetPublicSymbol(), reducedFrom.GetPublicSymbol()); } public static void CheckConstructedMethod(IMethodSymbol constructedMethod, IMethodSymbol constructedFrom) { Assert.NotNull(constructedFrom); Assert.Same(constructedFrom, constructedMethod.ConstructedFrom); Assert.Same(constructedFrom, constructedMethod.OriginalDefinition); Assert.Same(constructedFrom, constructedFrom.ConstructedFrom); Assert.Same(constructedFrom, constructedFrom.OriginalDefinition); CheckTypeParameters(constructedMethod); CheckTypeParameters(constructedFrom); } internal static void CheckConstructedMethod(MethodSymbol constructedMethod, MethodSymbol constructedFrom) { CheckConstructedMethod(constructedMethod.GetPublicSymbol(), constructedFrom.GetPublicSymbol()); } private static void CheckTypeParameters(IMethodSymbol method) { var constructedFrom = method.ConstructedFrom; Assert.NotNull(constructedFrom); foreach (var typeParameter in method.TypeParameters) { Assert.Equal<ISymbol>(typeParameter.ContainingSymbol, constructedFrom); } } internal static TypeParameterConstraintKind GetTypeParameterConstraints(ITypeParameterSymbol typeParameter) { var constraints = TypeParameterConstraintKind.None; if (typeParameter.HasConstructorConstraint) { constraints |= TypeParameterConstraintKind.Constructor; } if (typeParameter.HasReferenceTypeConstraint) { constraints |= TypeParameterConstraintKind.ReferenceType; } if (typeParameter.HasValueTypeConstraint) { constraints |= TypeParameterConstraintKind.ValueType; } return constraints; } internal static TypeParameterConstraintKind GetTypeParameterConstraints(TypeParameterSymbol typeParameter) { var constraints = TypeParameterConstraintKind.None; if (typeParameter.HasConstructorConstraint) { constraints |= TypeParameterConstraintKind.Constructor; } if (typeParameter.HasReferenceTypeConstraint) { constraints |= TypeParameterConstraintKind.ReferenceType; } if (typeParameter.HasValueTypeConstraint) { constraints |= TypeParameterConstraintKind.ValueType; } return constraints; } public class SemanticInfoSummary { public ISymbol Symbol; public CandidateReason CandidateReason; public ImmutableArray<ISymbol> CandidateSymbols = ImmutableArray.Create<ISymbol>(); public ITypeSymbol Type; public NullabilityInfo Nullability; public ITypeSymbol ConvertedType; public NullabilityInfo ConvertedNullability; public Conversion ImplicitConversion = default(Conversion); public IAliasSymbol Alias; public Optional<object> ConstantValue = default(Optional<object>); public bool IsCompileTimeConstant { get { return ConstantValue.HasValue; } } public ImmutableArray<ISymbol> MemberGroup = ImmutableArray.Create<ISymbol>(); public ImmutableArray<IMethodSymbol> MethodGroup { get { return this.MemberGroup.WhereAsArray(s => s.Kind == SymbolKind.Method).SelectAsArray(s => (IMethodSymbol)s); } } } public static SemanticInfoSummary GetSemanticInfoSummary(this SemanticModel semanticModel, SyntaxNode node) { SemanticInfoSummary summary = new SemanticInfoSummary(); // The information that is available varies by the type of the syntax node. SymbolInfo symbolInfo = SymbolInfo.None; if (node is ExpressionSyntax expr) { symbolInfo = semanticModel.GetSymbolInfo(expr); summary.ConstantValue = semanticModel.GetConstantValue(expr); var typeInfo = semanticModel.GetTypeInfo(expr); summary.Type = typeInfo.Type; summary.ConvertedType = typeInfo.ConvertedType; summary.Nullability = typeInfo.Nullability; summary.ConvertedNullability = typeInfo.ConvertedNullability; summary.ImplicitConversion = semanticModel.GetConversion(expr); summary.MemberGroup = semanticModel.GetMemberGroup(expr); } else if (node is AttributeSyntax attribute) { symbolInfo = semanticModel.GetSymbolInfo(attribute); var typeInfo = semanticModel.GetTypeInfo(attribute); summary.Type = typeInfo.Type; summary.ConvertedType = typeInfo.ConvertedType; summary.ImplicitConversion = semanticModel.GetConversion(attribute); summary.MemberGroup = semanticModel.GetMemberGroup(attribute); } else if (node is OrderingSyntax ordering) { symbolInfo = semanticModel.GetSymbolInfo(ordering); } else if (node is SelectOrGroupClauseSyntax selectOrGroupClause) { symbolInfo = semanticModel.GetSymbolInfo(selectOrGroupClause); } else if (node is ConstructorInitializerSyntax initializer) { symbolInfo = semanticModel.GetSymbolInfo(initializer); var typeInfo = semanticModel.GetTypeInfo(initializer); summary.Type = typeInfo.Type; summary.ConvertedType = typeInfo.ConvertedType; summary.ImplicitConversion = semanticModel.GetConversion(initializer); summary.MemberGroup = semanticModel.GetMemberGroup(initializer); } else if (node is PatternSyntax pattern) { symbolInfo = semanticModel.GetSymbolInfo(pattern); var typeInfo = semanticModel.GetTypeInfo(pattern); summary.Type = typeInfo.Type; summary.ConvertedType = typeInfo.ConvertedType; summary.Nullability = typeInfo.Nullability; summary.ConvertedNullability = typeInfo.ConvertedNullability; summary.ImplicitConversion = semanticModel.GetConversion(pattern); summary.MemberGroup = semanticModel.GetMemberGroup(pattern); } else { throw ExceptionUtilities.UnexpectedValue(node); } summary.Symbol = symbolInfo.Symbol; summary.CandidateReason = symbolInfo.CandidateReason; summary.CandidateSymbols = symbolInfo.CandidateSymbols; if (node is IdentifierNameSyntax identifier) { summary.Alias = semanticModel.GetAliasInfo(identifier); } return summary; } public static SemanticInfoSummary GetSpeculativeSemanticInfoSummary(this SemanticModel semanticModel, int position, SyntaxNode node, SpeculativeBindingOption bindingOption) { SemanticInfoSummary summary = new SemanticInfoSummary(); // The information that is available varies by the type of the syntax node. SymbolInfo symbolInfo = new SymbolInfo(); if (node is ExpressionSyntax) { ExpressionSyntax expr = (ExpressionSyntax)node; symbolInfo = semanticModel.GetSpeculativeSymbolInfo(position, expr, bindingOption); //summary.ConstantValue = semanticModel.GetSpeculativeConstantValue(expr); var typeInfo = semanticModel.GetSpeculativeTypeInfo(position, expr, bindingOption); summary.Type = typeInfo.Type; summary.ConvertedType = typeInfo.ConvertedType; summary.Nullability = typeInfo.Nullability; summary.ConvertedNullability = typeInfo.ConvertedNullability; summary.ImplicitConversion = semanticModel.GetSpeculativeConversion(position, expr, bindingOption); //summary.MethodGroup = semanticModel.GetSpeculativeMethodGroup(expr); } else if (node is ConstructorInitializerSyntax) { var initializer = (ConstructorInitializerSyntax)node; symbolInfo = semanticModel.GetSpeculativeSymbolInfo(position, initializer); } else { throw new NotSupportedException("Type of syntax node is not supported by GetSemanticInfoSummary"); } summary.Symbol = symbolInfo.Symbol; summary.CandidateReason = symbolInfo.CandidateReason; summary.CandidateSymbols = symbolInfo.CandidateSymbols; if (node is IdentifierNameSyntax) { summary.Alias = semanticModel.GetSpeculativeAliasInfo(position, (IdentifierNameSyntax)node, bindingOption); } return summary; } public static List<string> LookupNames(this SemanticModel model, int position, INamespaceOrTypeSymbol container = null, bool namespacesAndTypesOnly = false, bool useBaseReferenceAccessibility = false) { Assert.True(!useBaseReferenceAccessibility || (object)container == null); Assert.True(!useBaseReferenceAccessibility || !namespacesAndTypesOnly); var symbols = useBaseReferenceAccessibility ? model.LookupBaseMembers(position) : namespacesAndTypesOnly ? model.LookupNamespacesAndTypes(position, container) : model.LookupSymbols(position, container); return symbols.Select(s => s.Name).Distinct().ToList(); } internal static TypeInfo GetTypeInfoAndVerifyIOperation(this SemanticModel model, SyntaxNode expression) { var typeInfo = model.GetTypeInfo(expression); var iop = getOperation(model, expression); if (typeInfo.Type is null) { assertTypeInfoNull(iop, typeInfo); } else if (iop is { Type: { } }) { Assert.Equal(typeInfo.Type.NullableAnnotation, iop.Type.NullableAnnotation); } else { Assert.True(isValidDeclaration(expression)); static bool isValidDeclaration(SyntaxNode expression) => (expression.Parent is VariableDeclarationSyntax decl && decl.Type == expression) || (expression.Parent is ForEachStatementSyntax forEach && forEach.Type == expression) || (expression.Parent is DeclarationExpressionSyntax declExpr && declExpr.Type == expression) || (expression.Parent is RefTypeSyntax refType && isValidDeclaration(refType)); } if (iop is { Parent: IConversionOperation parentConversion }) { iop = parentConversion; } if (typeInfo.ConvertedType is null) { Assert.Null(iop?.Type); } else if (iop is { Type: { } }) { Assert.Equal(typeInfo.ConvertedType.NullableAnnotation, iop.Type.NullableAnnotation); } return typeInfo; static IOperation getOperation(SemanticModel model, SyntaxNode expression) { while (true) { // Nullable suppressions and parenthesized expressions are not directly represented in the bound tree. // Rather, they are set as flags on the bound node underlying the node. Therefore, there is similarly // no representation in the IOperation tree, and we should retrieve the IOperation node underlying // the expression. switch (expression) { case PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.SuppressNullableWarningExpression, Operand: { } operand }: expression = operand; continue; case ParenthesizedExpressionSyntax { Expression: { } nested }: expression = nested; continue; default: goto getOperation; } } getOperation: return model.GetOperation(expression); } static void assertTypeInfoNull(IOperation iop, TypeInfo typeInfo) { switch (iop) { // For both of these types, their `IOperation.Type` property represents the converted type, // because any conversions that need to occur are pushed into the branches. However, the // `TypeInfo.Type` property represents the natural type of the switch expression. case ITupleOperation { NaturalType: null }: case ISwitchExpressionOperation _: Assert.True(iop.Type?.NullableAnnotation == typeInfo.ConvertedType?.NullableAnnotation); break; default: Assert.Null(iop?.Type); break; } } } /// <summary> /// Verify the type and nullability inferred by NullabilityWalker of all expressions in the source /// that are followed by specific annotations. Annotations are of the form /*T:type*/. /// </summary> internal static void VerifyTypes(this CSharpCompilation compilation, SyntaxTree tree = null) { if (tree == null) { foreach (var syntaxTree in compilation.SyntaxTrees) { VerifyTypes(compilation, syntaxTree); } return; } Assert.True(compilation.IsNullableAnalysisEnabledIn((CSharpSyntaxTree)tree, new TextSpan(0, tree.Length))); var root = tree.GetRoot(); var allAnnotations = getAnnotations(); if (allAnnotations.IsEmpty) { return; } var model = compilation.GetSemanticModel(tree); var annotationsByMethod = allAnnotations.GroupBy(annotation => { var containingMember = annotation.Expression.Ancestors().OfType<MemberDeclarationSyntax>().First(); if (containingMember is GlobalStatementSyntax) return containingMember.GetRootNode(); return containingMember; }).ToArray(); foreach (var annotations in annotationsByMethod) { var methodSyntax = annotations.Key; var method = model.GetDeclaredSymbol(methodSyntax); var expectedTypes = annotations.SelectAsArray(annotation => annotation.Text); var actualTypes = annotations.SelectAsArray(annotation => { var typeInfo = model.GetTypeInfoAndVerifyIOperation(annotation.Expression); Assert.NotEqual(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); // https://github.com/dotnet/roslyn/issues/35035: After refactoring symboldisplay, we should be able to just call something like typeInfo.Type.ToDisplayString(typeInfo.Nullability.FlowState, TypeWithState.TestDisplayFormat) var type = TypeWithState.Create( (annotation.IsConverted ? typeInfo.ConvertedType : typeInfo.Type).GetSymbol(), (annotation.IsConverted ? typeInfo.ConvertedNullability : typeInfo.Nullability).FlowState.ToInternalFlowState()).ToTypeWithAnnotations(compilation); return type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat); }); // Consider reporting the correct source with annotations on mismatch. AssertEx.Equal(expectedTypes, actualTypes, message: method.ToTestDisplayString()); } ImmutableArray<(ExpressionSyntax Expression, string Text, bool IsConverted)> getAnnotations() { var builder = ArrayBuilder<(ExpressionSyntax, string, bool)>.GetInstance(); foreach (var token in root.DescendantTokens()) { foreach (var trivia in token.TrailingTrivia) { if (trivia.Kind() == SyntaxKind.MultiLineCommentTrivia) { var text = trivia.ToFullString(); const string typePrefix = "/*T:"; const string convertedPrefix = "/*CT:"; const string suffix = "*/"; bool startsWithTypePrefix = text.StartsWith(typePrefix); if (text.EndsWith(suffix) && (startsWithTypePrefix || text.StartsWith(convertedPrefix))) { var prefix = startsWithTypePrefix ? typePrefix : convertedPrefix; var expr = getEnclosingExpression(token); Assert.True(expr != null, $"VerifyTypes could not find a matching expression for annotation '{text}'."); var content = text.Substring(prefix.Length, text.Length - prefix.Length - suffix.Length); builder.Add((expr, content, !startsWithTypePrefix)); } } } } return builder.ToImmutableAndFree(); } ExpressionSyntax getEnclosingExpression(SyntaxToken token) { var node = token.Parent; while (true) { var expr = asExpression(node); if (expr != null) { return expr; } if (node == root) { break; } node = node.Parent; } return null; } ExpressionSyntax asExpression(SyntaxNode node) { while (true) { switch (node) { case null: return null; case ParenthesizedExpressionSyntax paren: return paren.Expression; case IdentifierNameSyntax id when id.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Name == node: node = memberAccess; continue; case ExpressionSyntax expr when expr.Parent is ConditionalAccessExpressionSyntax cond && cond.WhenNotNull == node: node = cond; continue; case ExpressionSyntax expr: return expr; case { Parent: var parent }: node = parent; continue; } } } } } }