diff --git a/src/QsCompiler/CommandLineTool/LoadContext.cs b/src/QsCompiler/CommandLineTool/LoadContext.cs index 1f9284ca54..02237d3452 100644 --- a/src/QsCompiler/CommandLineTool/LoadContext.cs +++ b/src/QsCompiler/CommandLineTool/LoadContext.cs @@ -111,7 +111,7 @@ private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) return path == null ? null : LoadFromAssemblyPath(path); } - private static ConcurrentBag Loaded = + private static readonly ConcurrentBag Loaded = new ConcurrentBag(); /// diff --git a/src/QsCompiler/CompilationManager/CompilationUnit.cs b/src/QsCompiler/CompilationManager/CompilationUnit.cs index d300341b85..1b6230c700 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -6,12 +6,15 @@ using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -28,9 +31,9 @@ public class Headers public readonly ImmutableArray<(SpecializationDeclarationHeader, SpecializationImplementation)> Specializations; public readonly ImmutableArray Types; - private Headers(string source, - IEnumerable callables = null, - IEnumerable<(SpecializationDeclarationHeader, SpecializationImplementation)> specs = null, + internal Headers(string source, + IEnumerable callables = null, + IEnumerable<(SpecializationDeclarationHeader, SpecializationImplementation)> specs = null, IEnumerable types = null) { NonNullable SourceOr(NonNullable origSource) => NonNullable.New(source ?? origSource.Value); @@ -74,6 +77,40 @@ private static IEnumerable TypeHeaders(IEnumerable<(strin attributes.Select(IsDeclaration("TypeDeclarationAttribute")).Where(v => v != null) .Select(TypeDeclarationHeader.FromJson).Select(built => built.Item2); + /// + /// Renames all internal declarations in the headers to have names that are based on the ID of the referenced + /// assembly. + /// + /// A number uniquely identifying the referenced assembly that contains the headers. + /// The path to the assembly that is the source of the given headers. + /// The declaration headers in the assembly. + /// + private static Headers RenameInternals(int id, string source, Headers headers) + { + var internalNames = headers.Callables + .Where(callable => callable.Modifiers.Access.IsInternal) + .Select(callable => callable.QualifiedName) + .Concat(headers.Types + .Where(type => type.Modifiers.Access.IsInternal) + .Select(type => type.QualifiedName)) + .ToImmutableDictionary(name => name, name => GetNewNameForInternal(id, name)); + var rename = new RenameReferences(internalNames); + var callables = headers.Callables.Select(rename.OnCallableDeclarationHeader); + var specializations = headers.Specializations.Select( + specialization => (rename.OnSpecializationDeclarationHeader(specialization.Item1), + rename.Namespaces.OnSpecializationImplementation(specialization.Item2))); + var types = headers.Types.Select(rename.OnTypeDeclarationHeader); + return new Headers(source, callables, specializations, types); + } + + /// + /// Returns a new name for an internal declaration that is tagged with its reference ID. + /// + /// The ID of the reference in which this name was declared. + /// The name of the internal declaration. + /// A new name for an internal declaration that is tagged with its reference ID. + internal static QsQualifiedName GetNewNameForInternal(int id, QsQualifiedName name) => + new QsQualifiedName(name.Namespace, NonNullable.New($"__Reference{id}_{name.Name.Value}__")); /// /// Dictionary that maps the id of a referenced assembly (given by its location on disk) to the headers defined in that assembly. @@ -109,14 +146,22 @@ internal References Remove(NonNullable source, Action /// Given a dictionary that maps the ids of dll files to the corresponding headers, - /// initializes a new set of references based on the given headers and verifies that there are no conflicts. - /// Calls the given Action onError with suitable diagnostics if two or more references conflict, - /// i.e. if two or more references contain a declaration with the same fully qualified name. - /// Throws an ArgumentNullException if the given dictionary of references is null. + /// initializes a new set of references based on the given headers and verifies that there are no conflicts. + /// Calls the given Action onError with suitable diagnostics if two or more references conflict, + /// i.e. if two or more references contain a public declaration with the same fully qualified name. + /// Throws an ArgumentNullException if the given dictionary of references is null. /// public References(ImmutableDictionary, Headers> refs, Action onError = null) { - this.Declarations = refs ?? throw new ArgumentNullException(nameof(refs)); + // TODO: We mangle the names of internal declarations in references to avoid name conflicts in the symbol + // table. However, it might be better to instead change the symbol table so it can support duplicate names + // as long as they are not in overlapping scopes, which is the case with internal declarations in separate + // assemblies. + this.Declarations = + (refs ?? throw new ArgumentNullException(nameof(refs))) + .Select((reference, index) => (reference.Key, + RenameInternals(index, reference.Key.Value, reference.Value))) + .ToImmutableDictionary(reference => reference.Key, reference => reference.Item2); if (onError == null) return; var conflictingCallables = refs.Values.SelectMany(r => r.Callables) @@ -414,7 +459,7 @@ internal void UpdateCallables(IEnumerable updates) header.Location, header.Signature, header.ArgumentTuple, - ImmutableArray.Create(defaultSpec), + ImmutableArray.Create(defaultSpec), header.Documentation, QsComments.Empty ); diff --git a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs index f404855810..b5360f587e 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs @@ -143,10 +143,10 @@ internal static bool TryGetReferences(this CompilationUnit compilation, QsQualif referenceLocations = namespaces.SelectMany(ns => { var locs = IdentifierReferences.Find(fullName, ns, defaultOffset, out var dLoc, limitToSourceFiles); - declLoc = declLoc ?? dLoc; + declLoc ??= dLoc; return locs; }) - .Distinct().Select(AsLocation).ToArray(); // ToArray is needed here to force the execution before checking declLoc + .Select(AsLocation).ToArray(); // ToArray is needed here to force the execution before checking declLoc declarationLocation = declLoc == null ? null : AsLocation(declLoc.Item1, declLoc.Item2.Offset, declLoc.Item2.Range); return true; } @@ -218,8 +218,8 @@ internal static bool TryGetReferences( .SelectMany(spec => spec.Implementation is SpecializationImplementation.Provided impl && spec.Location.IsValue ? IdentifierReferences.Find(definition.Item.Item1, impl.Item2, file.FileName, spec.Location.Item.Offset) - : ImmutableArray.Empty) - .Distinct().Select(AsLocation); + : ImmutableHashSet.Empty) + .Select(AsLocation); } else // the given position corresponds to a variable declared as part of a specialization declaration or implementation { @@ -227,7 +227,7 @@ spec.Implementation is SpecializationImplementation.Provided impl && spec.Locati var statements = implementation.StatementsAfterDeclaration(defStart.Subtract(specPos)); var scope = new QsScope(statements.ToImmutableArray(), locals); var rootOffset = DiagnosticTools.AsTuple(specPos); - referenceLocations = IdentifierReferences.Find(definition.Item.Item1, scope, file.FileName, rootOffset).Distinct().Select(AsLocation); + referenceLocations = IdentifierReferences.Find(definition.Item.Item1, scope, file.FileName, rootOffset).Select(AsLocation); } declarationLocation = AsLocation(file.FileName, definition.Item.Item2, defRange); return true; diff --git a/src/QsCompiler/Core/TypeTransformation.fs b/src/QsCompiler/Core/TypeTransformation.fs index 8737747695..cc58d49419 100644 --- a/src/QsCompiler/Core/TypeTransformation.fs +++ b/src/QsCompiler/Core/TypeTransformation.fs @@ -132,4 +132,4 @@ type TypeTransformationBase(options : TransformationOptions) = | ExpressionType.Result -> this.OnResult () | ExpressionType.Pauli -> this.OnPauli () | ExpressionType.Range -> this.OnRange () - ResolvedType.New |> Node.BuildOr t transformed + (fun t -> ResolvedType.New (true, t)) |> Node.BuildOr t transformed diff --git a/src/QsCompiler/DataStructures/SyntaxTree.fs b/src/QsCompiler/DataStructures/SyntaxTree.fs index 87d83babb3..3d6e8b8fa1 100644 --- a/src/QsCompiler/DataStructures/SyntaxTree.fs +++ b/src/QsCompiler/DataStructures/SyntaxTree.fs @@ -661,6 +661,7 @@ type QsSpecialization = { with member this.AddAttribute att = {this with Attributes = this.Attributes.Add att} member this.WithImplementation impl = {this with Implementation = impl} + member this.WithParent (getName : Func<_,_>) = {this with Parent = getName.Invoke(this.Parent)} /// describes a Q# function, operation, or type constructor @@ -739,6 +740,7 @@ type QsCustomType = { } with member this.AddAttribute att = {this with Attributes = this.Attributes.Add att} + member this.WithFullName (getName : Func<_,_>) = {this with FullName = getName.Invoke(this.FullName)} /// Describes a valid Q# namespace element. diff --git a/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs index 8b47abeeb7..ac831ac137 100644 --- a/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs +++ b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs @@ -1,11 +1,9 @@ /// This file contains redefinitions of types and callables declared in Tests.Compiler\TestCases\AccessModifiers.qs. It /// is used as an assembly reference to test support for re-using names of inaccessible declarations in references. namespace Microsoft.Quantum.Testing.AccessModifiers { - // TODO: Uncomment these definitions when re-using names of inaccessible declarations in references is supported. + internal newtype InternalType = Unit; - // internal newtype InternalType = Unit; - - // internal function InternalFunction () : Unit {} + internal function InternalFunction () : Unit {} } /// This namespace contains additional definitions of types and callables meant to be used by the diff --git a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs index 5480b88ab3..16da96f20e 100644 --- a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs +++ b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs @@ -24,16 +24,19 @@ type AccessModifierTests (output) = let name = name |> NonNullable<_>.New this.Verify (QsQualifiedName.New (ns, name), diagnostics) + // Note: Since internal declarations in references are renamed, the error codes will be for unidentified callables + // and types instead of inaccessible ones. + [] member this.``Callables`` () = this.Expect "CallableUseOK" [] - this.Expect "CallableReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + this.Expect "CallableReferenceInternalInaccessible" [Error ErrorCode.UnknownIdentifier] [] member this.``Types`` () = this.Expect "TypeUseOK" [] - this.Expect "TypeReferenceInternalInaccessible" [Error ErrorCode.InaccessibleType] - this.Expect "TypeConstructorReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + this.Expect "TypeReferenceInternalInaccessible" [Error ErrorCode.UnknownType] + this.Expect "TypeConstructorReferenceInternalInaccessible" [Error ErrorCode.UnknownIdentifier] [] member this.``Callable signatures`` () = diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index 760730135e..257ee6e47c 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -5,7 +5,10 @@ namespace Microsoft.Quantum.QsCompiler.Testing open System open System.Collections.Generic +open System.Collections.Immutable open System.IO +open System.Linq +open Microsoft.VisualStudio.LanguageServer.Protocol open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.DataTypes @@ -15,6 +18,7 @@ open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations.IntrinsicResolution open Microsoft.Quantum.QsCompiler.Transformations.Monomorphization open Microsoft.Quantum.QsCompiler.Transformations.Monomorphization.Validation +open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace open Xunit open Xunit.Abstractions @@ -24,9 +28,51 @@ type LinkingTests (output:ITestOutputHelper) = let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message)) - let getTempFile () = new Uri(Path.GetFullPath(Path.GetRandomFileName())) + let getTempFile () = + // The file name needs to end in ".qs" so that it isn't ignored by the References.Headers class during the + // internal renaming tests. + new Uri(Path.GetFullPath(Path.GetRandomFileName() + ".qs")) + let getManager uri content = CompilationUnitManager.InitializeFileManager(uri, content, compilationManager.PublishDiagnostics, compilationManager.LogException) + let defaultOffset = { + Offset = DiagnosticTools.AsTuple (Position (0, 0)) + Range = QsCompilerDiagnostic.DefaultRange + } + + /// Counts the number of references to the qualified name in the headers and specialization implementations. The + /// declaration of the name is included in the count. + let countReferencesInHeaders (headers : References.Headers) (name : QsQualifiedName) = + let references = IdentifierReferences (name, defaultOffset) + references.SharedState.DeclarationOffset <- (0, 0) + + for callable in headers.Callables do + Seq.iter (references.Namespaces.OnAttribute >> ignore) callable.Attributes + references.Namespaces.OnArgumentTuple callable.ArgumentTuple |> ignore + references.Namespaces.OnSignature callable.Signature |> ignore + references.Namespaces.OnDocumentation callable.Documentation |> ignore + for (specialization, implementation) in headers.Specializations do + Seq.iter (references.Namespaces.OnAttribute >> ignore) specialization.Attributes + references.Namespaces.OnSpecializationImplementation implementation |> ignore + references.Namespaces.OnDocumentation specialization.Documentation |> ignore + for qsType in headers.Types do + Seq.iter (references.Namespaces.OnAttribute >> ignore) qsType.Attributes + references.Types.OnType qsType.Type |> ignore + references.Namespaces.OnTypeItems qsType.TypeItems |> ignore + references.Namespaces.OnDocumentation qsType.Documentation |> ignore + + let count = + headers.Callables.Count(fun callable -> callable.QualifiedName = name) + + headers.Specializations.Count(fun specialization -> (fst (specialization.ToTuple ())).Parent = name) + + headers.Types.Count(fun qsType -> qsType.QualifiedName = name) + + references.SharedState.Locations.Count + count + + let qualifiedName ns name = { + Namespace = NonNullable<_>.New ns + Name = NonNullable<_>.New name + } + do let addOrUpdateSourceFile filePath = getManager (new Uri(filePath)) (File.ReadAllText filePath) |> compilationManager.AddOrUpdateSourceFileAsync |> ignore Path.Combine ("TestCases", "LinkingTests", "Core.qs") |> Path.GetFullPath |> addOrUpdateSourceFile @@ -111,6 +157,36 @@ type LinkingTests (output:ITestOutputHelper) = |> Assert.True) |> ignore + /// Runs the nth internal renaming test, asserting that declarations with the given name and references to them have + /// been renamed across the compilation unit. + member private this.RunInternalRenamingTest num renamed notRenamed = + let countAll names headers = + names + |> Seq.map (countReferencesInHeaders headers) + |> Seq.sum + + let chunks = LinkingTests.ReadAndChunkSourceFile "InternalRenaming.qs" + let compilation = this.BuildContent chunks.[num - 1] + let dllSource = "InternalRenaming.dll" + let originalHeaders = + References.Headers (NonNullable.New dllSource, compilation.BuiltCompilation.Namespaces) + + let beforeCount = originalHeaders |> countAll (Seq.concat [renamed; notRenamed]) + + let references = + [KeyValuePair.Create(NonNullable<_>.New dllSource, originalHeaders)] + |> ImmutableDictionary.CreateRange + |> References + let newNames = + renamed |> Seq.map (0 |> FuncConvert.FuncFromTupled References.GetNewNameForInternal) + let afterCount = references.Declarations.Single().Value |> countAll (Seq.concat [newNames; notRenamed]) + + let afterCountOriginalName = references.Declarations.Single().Value |> countAll renamed + + Assert.NotEqual (0, beforeCount) + Assert.Equal (0, afterCountOriginalName) + Assert.Equal (beforeCount, afterCount) + [] member this.``Monomorphization`` () = @@ -245,3 +321,57 @@ type LinkingTests (output:ITestOutputHelper) = this.Expect "InvalidEntryPoint35" [Error ErrorCode.CallableTypeInEntryPointSignature; Error ErrorCode.UserDefinedTypeInEntryPointSignature] this.Expect "InvalidEntryPoint36" [Error ErrorCode.CallableTypeInEntryPointSignature; Error ErrorCode.UserDefinedTypeInEntryPointSignature] + + [] + member this.``Rename internal operation call references`` () = + this.RunInternalRenamingTest 1 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename internal function call references`` () = + this.RunInternalRenamingTest 2 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename internal type references`` () = + this.RunInternalRenamingTest 3 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName Signatures.InternalRenamingNs "Baz" + ] + [] + + [] + member this.``Rename internal references across namespaces`` () = + this.RunInternalRenamingTest 4 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Qux" + ] + [qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Baz"] + + [] + member this.``Rename internal qualified references`` () = + this.RunInternalRenamingTest 5 + [ + qualifiedName Signatures.InternalRenamingNs "Foo" + qualifiedName Signatures.InternalRenamingNs "Bar" + qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Qux" + ] + [qualifiedName (Signatures.InternalRenamingNs + ".Extra") "Baz"] + + [] + member this.``Rename internal attribute references`` () = + this.RunInternalRenamingTest 6 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] + + [] + member this.``Rename specializations for internal operations`` () = + this.RunInternalRenamingTest 7 + [qualifiedName Signatures.InternalRenamingNs "Foo"] + [qualifiedName Signatures.InternalRenamingNs "Bar"] diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs new file mode 100644 index 0000000000..3f67ca815a --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Test 1: Rename internal operation call references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit { + } + + operation Bar () : Unit { + Foo(); + } +} + +// ================================= +// Test 2: Rename internal function call references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal function Foo () : Int { + return 42; + } + + function Bar () : Unit { + let x = Foo(); + let y = 1 + (Foo() - 5); + let z = (Foo(), 9); + } +} + +// ================================= +// Test 3: Rename internal type references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal newtype Bar = (Int, Foo); + + internal function Baz (x : Foo) : Foo { + return Foo(); + } +} + +// ================================= +// Test 4: Rename internal references across namespaces + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal function Bar () : Unit { + } +} + +namespace Microsoft.Quantum.Testing.InternalRenaming.Extra { + open Microsoft.Quantum.Testing.InternalRenaming; + + function Baz () : Unit { + return Bar(); + } + + internal function Qux (x : Foo) : Foo { + return x; + } +} + +// ================================= +// Test 5: Rename internal qualified references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal newtype Foo = Unit; + + internal function Bar () : Unit { + } +} + +namespace Microsoft.Quantum.Testing.InternalRenaming.Extra { + function Baz () : Unit { + return Microsoft.Quantum.Testing.InternalRenaming.Bar(); + } + + internal function Qux (x : Microsoft.Quantum.Testing.InternalRenaming.Foo) + : Microsoft.Quantum.Testing.InternalRenaming.Foo { + return x; + } +} + +// ================================= +// Test 6: Rename internal attribute references + +namespace Microsoft.Quantum.Testing.InternalRenaming { + @Attribute() + internal newtype Foo = Unit; + + @Foo() + function Bar () : Unit { + } +} + +// ================================= +// Test 7: Rename specializations for internal operations + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit is Adj { + body { + } + + adjoint { + } + } + + operation Bar () : Unit { + Foo(); + Adjoint Foo(); + } +} diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs index e7fd7462ac..5a8be35942 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs @@ -113,6 +113,7 @@ let public MonomorphizationNs = "Microsoft.Quantum.Testing.Monomorphization" let public GenericsNs = "Microsoft.Quantum.Testing.Generics" let public IntrinsicResolutionNs = "Microsoft.Quantum.Testing.IntrinsicResolution" let public ClassicalControlNs = "Microsoft.Quantum.Testing.ClassicalControl" +let public InternalRenamingNs = "Microsoft.Quantum.Testing.InternalRenaming" /// Expected callable signatures to be found when running Monomorphization tests let public MonomorphizationSignatures = diff --git a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj index 53a48775d0..293938f255 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -47,6 +47,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/QsCompiler/Transformations/SearchAndReplace.cs b/src/QsCompiler/Transformations/SearchAndReplace.cs index e12ed5dd81..82f3ead34f 100644 --- a/src/QsCompiler/Transformations/SearchAndReplace.cs +++ b/src/QsCompiler/Transformations/SearchAndReplace.cs @@ -84,7 +84,7 @@ public override int GetHashCode() public class TransformationState { public Tuple, QsLocation> DeclarationLocation { get; internal set; } - public ImmutableList Locations { get; private set; } + public ImmutableHashSet Locations { get; private set; } /// /// Whenever DeclarationOffset is set, the current statement offset is set to this default value. @@ -101,7 +101,7 @@ internal TransformationState(Func trackId, { this.TrackIdentifier = trackId ?? throw new ArgumentNullException(nameof(trackId)); this.RelevantSourseFiles = limitToSourceFiles; - this.Locations = ImmutableList.Empty; + this.Locations = ImmutableHashSet.Empty; this.DefaultOffset = defaultOffset; } @@ -168,7 +168,7 @@ public IdentifierReferences(QsQualifiedName idName, QsLocation defaultOffset, II // static methods for convenience - public static IEnumerable Find(NonNullable idName, QsScope scope, + public static ImmutableHashSet Find(NonNullable idName, QsScope scope, NonNullable sourceFile, Tuple rootLoc) { var finder = new IdentifierReferences(idName, null, ImmutableHashSet.Create(sourceFile)); @@ -178,7 +178,7 @@ public static IEnumerable Find(NonNullable idName, QsScope sco return finder.SharedState.Locations; } - public static IEnumerable Find(QsQualifiedName idName, QsNamespace ns, QsLocation defaultOffset, + public static ImmutableHashSet Find(QsQualifiedName idName, QsNamespace ns, QsLocation defaultOffset, out Tuple, QsLocation> declarationLocation, IImmutableSet> limitToSourceFiles = null) { var finder = new IdentifierReferences(idName, defaultOffset, limitToSourceFiles); @@ -446,6 +446,173 @@ public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable + /// A transformation that renames all references to each given qualified name. + /// + public class RenameReferences : SyntaxTreeTransformation + { + private readonly TransformationState state; + + /// + /// Creates a new rename references transformation. + /// + /// The mapping from existing names to new names. + public RenameReferences(IImmutableDictionary names) + { + state = new TransformationState(names); + Types = new TypeTransformation(this); + ExpressionKinds = new ExpressionKindTransformation(this); + Namespaces = new NamespaceTransformation(this); + } + + /// + /// Renames references in the callable declaration header, including the name of the callable itself. + /// + /// The callable declaration header in which to rename references. + /// The callable declaration header with renamed references. + public CallableDeclarationHeader OnCallableDeclarationHeader(CallableDeclarationHeader callable) => + new CallableDeclarationHeader( + kind: callable.Kind, + qualifiedName: state.GetNewName(callable.QualifiedName), + attributes: callable.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + modifiers: callable.Modifiers, + sourceFile: callable.SourceFile, + position: callable.Position, + symbolRange: callable.SymbolRange, + argumentTuple: Namespaces.OnArgumentTuple(callable.ArgumentTuple), + signature: Namespaces.OnSignature(callable.Signature), + documentation: Namespaces.OnDocumentation(callable.Documentation)); + + /// + /// Renames references in the specialization declaration header, including the name of the specialization + /// itself. + /// + /// The specialization declaration header in which to rename references. + /// The specialization declaration header with renamed references. + public SpecializationDeclarationHeader OnSpecializationDeclarationHeader( + SpecializationDeclarationHeader specialization) + { + var typeArguments = + specialization.TypeArguments.IsValue + ? QsNullable>.NewValue( + specialization.TypeArguments.Item.Select(Types.OnType).ToImmutableArray()) + : QsNullable>.Null; + return new SpecializationDeclarationHeader( + kind: specialization.Kind, + typeArguments: typeArguments, + information: specialization.Information, + parent: state.GetNewName(specialization.Parent), + attributes: specialization.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + sourceFile: specialization.SourceFile, + position: specialization.Position, + headerRange: specialization.HeaderRange, + documentation: Namespaces.OnDocumentation(specialization.Documentation)); + } + + /// + /// Renames references in the type declaration header, including the name of the type itself. + /// + /// The type declaration header in which to rename references. + /// The type declaration header with renamed references. + public TypeDeclarationHeader OnTypeDeclarationHeader(TypeDeclarationHeader type) + { + return new TypeDeclarationHeader( + qualifiedName: state.GetNewName(type.QualifiedName), + attributes: type.Attributes.Select(Namespaces.OnAttribute).ToImmutableArray(), + modifiers: type.Modifiers, + sourceFile: type.SourceFile, + position: type.Position, + symbolRange: type.SymbolRange, + type: Types.OnType(type.Type), + typeItems: Namespaces.OnTypeItems(type.TypeItems), + documentation: Namespaces.OnDocumentation(type.Documentation)); + } + + private class TransformationState + { + private readonly IImmutableDictionary names; + + internal TransformationState(IImmutableDictionary names) => + this.names = names; + + /// + /// Gets the renamed version of the qualified name if one exists; otherwise, returns the original name. + /// + /// The qualified name to rename. + /// + /// The renamed version of the qualified name if one exists; otherwise, returns the original name. + /// + internal QsQualifiedName GetNewName(QsQualifiedName name) => names.GetValueOrDefault(name) ?? name; + + /// + /// Gets the renamed version of the user-defined type if one exists; otherwise, returns the original one. + /// + /// + /// The renamed version of the user-defined type if one exists; otherwise, returns the original one. + /// + internal UserDefinedType RenameUdt(UserDefinedType udt) + { + var newName = GetNewName(new QsQualifiedName(udt.Namespace, udt.Name)); + return new UserDefinedType(newName.Namespace, newName.Name, udt.Range); + } + } + + private class TypeTransformation : Core.TypeTransformation + { + private readonly TransformationState state; + + public TypeTransformation(RenameReferences parent) : base(parent) => state = parent.state; + + public override QsTypeKind OnUserDefinedType(UserDefinedType udt) => + base.OnUserDefinedType(state.RenameUdt(udt)); + + public override QsTypeKind OnTypeParameter(QsTypeParameter tp) => + base.OnTypeParameter(new QsTypeParameter(state.GetNewName(tp.Origin), tp.TypeName, tp.Range)); + } + + private class ExpressionKindTransformation : Core.ExpressionKindTransformation + { + private readonly TransformationState state; + + public ExpressionKindTransformation(RenameReferences parent) : base(parent) => state = parent.state; + + public override QsExpressionKind OnIdentifier(Identifier id, QsNullable> typeArgs) + { + if (id is Identifier.GlobalCallable global) + { + id = Identifier.NewGlobalCallable(state.GetNewName(global.Item)); + } + return base.OnIdentifier(id, typeArgs); + } + } + + private class NamespaceTransformation : Core.NamespaceTransformation + { + private readonly TransformationState state; + + public NamespaceTransformation(RenameReferences parent) : base(parent) => state = parent.state; + + public override QsDeclarationAttribute OnAttribute(QsDeclarationAttribute attribute) + { + var argument = Transformation.Expressions.OnTypedExpression(attribute.Argument); + var typeId = attribute.TypeId.IsValue + ? QsNullable.NewValue(state.RenameUdt(attribute.TypeId.Item)) + : attribute.TypeId; + return base.OnAttribute( + new QsDeclarationAttribute(typeId, argument, attribute.Offset, attribute.Comments)); + } + + public override QsCallable OnCallableDeclaration(QsCallable callable) => + base.OnCallableDeclaration(callable.WithFullName(state.GetNewName)); + + public override QsCustomType OnTypeDeclaration(QsCustomType type) => + base.OnTypeDeclaration(type.WithFullName(state.GetNewName)); + + public override QsSpecialization OnSpecializationDeclaration(QsSpecialization spec) => + base.OnSpecializationDeclaration(spec.WithParent(state.GetNewName)); + } + } + // general purpose helpers