// 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. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed class CSharpSemanticFacts : ISemanticFacts { internal static readonly CSharpSemanticFacts Instance = new CSharpSemanticFacts(); private CSharpSemanticFacts() { } public bool SupportsImplicitInterfaceImplementation => true; public bool ExposesAnonymousFunctionParameterNames => false; public bool IsWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsWrittenTo(); public bool IsOnlyWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsOnlyWrittenTo(); public bool IsInOutContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsInOutContext(); public bool IsInRefContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsInRefContext(); public bool IsInInContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsInInContext(); public bool CanReplaceWithRValue(SemanticModel semanticModel, SyntaxNode expression, CancellationToken cancellationToken) => (expression as ExpressionSyntax).CanReplaceWithRValue(semanticModel, cancellationToken); public string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize, CancellationToken cancellationToken) => semanticModel.GenerateNameForExpression((ExpressionSyntax)expression, capitalize, cancellationToken); public ISymbol GetDeclaredSymbol(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { var location = token.GetLocation(); foreach (var ancestor in token.GetAncestors()) { var symbol = semanticModel.GetDeclaredSymbol(ancestor, cancellationToken); if (symbol != null) { if (symbol.Locations.Contains(location)) { return symbol; } // We found some symbol, but it defined something else. We're not going to have a higher node defining _another_ symbol with this token, so we can stop now. return null; } // If we hit an executable statement syntax and didn't find anything yet, we can just stop now -- anything higher would be a member declaration which won't be defined by something inside a statement. if (CSharpSyntaxFacts.Instance.IsExecutableStatement(ancestor)) { return null; } } return null; } public bool LastEnumValueHasInitializer(INamedTypeSymbol namedTypeSymbol) { var enumDecl = namedTypeSymbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax()).OfType().FirstOrDefault(); if (enumDecl != null) { var lastMember = enumDecl.Members.LastOrDefault(); if (lastMember != null) { return lastMember.EqualsValue != null; } } return false; } public bool SupportsParameterizedProperties => false; public bool TryGetSpeculativeSemanticModel(SemanticModel oldSemanticModel, SyntaxNode oldNode, SyntaxNode newNode, out SemanticModel speculativeModel) { Debug.Assert(oldNode.Kind() == newNode.Kind()); var model = oldSemanticModel; if (!(oldNode is BaseMethodDeclarationSyntax oldMethod) || !(newNode is BaseMethodDeclarationSyntax newMethod) || oldMethod.Body == null) { speculativeModel = null; return false; } var success = model.TryGetSpeculativeSemanticModelForMethodBody(oldMethod.Body.OpenBraceToken.Span.End, newMethod, out var csharpModel); speculativeModel = csharpModel; return success; } public ImmutableHashSet GetAliasNameSet(SemanticModel model, CancellationToken cancellationToken) { var original = model.GetOriginalSemanticModel(); if (!original.SyntaxTree.HasCompilationUnitRoot) { return ImmutableHashSet.Create(); } var root = original.SyntaxTree.GetCompilationUnitRoot(cancellationToken); var builder = ImmutableHashSet.CreateBuilder(StringComparer.Ordinal); AppendAliasNames(root.Usings, builder); AppendAliasNames(root.Members.OfType(), builder, cancellationToken); return builder.ToImmutable(); } private static void AppendAliasNames(SyntaxList usings, ImmutableHashSet.Builder builder) { foreach (var @using in usings) { if (@using.Alias == null || @using.Alias.Name == null) { continue; } @using.Alias.Name.Identifier.ValueText.AppendToAliasNameSet(builder); } } private void AppendAliasNames(IEnumerable namespaces, ImmutableHashSet.Builder builder, CancellationToken cancellationToken) { foreach (var @namespace in namespaces) { cancellationToken.ThrowIfCancellationRequested(); AppendAliasNames(@namespace.Usings, builder); AppendAliasNames(@namespace.Members.OfType(), builder, cancellationToken); } } public ForEachSymbols GetForEachSymbols(SemanticModel semanticModel, SyntaxNode forEachStatement) { if (forEachStatement is CommonForEachStatementSyntax csforEachStatement) { var info = semanticModel.GetForEachStatementInfo(csforEachStatement); return new ForEachSymbols( info.GetEnumeratorMethod, info.MoveNextMethod, info.CurrentProperty, info.DisposeMethod, info.ElementType); } else { return default; } } public IMethodSymbol GetGetAwaiterMethod(SemanticModel semanticModel, SyntaxNode node) { if (node is AwaitExpressionSyntax awaitExpression) { var info = semanticModel.GetAwaitExpressionInfo(awaitExpression); return info.GetAwaiterMethod; } return null; } public ImmutableArray GetDeconstructionAssignmentMethods(SemanticModel semanticModel, SyntaxNode node) { if (node is AssignmentExpressionSyntax assignment && assignment.IsDeconstruction()) { var builder = ArrayBuilder.GetInstance(); FlattenDeconstructionMethods(semanticModel.GetDeconstructionInfo(assignment), builder); return builder.ToImmutableAndFree(); } return ImmutableArray.Empty; } public ImmutableArray GetDeconstructionForEachMethods(SemanticModel semanticModel, SyntaxNode node) { if (node is ForEachVariableStatementSyntax @foreach) { var builder = ArrayBuilder.GetInstance(); FlattenDeconstructionMethods(semanticModel.GetDeconstructionInfo(@foreach), builder); return builder.ToImmutableAndFree(); } return ImmutableArray.Empty; } private static void FlattenDeconstructionMethods(DeconstructionInfo deconstruction, ArrayBuilder builder) { var method = deconstruction.Method; if (method != null) { builder.Add(method); } foreach (var nested in deconstruction.Nested) { FlattenDeconstructionMethods(nested, builder); } } public bool IsPartial(ITypeSymbol typeSymbol, CancellationToken cancellationToken) { var syntaxRefs = typeSymbol.DeclaringSyntaxReferences; return syntaxRefs.Any(n => ((BaseTypeDeclarationSyntax)n.GetSyntax(cancellationToken)).Modifiers.Any(SyntaxKind.PartialKeyword)); } public IEnumerable GetDeclaredSymbols( SemanticModel semanticModel, SyntaxNode memberDeclaration, CancellationToken cancellationToken) { switch (memberDeclaration) { case FieldDeclarationSyntax field: return field.Declaration.Variables.Select( v => semanticModel.GetDeclaredSymbol(v, cancellationToken)); case EventFieldDeclarationSyntax eventField: return eventField.Declaration.Variables.Select( v => semanticModel.GetDeclaredSymbol(v, cancellationToken)); default: return SpecializedCollections.SingletonEnumerable( semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken)); } } public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken) => ((ArgumentSyntax)argumentNode).DetermineParameter(semanticModel, allowParams: false, cancellationToken); public ImmutableArray GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken) { if (node == null) return ImmutableArray.Empty; return node switch { AssignmentExpressionSyntax _ when token.Kind() == SyntaxKind.EqualsToken => GetDeconstructionAssignmentMethods(semanticModel, node).As(), ForEachVariableStatementSyntax _ when token.Kind() == SyntaxKind.InKeyword => GetDeconstructionForEachMethods(semanticModel, node).As(), _ => GetSymbolInfo(semanticModel, node, token, cancellationToken).GetBestOrAllSymbols(), }; } private static SymbolInfo GetSymbolInfo(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken) { switch (node) { case OrderByClauseSyntax orderByClauseSyntax: if (token.Kind() == SyntaxKind.CommaToken) { // Returning SymbolInfo for a comma token is the last resort // in an order by clause if no other tokens to bind to a are present. // See also the proposal at https://github.com/dotnet/roslyn/issues/23394 var separators = orderByClauseSyntax.Orderings.GetSeparators().ToImmutableList(); var index = separators.IndexOf(token); if (index >= 0 && (index + 1) < orderByClauseSyntax.Orderings.Count) { var ordering = orderByClauseSyntax.Orderings[index + 1]; if (ordering.AscendingOrDescendingKeyword.Kind() == SyntaxKind.None) { return semanticModel.GetSymbolInfo(ordering, cancellationToken); } } } else if (orderByClauseSyntax.Orderings[0].AscendingOrDescendingKeyword.Kind() == SyntaxKind.None) { // The first ordering is displayed on the "orderby" keyword itself if there isn't a // ascending/descending keyword. return semanticModel.GetSymbolInfo(orderByClauseSyntax.Orderings[0], cancellationToken); } return default; case QueryClauseSyntax queryClauseSyntax: var queryInfo = semanticModel.GetQueryClauseInfo(queryClauseSyntax, cancellationToken); var hasCastInfo = queryInfo.CastInfo.Symbol != null; var hasOperationInfo = queryInfo.OperationInfo.Symbol != null; if (hasCastInfo && hasOperationInfo) { // In some cases a single clause binds to more than one method. In those cases // the tokens in the clause determine which of the two SymbolInfos are returned. // See also the proposal at https://github.com/dotnet/roslyn/issues/23394 return token.IsKind(SyntaxKind.InKeyword) ? queryInfo.CastInfo : queryInfo.OperationInfo; } if (hasCastInfo) { return queryInfo.CastInfo; } return queryInfo.OperationInfo; } //Only in the orderby clause a comma can bind to a symbol. if (token.IsKind(SyntaxKind.CommaToken)) { return default; } return semanticModel.GetSymbolInfo(node, cancellationToken); } public bool IsInsideNameOfExpression(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) => (node as ExpressionSyntax).IsInsideNameOfExpression(semanticModel, cancellationToken); } }