// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static class SemanticModelExtensions { public static SemanticMap GetSemanticMap(this SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken) { return SemanticMap.From(semanticModel, node, cancellationToken); } /// /// Gets semantic information, such as type, symbols, and diagnostics, about the parent of a token. /// /// The SemanticModel object to get semantic information /// from. /// The token to get semantic information from. This must be part of the /// syntax tree associated with the binding. /// A cancellation token. public static SymbolInfo GetSymbolInfo(this SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { return semanticModel.GetSymbolInfo(token.Parent, cancellationToken); } public static TSymbol GetEnclosingSymbol(this SemanticModel semanticModel, int position, CancellationToken cancellationToken) where TSymbol : ISymbol { for (var symbol = semanticModel.GetEnclosingSymbol(position, cancellationToken); symbol != null; symbol = symbol.ContainingSymbol) { if (symbol is TSymbol tSymbol) { return tSymbol; } } return default; } public static ISymbol GetEnclosingNamedTypeOrAssembly(this SemanticModel semanticModel, int position, CancellationToken cancellationToken) { return semanticModel.GetEnclosingSymbol(position, cancellationToken) ?? (ISymbol)semanticModel.Compilation.Assembly; } public static INamedTypeSymbol GetEnclosingNamedType(this SemanticModel semanticModel, int position, CancellationToken cancellationToken) { return semanticModel.GetEnclosingSymbol(position, cancellationToken); } public static INamespaceSymbol GetEnclosingNamespace(this SemanticModel semanticModel, int position, CancellationToken cancellationToken) { return semanticModel.GetEnclosingSymbol(position, cancellationToken); } public static ITypeSymbol GetType( this SemanticModel semanticModel, SyntaxNode expression, CancellationToken cancellationToken) { var typeInfo = semanticModel.GetTypeInfo(expression, cancellationToken); var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); return typeInfo.Type ?? symbolInfo.GetAnySymbol().ConvertToType(semanticModel.Compilation); } private static ISymbol MapSymbol(ISymbol symbol, ITypeSymbol type) { if (symbol.IsConstructor() && symbol.ContainingType.IsAnonymousType) { return symbol.ContainingType; } if (symbol.IsThisParameter()) { // Map references to this/base to the actual type that those correspond to. return type; } if (symbol.IsFunctionValue() && symbol.ContainingSymbol is IMethodSymbol method) { if (method?.AssociatedSymbol != null) { return method.AssociatedSymbol; } else { return method; } } // see if we can map the built-in language operator to a real method on the containing // type of the symbol. built-in operators can happen when querying the semantic model // for operators. However, we would prefer to just use the real operator on the type // if it has one. if (symbol is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.BuiltinOperator && methodSymbol.ContainingType is ITypeSymbol containingType) { var comparer = SymbolEquivalenceComparer.Instance.ParameterEquivalenceComparer; // Note: this will find the real method vs the built-in. That's because the // built-in is synthesized operator that isn't actually in the list of members of // its 'ContainingType'. var mapped = containingType.GetMembers(methodSymbol.Name) .OfType() .FirstOrDefault(s => s.Parameters.SequenceEqual(methodSymbol.Parameters, comparer)); symbol = mapped ?? symbol; } return symbol; } public static TokenSemanticInfo GetSemanticInfo( this SemanticModel semanticModel, SyntaxToken token, Workspace workspace, CancellationToken cancellationToken) { var languageServices = workspace.Services.GetLanguageServices(token.Language); var syntaxFacts = languageServices.GetService(); if (!syntaxFacts.IsBindableToken(token)) { return TokenSemanticInfo.Empty; } var semanticFacts = languageServices.GetService(); IAliasSymbol aliasSymbol; ITypeSymbol type; ITypeSymbol convertedType; ISymbol declaredSymbol; ImmutableArray allSymbols; var overriddingIdentifier = syntaxFacts.GetDeclarationIdentifierIfOverride(token); if (overriddingIdentifier.HasValue) { // on an "override" token, we'll find the overridden symbol aliasSymbol = null; var overriddingSymbol = semanticFacts.GetDeclaredSymbol(semanticModel, overriddingIdentifier.Value, cancellationToken); var overriddenSymbol = overriddingSymbol.GetOverriddenMember(); // on an "override" token, the overridden symbol is the only part of TokenSemanticInfo used by callers, so type doesn't matter type = null; convertedType = null; declaredSymbol = null; allSymbols = overriddenSymbol is null ? ImmutableArray.Empty : ImmutableArray.Create(overriddenSymbol); } else { aliasSymbol = semanticModel.GetAliasInfo(token.Parent, cancellationToken); var bindableParent = syntaxFacts.GetBindableParent(token); var typeInfo = semanticModel.GetTypeInfo(bindableParent, cancellationToken); type = typeInfo.Type; convertedType = typeInfo.ConvertedType; declaredSymbol = MapSymbol(semanticFacts.GetDeclaredSymbol(semanticModel, token, cancellationToken), type); var skipSymbolInfoLookup = declaredSymbol.IsKind(SymbolKind.RangeVariable); allSymbols = skipSymbolInfoLookup ? ImmutableArray.Empty : semanticFacts .GetBestOrAllSymbols(semanticModel, bindableParent, token, cancellationToken) .WhereAsArray(s => !s.Equals(declaredSymbol)) .SelectAsArray(s => MapSymbol(s, type)); } // NOTE(cyrusn): This is a workaround to how the semantic model binds and returns // information for VB event handlers. Namely, if you have: // // Event X]() // Sub Goo() // Dim y = New $$XEventHandler(AddressOf bar) // End Sub // // Only GetTypeInfo will return any information for XEventHandler. So, in this // case, we upgrade the type to be the symbol we return. if (type != null && allSymbols.Length == 0) { if (type.Kind == SymbolKind.NamedType) { var namedType = (INamedTypeSymbol)type; if (namedType.TypeKind == TypeKind.Delegate || namedType.AssociatedSymbol != null) { allSymbols = ImmutableArray.Create(type); type = null; } } } if (allSymbols.Length == 0 && syntaxFacts.IsQueryKeyword(token)) { type = null; convertedType = null; } return new TokenSemanticInfo(declaredSymbol, aliasSymbol, allSymbols, type, convertedType, token.Span); } public static SemanticModel GetOriginalSemanticModel(this SemanticModel semanticModel) { if (!semanticModel.IsSpeculativeSemanticModel) { return semanticModel; } Contract.ThrowIfNull(semanticModel.ParentModel); Contract.ThrowIfTrue(semanticModel.ParentModel.IsSpeculativeSemanticModel); Contract.ThrowIfTrue(semanticModel.ParentModel.ParentModel != null); return semanticModel.ParentModel; } public static HashSet GetAllDeclaredSymbols( this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken) { var symbols = new HashSet(); if (container != null) { GetAllDeclaredSymbols(semanticModel, container, symbols, cancellationToken); } return symbols; } public static IEnumerable GetExistingSymbols( this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken) { // Ignore an anonymous type property or tuple field. It's ok if they have a name that // matches the name of the local we're introducing. return semanticModel.GetAllDeclaredSymbols(container, cancellationToken) .Where(s => !s.IsAnonymousTypeProperty() && !s.IsTupleField()); } private static void GetAllDeclaredSymbols( SemanticModel semanticModel, SyntaxNode node, HashSet symbols, CancellationToken cancellationToken) { var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken); if (symbol != null) { symbols.Add(symbol); } foreach (var child in node.ChildNodesAndTokens()) { if (child.IsNode) { GetAllDeclaredSymbols(semanticModel, child.AsNode(), symbols, cancellationToken); } } } } }