// 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 Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; using System.Collections.Immutable; using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp { /// /// Some error messages are particularly confusing if multiple placeholders are substituted /// with the same string. For example, "cannot convert from 'Foo' to 'Foo'". Usually, this /// occurs because there are two types in different contexts with the same qualified name. /// The solution is to provide additional qualification on each symbol - either a source /// location, an assembly path, or an assembly identity. /// /// /// Performs the same function as ErrArgFlags::Unique in the native compiler. /// internal sealed class SymbolDistinguisher { private readonly Compilation _compilation; private readonly Symbol _symbol0; private readonly Symbol _symbol1; private ImmutableArray _lazyDescriptions; public SymbolDistinguisher(Compilation compilation, Symbol symbol0, Symbol symbol1) { Debug.Assert(symbol0 != symbol1); CheckSymbolKind(symbol0); CheckSymbolKind(symbol1); _compilation = compilation; _symbol0 = symbol0; _symbol1 = symbol1; } public IMessageSerializable First { get { return new Description(this, 0); } } public IMessageSerializable Second { get { return new Description(this, 1); } } [Conditional("DEBUG")] private static void CheckSymbolKind(Symbol symbol) { switch (symbol.Kind) { case SymbolKind.ErrorType: case SymbolKind.NamedType: case SymbolKind.Event: case SymbolKind.Field: case SymbolKind.Method: case SymbolKind.Property: case SymbolKind.TypeParameter: break; // Can sensibly append location. case SymbolKind.ArrayType: case SymbolKind.PointerType: case SymbolKind.Parameter: break; // Can sensibly append location, after unwrapping. case SymbolKind.DynamicType: break; // Can't sensibly append location, but it should never be ambiguous. case SymbolKind.Namespace: case SymbolKind.Alias: case SymbolKind.Assembly: case SymbolKind.NetModule: case SymbolKind.Label: case SymbolKind.Local: case SymbolKind.RangeVariable: case SymbolKind.Preprocessing: default: throw ExceptionUtilities.UnexpectedValue(symbol.Kind); } } private void MakeDescriptions() { if (!_lazyDescriptions.IsDefault) return; string description0 = _symbol0.ToDisplayString(); string description1 = _symbol1.ToDisplayString(); if (description0 == description1) { Symbol unwrappedSymbol0 = UnwrapSymbol(_symbol0); Symbol unwrappedSymbol1 = UnwrapSymbol(_symbol1); string location0 = GetLocationString(_compilation, unwrappedSymbol0); string location1 = GetLocationString(_compilation, unwrappedSymbol1); // The locations should not be equal, but they might be if the same // SyntaxTree is referenced by two different compilations. if (location0 == location1) { var containingAssembly0 = unwrappedSymbol0.ContainingAssembly; var containingAssembly1 = unwrappedSymbol1.ContainingAssembly; // May not be the case if there are error types. if ((object)containingAssembly0 != null && (object)containingAssembly1 != null) { // Use the assembly identities rather than locations. Note that the // assembly identities may be identical as well. (For instance, the // symbols are type arguments to the same generic type, and the type // arguments have the same string representation. The assembly // identities will refer to the generic types, not the type arguments.) location0 = containingAssembly0.Identity.ToString(); location1 = containingAssembly1.Identity.ToString(); } } if (location0 != location1) { if (location0 != null) { description0 = $"{description0} [{location0}]"; } if (location1 != null) { description1 = $"{description1} [{location1}]"; } } } if (!_lazyDescriptions.IsDefault) return; ImmutableInterlocked.InterlockedInitialize(ref _lazyDescriptions, ImmutableArray.Create(description0, description1)); } private static Symbol UnwrapSymbol(Symbol symbol) { while (true) { switch (symbol.Kind) { case SymbolKind.Parameter: symbol = ((ParameterSymbol)symbol).Type; continue; case SymbolKind.PointerType: symbol = ((PointerTypeSymbol)symbol).PointedAtType; continue; case SymbolKind.ArrayType: symbol = ((ArrayTypeSymbol)symbol).ElementType; continue; default: return symbol; } } } private static string GetLocationString(Compilation compilation, Symbol unwrappedSymbol) { Debug.Assert((object)unwrappedSymbol == UnwrapSymbol(unwrappedSymbol)); ImmutableArray syntaxReferences = unwrappedSymbol.DeclaringSyntaxReferences; if (syntaxReferences.Length > 0) { var tree = syntaxReferences[0].SyntaxTree; var span = syntaxReferences[0].Span; string path = tree.GetDisplayPath(span, (compilation != null) ? compilation.Options.SourceReferenceResolver : null); if (!string.IsNullOrEmpty(path)) { return $"{path}({tree.GetDisplayLineNumber(span)})"; } } AssemblySymbol containingAssembly = unwrappedSymbol.ContainingAssembly; if ((object)containingAssembly != null) { if (compilation != null) { PortableExecutableReference metadataReference = compilation.GetMetadataReference(containingAssembly) as PortableExecutableReference; if (metadataReference != null) { string path = metadataReference.FilePath; if (!string.IsNullOrEmpty(path)) { return path; } } } return containingAssembly.Identity.ToString(); } Debug.Assert(unwrappedSymbol.Kind == SymbolKind.DynamicType || unwrappedSymbol.Kind == SymbolKind.ErrorType); return null; } private string GetDescription(int index) { MakeDescriptions(); return _lazyDescriptions[index]; } private sealed class Description : IMessageSerializable { private readonly SymbolDistinguisher _distinguisher; private readonly int _index; public Description(SymbolDistinguisher distinguisher, int index) { _distinguisher = distinguisher; _index = index; } private Symbol GetSymbol() { return (_index == 0) ? _distinguisher._symbol0 : _distinguisher._symbol1; } public override bool Equals(object obj) { var other = obj as Description; return other != null && _distinguisher._compilation == other._distinguisher._compilation && GetSymbol() == other.GetSymbol(); } public override int GetHashCode() { int result = GetSymbol().GetHashCode(); var compilation = _distinguisher._compilation; if (compilation != null) { result = Hash.Combine(result, compilation.GetHashCode()); } return result; } public override string ToString() { return _distinguisher.GetDescription(_index); } } } }