diff --git a/src/QsCompiler/CommandLineTool/LoadContext.cs b/src/QsCompiler/CommandLineTool/LoadContext.cs index 1f9284ca54..db5ec2a15e 100644 --- a/src/QsCompiler/CommandLineTool/LoadContext.cs +++ b/src/QsCompiler/CommandLineTool/LoadContext.cs @@ -23,7 +23,7 @@ namespace Microsoft.Quantum.QsCompiler public class LoadContext : AssemblyLoadContext { public readonly string PathToParentAssembly; - private AssemblyDependencyResolver _Resolver; + private readonly AssemblyDependencyResolver _Resolver; private readonly HashSet _FallbackPaths; /// @@ -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 139c6302a1..9c824bb4c6 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -5,13 +5,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.IO; 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; @@ -119,11 +123,17 @@ public References(ImmutableDictionary, Headers> refs, Action this.Declarations = refs ?? throw new ArgumentNullException(nameof(refs)); if (onError == null) return; - var conflictingCallables = refs.Values.SelectMany(r => r.Callables) - .GroupBy(c => c.QualifiedName).Where(g => g.Count() != 1) + var conflictingCallables = refs.Values + .SelectMany(r => r.Callables) + .Where(c => Namespace.IsDeclarationAccessible(false, c.Modifiers.Access)) + .GroupBy(c => c.QualifiedName) + .Where(g => g.Count() != 1) .Select(g => (g.Key, String.Join(", ", g.Select(c => c.SourceFile.Value)))); - var conflictingTypes = refs.Values.SelectMany(r => r.Types) - .GroupBy(t => t.QualifiedName).Where(g => g.Count() != 1) + var conflictingTypes = refs.Values + .SelectMany(r => r.Types) + .Where(t => Namespace.IsDeclarationAccessible(false, t.Modifiers.Access)) + .GroupBy(t => t.QualifiedName) + .Where(g => g.Count() != 1) .Select(g => (g.Key, String.Join(", ", g.Select(c => c.SourceFile.Value)))); foreach (var (name, conflicts) in conflictingCallables.Concat(conflictingTypes).Distinct()) @@ -143,6 +153,8 @@ public References(ImmutableDictionary, Headers> refs, Action /// public class CompilationUnit : IReaderWriterLock, IDisposable { + internal static readonly NameDecorator ReferenceDecorator = new NameDecorator("QsReference"); + internal References Externals { get; private set; } internal NamespaceManager GlobalSymbols { get; private set; } private readonly Dictionary CompiledCallables; @@ -344,8 +356,17 @@ internal void UpdateTypes(IEnumerable updates) var compilationExists = this.CompiledTypes.TryGetValue(fullName, out QsCustomType compiled); if (!compilationExists) continue; // may happen if a file has been modified during global type checking - var type = new QsCustomType(compiled.FullName, compiled.Attributes, compiled.SourceFile, header.Location, - compiled.Type, compiled.TypeItems, compiled.Documentation, compiled.Comments); + var type = new QsCustomType( + compiled.FullName, + compiled.Attributes, + compiled.Modifiers, + compiled.SourceFile, + header.Location, + compiled.Type, + compiled.TypeItems, + compiled.Documentation, + compiled.Comments + ); this.CompiledTypes[fullName] = type; } } @@ -396,8 +417,19 @@ internal void UpdateCallables(IEnumerable updates) var defaultSpec = new QsSpecialization(QsSpecializationKind.QsBody, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, QsNullable>.Null, header.Signature, SpecializationImplementation.Intrinsic, ImmutableArray.Empty, QsComments.Empty); - this.CompiledCallables[fullName] = new QsCallable(header.Kind, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Signature, header.ArgumentTuple, ImmutableArray.Create(defaultSpec), header.Documentation, QsComments.Empty); + this.CompiledCallables[fullName] = new QsCallable( + header.Kind, + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Signature, + header.ArgumentTuple, + ImmutableArray.Create(defaultSpec), + header.Documentation, + QsComments.Empty + ); continue; } @@ -422,8 +454,19 @@ internal void UpdateCallables(IEnumerable updates) }) .Where(spec => spec != null).ToImmutableArray(); - var callable = new QsCallable(compiled.Kind, compiled.FullName, compiled.Attributes, compiled.SourceFile, header.Location, - compiled.Signature, compiled.ArgumentTuple, specializations, compiled.Documentation, compiled.Comments); + var callable = new QsCallable( + compiled.Kind, + compiled.FullName, + compiled.Attributes, + compiled.Modifiers, + compiled.SourceFile, + header.Location, + compiled.Signature, + compiled.ArgumentTuple, + specializations, + compiled.Documentation, + compiled.Comments + ); this.CompiledCallables[fullName] = callable; } } @@ -440,22 +483,46 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) { // TODO: this needs to be adapted if we want to support external specializations if (header == null) throw new ArgumentNullException(nameof(header)); - var importedSpecs = this.GlobalSymbols.ImportedSpecializations(header.QualifiedName); - var definedSpecs = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName); - QsCompilerError.Verify(definedSpecs.Length == 0, "external specializations are currently not supported"); - var specializations = importedSpecs.Select(imported => + if (Namespace.IsDeclarationAccessible(false, header.Modifiers.Access)) { - var (specHeader, implementation) = imported; - var specSignature = specHeader.Kind.IsQsControlled || specHeader.Kind.IsQsControlledAdjoint - ? SyntaxGenerator.BuildControlled(header.Signature) - : header.Signature; - return new QsSpecialization(specHeader.Kind, header.QualifiedName, specHeader.Attributes, - specHeader.SourceFile, specHeader.Location, specHeader.TypeArguments, specSignature, - implementation, specHeader.Documentation, QsComments.Empty); - }) - .ToImmutableArray(); - return new QsCallable(header.Kind, header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Signature, header.ArgumentTuple, specializations, header.Documentation, QsComments.Empty); + var definedSpecs = this.GlobalSymbols.DefinedSpecializations(header.QualifiedName); + QsCompilerError.Verify(definedSpecs.Length == 0, + "external specializations are currently not supported"); + } + + var specializations = + this.GlobalSymbols + .ImportedSpecializations(header.QualifiedName) + .Where(specialization => + // Either the callable is externally accessible, or all of its specializations must be defined in + // the same reference as the callable. + Namespace.IsDeclarationAccessible(false, header.Modifiers.Access) || + specialization.Item1.SourceFile.Equals(header.SourceFile)) + .Select(specialization => + { + var (specHeader, implementation) = specialization; + var specSignature = specHeader.Kind.IsQsControlled || specHeader.Kind.IsQsControlledAdjoint + ? SyntaxGenerator.BuildControlled(header.Signature) + : header.Signature; + return new QsSpecialization( + specHeader.Kind, header.QualifiedName, specHeader.Attributes, specHeader.SourceFile, + specHeader.Location, specHeader.TypeArguments, specSignature, implementation, + specHeader.Documentation, QsComments.Empty); + }) + .ToImmutableArray(); + return new QsCallable( + header.Kind, + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Signature, + header.ArgumentTuple, + specializations, + header.Documentation, + QsComments.Empty + ); } /// @@ -465,8 +532,17 @@ private QsCallable GetImportedCallable(CallableDeclarationHeader header) private QsCustomType GetImportedType(TypeDeclarationHeader header) { if (header == null) throw new ArgumentNullException(nameof(header)); - return new QsCustomType(header.QualifiedName, header.Attributes, header.SourceFile, header.Location, - header.Type, header.TypeItems, header.Documentation, QsComments.Empty); + return new QsCustomType( + header.QualifiedName, + header.Attributes, + header.Modifiers, + header.SourceFile, + header.Location, + header.Type, + header.TypeItems, + header.Documentation, + QsComments.Empty + ); } /// @@ -483,8 +559,8 @@ public static ImmutableArray NewSyntaxTree( if (types == null) throw new ArgumentNullException(nameof(types)); var emptyLookup = new NonNullable[0].ToLookup(ns => ns, _ => ImmutableArray.Empty); - string QualifiedName(QsQualifiedName fullName) => $"{fullName.Namespace.Value}.{fullName.Name.Value}"; - string ElementName(QsNamespaceElement e) => + static string QualifiedName(QsQualifiedName fullName) => $"{fullName.Namespace.Value}.{fullName.Name.Value}"; + static string ElementName(QsNamespaceElement e) => e is QsNamespaceElement.QsCustomType t ? QualifiedName(t.Item.FullName) : e is QsNamespaceElement.QsCallable c ? QualifiedName(c.Item.FullName) : null; var namespaceElements = callables.Select(c => (c.FullName.Namespace, QsNamespaceElement.NewQsCallable(c))) @@ -532,10 +608,12 @@ public QsCompilation Build() } // build the syntax tree - var callables = this.CompiledCallables.Values.Concat(this.GlobalSymbols.ImportedCallables().Select(this.GetImportedCallable)); var types = this.CompiledTypes.Values.Concat(this.GlobalSymbols.ImportedTypes().Select(this.GetImportedType)); - var tree = CompilationUnit.NewSyntaxTree(callables, types, this.GlobalSymbols.Documentation()); + // Rename imported internal declarations by tagging them with their source file to avoid potentially + // having duplicate names in the syntax tree. + var (taggedCallables, taggedTypes) = TagImportedInternalNames(callables, types); + var tree = NewSyntaxTree(taggedCallables, taggedTypes, this.GlobalSymbols.Documentation()); var entryPoints = tree.Callables().Where(c => c.Attributes.Any(BuiltIn.MarksEntryPoint)).Select(c => c.FullName).ToImmutableArray(); return new QsCompilation(tree, entryPoints); } @@ -609,7 +687,7 @@ LocalDeclarations TryGetLocalDeclarations() } if (parentCallable == null) return LocalDeclarations.Empty; - declarations = declarations ?? TryGetLocalDeclarations(); + declarations ??= TryGetLocalDeclarations(); Tuple AbsolutePosition(QsNullable> relOffset) => relOffset.IsNull @@ -635,5 +713,51 @@ internal LocalDeclarations TryGetLocalDeclarations(FileContentManager file, Posi var declarations = implementation?.LocalDeclarationsAt(pos.Subtract(specPos), includeDeclaredAtPosition); return this.PositionedDeclarations(parentCallable, callablePos, specPos, declarations); } + + /// + /// Tags the names of imported internal callables and types with a unique identifier based on the path to their + /// assembly, so that they do not conflict with callables and types defined locally. Renames all references to + /// the tagged declarations found in the given callables and types. + /// + /// The callables to rename and update references in. + /// The types to rename and update references in. + /// The tagged callables and types with references renamed everywhere. + private (IEnumerable, IEnumerable) + TagImportedInternalNames(IEnumerable callables, IEnumerable types) + { + // Assign a unique ID to each reference. + var ids = + callables.Select(callable => callable.SourceFile.Value) + .Concat(types.Select(type => type.SourceFile.Value)) + .Distinct() + .Where(source => Externals.Declarations.ContainsKey(NonNullable.New(source))) + .Select((source, index) => (source, index)) + .ToImmutableDictionary(item => item.source, item => item.index); + + ImmutableDictionary GetMappingForSourceGroup( + IGrouping group) => + group + .Where(item => + !Namespace.IsDeclarationAccessible(false, item.access) && + Externals.Declarations.ContainsKey(NonNullable.New(item.source))) + .ToImmutableDictionary(item => item.name, + item => ReferenceDecorator.Decorate(item.name, ids[item.source])); + + var transformations = + callables.Select(callable => + (name: callable.FullName, source: callable.SourceFile.Value, access: callable.Modifiers.Access)) + .Concat(types.Select(type => + (name: type.FullName, source: type.SourceFile.Value, access: type.Modifiers.Access))) + .GroupBy(item => item.source) + .ToImmutableDictionary( + group => group.Key, + group => new RenameReferences(GetMappingForSourceGroup(group))); + + var taggedCallables = callables.Select( + callable => transformations[callable.SourceFile.Value].Namespaces.OnCallableDeclaration(callable)); + var taggedTypes = types.Select( + type => transformations[type.SourceFile.Value].Namespaces.OnTypeDeclaration(type)); + return (taggedCallables, taggedTypes); + } } } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs index 3cb4f0eed4..6276a8676f 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs @@ -10,6 +10,7 @@ using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.TextProcessing; using Microsoft.Quantum.QsCompiler.Transformations.QsCodeOutput; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -38,9 +39,13 @@ private static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, para } /// - /// Returns all namespaces in which a callable with the name of the symbol at the given position in the given file belongs to. - /// Returns an empty collection if any of the arguments is null or if no unqualified symbol exists at that location. - /// Returns the name of the identifier as out parameter if an unqualified symbol exists at that location. + /// Returns all namespaces in which a callable with the name of the symbol at the given position in the given + /// file belongs to. + /// + /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that + /// location, or if the position is not part of a namespace. + /// + /// Returns the name of the identifier as an out parameter if an unqualified symbol exists at that location. /// private static IEnumerable> NamespaceSuggestionsForIdAtPosition (this FileContentManager file, Position pos, CompilationUnit compilation, out string idName) @@ -53,9 +58,13 @@ private static IEnumerable> NamespaceSuggestionsForIdAtPosit } /// - /// Returns all namespaces in which a type with the name of the symbol at the given position in the given file belongs to. - /// Returns an empty collection if any of the arguments is null or if no unqualified symbol exists at that location. - /// Returns the name of the type as out parameter if an unqualified symbol exists at that location. + /// Returns all namespaces in which a type with the name of the symbol at the given position in the given file + /// belongs to. + /// + /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that + /// location, or if the position is not part of a namespace. + /// + /// Returns the name of the type as an out parameter if an unqualified symbol exists at that location. /// private static IEnumerable> NamespaceSuggestionsForTypeAtPosition (this FileContentManager file, Position pos, CompilationUnit compilation, out string typeName) @@ -233,9 +242,9 @@ static string CharacteristicsAnnotation(Characteristics c) static IEnumerable GetCharacteristics(QsTuple> argTuple) => SyntaxGenerator.ExtractItems(argTuple).SelectMany(item => item.Item2.ExtractCharacteristics()).Distinct(); var characteristicsInFragment = - fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item2.Argument) : - fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item2.Argument) : - fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item2) : + fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item3.Argument) : + fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item3.Argument) : + fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item3) : Enumerable.Empty(); //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment); @@ -294,10 +303,25 @@ static IEnumerable GetCharacteristics(QsTuple SuggestionsForIndexRange (this FileContentManager file, CompilationUnit compilation, LSP.Range range) { - if (file == null || compilation == null || range?.Start == null) return Enumerable.Empty<(string, WorkspaceEdit)>(); - var indexRangeNamespaces = compilation.GlobalSymbols.NamespacesContainingCallable(BuiltIn.IndexRange.FullName.Name); - if (!indexRangeNamespaces.Contains(BuiltIn.IndexRange.FullName.Namespace)) return Enumerable.Empty<(string, WorkspaceEdit)>(); - var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.FullName.Namespace); + if (file == null || compilation == null || range?.Start == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } + + // Ensure that the IndexRange library function exists in this compilation unit. + var nsName = file.TryGetNamespaceAt(range.Start); + if (nsName == null) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } + var indexRange = compilation.GlobalSymbols.TryGetCallable( + new QsQualifiedName(BuiltIn.IndexRange.FullName.Namespace, BuiltIn.IndexRange.FullName.Name), + NonNullable.New(nsName), + file.FileName); + if (!indexRange.IsFound) + { + return Enumerable.Empty<(string, WorkspaceEdit)>(); + } /// Returns true the given expression is of the form "0 .. Length(args) - 1", /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters. @@ -343,8 +367,9 @@ static IEnumerable IndexRangeEdits(CodeFragment fragment) var fragments = file.FragmentsOverlappingWithRange(range); var edits = fragments.SelectMany(IndexRangeEdits); - return edits.Any() - ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) } + var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.FullName.Namespace); + return edits.Any() + ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) } : Enumerable.Empty<(string, WorkspaceEdit)>(); } @@ -437,10 +462,11 @@ static bool EmptyOrFirstAttribute(IEnumerable line, out CodeFragme var docString = $"{docPrefix}# Summary{endLine}{docPrefix}{endLine}"; var (argTuple, typeParams) = - callableDecl.IsValue ? (callableDecl.Item.Item2.Item2.Argument, callableDecl.Item.Item2.Item2.TypeParameters) : - typeDecl.IsValue ? (typeDecl.Item.Item2, ImmutableArray.Empty) : - (null, ImmutableArray.Empty); - var hasOutput = callableDecl.IsValue && !callableDecl.Item.Item2.Item2.ReturnType.Type.IsUnitType; + callableDecl.IsValue ? (callableDecl.Item.Item2.Item3.Argument, + callableDecl.Item.Item2.Item3.TypeParameters) + : typeDecl.IsValue ? (typeDecl.Item.Item2.Item2, ImmutableArray.Empty) + : (null, ImmutableArray.Empty); + var hasOutput = callableDecl.IsValue && !callableDecl.Item.Item2.Item3.ReturnType.Type.IsUnitType; var args = argTuple == null ? ImmutableArray>.Empty : SyntaxGenerator.ExtractItems(argTuple); docString = String.Concat( diff --git a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs index d94372d85c..4ec82b6449 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/CodeCompletion.cs @@ -3,6 +3,7 @@ using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -310,7 +311,8 @@ private static IEnumerable GetLocalCompletions( } /// - /// Returns completions for all callables visible given the current namespace and the list of open namespaces. + /// Returns completions for all accessible callables given the current namespace and the list of open + /// namespaces, or an empty enumerable if symbols haven't been resolved yet. /// /// /// Thrown when any argument except is null. @@ -331,9 +333,9 @@ private static IEnumerable GetCallableCompletions( if (!compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); return - compilation.GlobalSymbols.DefinedCallables() - .Concat(compilation.GlobalSymbols.ImportedCallables()) - .Where(callable => IsVisible(callable.QualifiedName, currentNamespace, openNamespaces)) + compilation.GlobalSymbols.AccessibleCallables() + .Where(callable => + IsAccessibleAsUnqualifiedName(callable.QualifiedName, currentNamespace, openNamespaces)) .Select(callable => new CompletionItem() { Label = callable.QualifiedName.Name.Value, @@ -348,7 +350,8 @@ private static IEnumerable GetCallableCompletions( } /// - /// Returns completions for all types visible given the current namespace and the list of open namespaces. + /// Returns completions for all accessible types given the current namespace and the list of open namespaces, or + /// an empty enumerable if symbols haven't been resolved yet. /// /// /// Thrown when any argument except is null. @@ -369,9 +372,8 @@ private static IEnumerable GetTypeCompletions( if (!compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); return - compilation.GlobalSymbols.DefinedTypes() - .Concat(compilation.GlobalSymbols.ImportedTypes()) - .Where(type => IsVisible(type.QualifiedName, currentNamespace, openNamespaces)) + compilation.GlobalSymbols.AccessibleTypes() + .Where(type => IsAccessibleAsUnqualifiedName(type.QualifiedName, currentNamespace, openNamespaces)) .Select(type => new CompletionItem() { Label = type.QualifiedName.Name.Value, @@ -385,7 +387,8 @@ private static IEnumerable GetTypeCompletions( } /// - /// Returns completions for all named items in any type. + /// Returns completions for all named items in any accessible type, or an empty enumerable if symbols haven't + /// been resolved yet. /// /// Thrown when the argument is null. private static IEnumerable GetNamedItemCompletions(CompilationUnit compilation) @@ -395,8 +398,7 @@ private static IEnumerable GetNamedItemCompletions(CompilationUn if (!compilation.GlobalSymbols.ContainsResolutions) return Array.Empty(); - return compilation.GlobalSymbols.DefinedTypes() - .Concat(compilation.GlobalSymbols.ImportedTypes()) + return compilation.GlobalSymbols.AccessibleTypes() .SelectMany(type => ExtractItems(type.TypeItems)) .Where(item => item.IsNamed) .Select(item => new CompletionItem() @@ -438,8 +440,8 @@ private static IEnumerable GetGlobalNamespaceCompletions( } /// - /// Returns completions for namespace aliases with the given prefix that are visible at the given position in - /// the file. + /// Returns completions for namespace aliases with the given prefix that are accessible from the given position + /// in the file. /// /// Note: a dot will be added after the given prefix if it is not the empty string, and doesn't already end with /// a dot. @@ -499,9 +501,9 @@ private static string TryGetDocumentation( { case CompletionItemKind.Function: case CompletionItemKind.Constructor: - var callable = compilation.GlobalSymbols.TryGetCallable( + var result = compilation.GlobalSymbols.TryGetCallable( data.QualifiedName, data.QualifiedName.Namespace, NonNullable.New(data.SourceFile)); - if (callable.IsNull) + if (!(result is ResolutionResult.Found callable)) return null; var signature = callable.Item.PrintSignature(); var documentation = callable.Item.Documentation.PrintSummary(useMarkdown); @@ -510,15 +512,15 @@ private static string TryGetDocumentation( var type = compilation.GlobalSymbols.TryGetType( data.QualifiedName, data.QualifiedName.Namespace, NonNullable.New(data.SourceFile)) - .Item; - return type?.Documentation.PrintSummary(useMarkdown).Trim(); + as ResolutionResult.Found; + return type?.Item.Documentation.PrintSummary(useMarkdown).Trim(); default: return null; } } /// - /// Returns the names of all namespaces that have been opened without an alias and are visible from the given + /// Returns the names of all namespaces that have been opened without an alias and are accessible from the given /// position in the file. Returns an empty enumerator if the position is invalid. /// /// Thrown when any argument is null. @@ -714,14 +716,15 @@ private static CompletionList ToCompletionList(this IEnumerable }; /// - /// Returns true if the qualified name is visible given the current namespace and a list of open namespaces. + /// Returns true if the declaration with the given qualified name would be accessible if it was referenced using + /// its unqualified name, given the current namespace and a list of open namespaces. /// - /// Names that start with "_" are treated as "private"; they are only visible from the namespace in which they - /// are declared. + /// Note: Names that start with "_" are treated as "private;" they are only accessible from the namespace in + /// which they are declared. /// - private static bool IsVisible(QsQualifiedName qualifiedName, - string currentNamespace, - IEnumerable openNamespaces) => + private static bool IsAccessibleAsUnqualifiedName(QsQualifiedName qualifiedName, + string currentNamespace, + IEnumerable openNamespaces) => openNamespaces.Contains(qualifiedName.Namespace.Value) && (!qualifiedName.Name.Value.StartsWith("_") || qualifiedName.Namespace.Value == currentNamespace); } diff --git a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs index 0a0750f4a7..5adda7ec1e 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SymbolManagement; using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; @@ -248,13 +249,28 @@ List FunctorApplications(ref QsExpression ex) // extracting and adapting the relevant information for the called callable - var ns = NonNullable.New(nsName); - var methodDecl = id.Item1.Symbol is QsSymbolKind.Symbol sym - ? compilation.GlobalSymbols.TryResolveAndGetCallable(sym.Item, ns, file.FileName).Item1 - : id.Item1.Symbol is QsSymbolKind.QualifiedSymbol qualSym - ? compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualSym.Item1, qualSym.Item2), ns, file.FileName) - : QsNullable.Null; - if (methodDecl.IsNull) return null; + ResolutionResult.Found methodDecl = null; + if (id.Item1.Symbol is QsSymbolKind.Symbol sym) + { + methodDecl = + compilation.GlobalSymbols.TryResolveAndGetCallable(sym.Item, + NonNullable.New(nsName), + file.FileName) + as ResolutionResult.Found; + } + else if (id.Item1.Symbol is QsSymbolKind.QualifiedSymbol qualSym) + { + methodDecl = + compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualSym.Item1, qualSym.Item2), + NonNullable.New(nsName), + file.FileName) + as ResolutionResult.Found; + } + + if (methodDecl == null) + { + return null; + } var (documentation, argTuple) = (methodDecl.Item.Documentation, methodDecl.Item.ArgumentTuple); var nrCtlApplications = functors.Where(f => f.Equals(QsFunctor.Controlled)).Count(); diff --git a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs index 501d7d73c7..19a916ddc4 100644 --- a/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs +++ b/src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs @@ -142,10 +142,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; } @@ -188,17 +188,20 @@ internal static bool TryGetReferences( if (nsName == null) return false; var ns = NonNullable.New(nsName); - QsQualifiedName fullName = null; + var result = ResolutionResult.NotFound; if (sym.Symbol is QsSymbolKind.Symbol name) { - var header = compilation.GlobalSymbols.TryResolveAndGetCallable(name.Item, ns, file.FileName).Item1; - if (header.IsValue) fullName = header.Item.QualifiedName; + result = compilation.GlobalSymbols.TryResolveAndGetCallable(name.Item, ns, file.FileName); } - if (sym.Symbol is QsSymbolKind.QualifiedSymbol qualName) + else if (sym.Symbol is QsSymbolKind.QualifiedSymbol qualifiedName) { - var header = compilation.GlobalSymbols.TryGetCallable(new QsQualifiedName(qualName.Item1, qualName.Item2), ns, file.FileName); - if (header.IsValue) fullName = header.Item.QualifiedName; + result = compilation.GlobalSymbols.TryGetCallable( + new QsQualifiedName(qualifiedName.Item1, qualifiedName.Item2), + ns, + file.FileName); } + var fullName = result is ResolutionResult.Found header ? header.Item.QualifiedName : null; + return compilation.TryGetReferences(fullName, out declarationLocation, out referenceLocations, limitToSourceFiles); } @@ -214,8 +217,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 { @@ -223,7 +226,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/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 011af90375..99934ce13e 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -111,13 +111,13 @@ internal static ImmutableArray DocumentingComments(this FileContentManag /// /// Returns the HeaderItems corresponding to all type declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>)> GetTypeDeclarationHeaderItems + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>>>)> GetTypeDeclarationHeaderItems (this FileContentManager file) => file.GetHeaderItems(file?.TypeDeclarationTokens(), frag => frag.Kind.DeclaredType(), null); /// /// Returns the HeaderItems corresponding to all callable declarations with a valid name in the given file, or null if the given file is null. /// - private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems + private static IEnumerable<(CodeFragment.TokenIndex, HeaderEntry>)> GetCallableDeclarationHeaderItems (this FileContentManager file) => file.GetHeaderItems(file?.CallableDeclarationTokens(), frag => frag.Kind.DeclaredCallable(), null); /// @@ -128,7 +128,7 @@ internal static ImmutableArray DocumentingComments(this FileContentManag /// The function returns Null if the Kind of the given fragment is null. /// private static QsNullable)>> SpecializationDeclaration - (HeaderEntry> parent, CodeFragment fragment) + (HeaderEntry> parent, CodeFragment fragment) { var specDecl = fragment.Kind?.DeclaredSpecialization(); var Null = QsNullable)>>.Null; @@ -226,7 +226,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // add all type declarations var typesToCompile = AddItems(file.GetTypeDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddType(file.FileName, Location(pos, name.Item2), name, decl, att, doc), + (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddType(file.FileName, Location(pos, name.Item2), name, decl.Item2, att, decl.Item1, doc), file.FileName.Value, diagnostics); var tokensToCompile = new List<(QsQualifiedName, (QsComments, IEnumerable))>(); @@ -238,7 +238,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position // add all callable declarations var callablesToCompile = AddItems(file.GetCallableDeclarationHeaderItems(), - (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, decl, att, doc), + (pos, name, decl, att, doc) => (ContainingParent(pos, namespaces)).TryAddCallableDeclaration(file.FileName, Location(pos, name.Item2), name, Tuple.Create(decl.Item1, decl.Item3), att, decl.Item2, doc), file.FileName.Value, diagnostics); // add all callable specilizations -> TOOD: needs to be adapted for specializations outside the declaration body (not yet supported) @@ -271,7 +271,7 @@ private static T ContainingParent(Position pos, IReadOnlyCollection<(Position /// Throws an ArgumentNullException if either the given namespace or diagnostics are null. /// private static List AddSpecializationsToNamespace(FileContentManager file, Namespace ns, - (CodeFragment.TokenIndex, HeaderEntry>) parent, List diagnostics) + (CodeFragment.TokenIndex, HeaderEntry>) parent, List diagnostics) { if (ns == null) throw new ArgumentNullException(nameof(ns)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); @@ -1346,19 +1346,23 @@ internal static List RunTypeChecking (CompilationUnit compilation, // check that the declarations for the types and callables to be built from the given FragmentTrees exist in the given CompilationUnit var typeRoots = roots.Where(root => root.Value.Item2.Specializations == null); var typeDeclarations = typeRoots.ToImmutableDictionary( - root => root.Key, root => + root => root.Key, + root => { - var info = compilation.GlobalSymbols.TryGetType(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); - if (info.IsNull) throw new ArgumentException("type to build is no longer present in the given NamespaceManager"); - return info.Item; + var result = compilation.GlobalSymbols.TryGetType(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); + return result is ResolutionResult.Found type + ? type.Item + : throw new ArgumentException("type to build is no longer present in the given NamespaceManager"); }); var callableRoots = roots.Where(root => root.Value.Item2.Specializations != null); var callableDeclarations = callableRoots.ToImmutableDictionary( - root => root.Key, root => + root => root.Key, + root => { - var info = compilation.GlobalSymbols.TryGetCallable(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); - if (info.IsNull) throw new ArgumentException("callable to build is no longer present in the given NamespaceManager"); - return info.Item; + var result = compilation.GlobalSymbols.TryGetCallable(root.Key, root.Value.Item2.Namespace, root.Value.Item2.Source); + return result is ResolutionResult.Found callable + ? callable.Item + : throw new ArgumentException("callable to build is no longer present in the given NamespaceManager"); }); (QsQualifiedName, ImmutableArray) GetSpecializations @@ -1389,14 +1393,33 @@ QsCallable GetCallable((QsQualifiedName, ImmutableArray) specI } symbolTracker.EndScope(); QsCompilerError.Verify(symbolTracker.AllScopesClosed, "all scopes should be closed"); - return new QsCallable(info.Kind, parent, info.Attributes, info.SourceFile, QsNullable.Null, - info.Signature, info.ArgumentTuple, specs, info.Documentation, roots[parent].Item1); + return new QsCallable( + info.Kind, + parent, + info.Attributes, + info.Modifiers, + info.SourceFile, + QsNullable.Null, + info.Signature, + info.ArgumentTuple, + specs, + info.Documentation, + roots[parent].Item1 + ); } var callables = callableRoots.Select(GetSpecializations).Select(GetCallable).ToImmutableArray(); var types = typeDeclarations.Select(decl => new QsCustomType( - decl.Key, decl.Value.Attributes, decl.Value.SourceFile, decl.Value.Location, - decl.Value.Type, decl.Value.TypeItems, decl.Value.Documentation, roots[decl.Key].Item1)).ToImmutableArray(); + decl.Key, + decl.Value.Attributes, + decl.Value.Modifiers, + decl.Value.SourceFile, + decl.Value.Location, + decl.Value.Type, + decl.Value.TypeItems, + decl.Value.Documentation, + roots[decl.Key].Item1 + )).ToImmutableArray(); if (cancellationToken.IsCancellationRequested) return null; compilation.UpdateCallables(callables); diff --git a/src/QsCompiler/Core/ConstructorExtensions.fs b/src/QsCompiler/Core/ConstructorExtensions.fs index ce3f4f7964..2c3a8226ec 100644 --- a/src/QsCompiler/Core/ConstructorExtensions.fs +++ b/src/QsCompiler/Core/ConstructorExtensions.fs @@ -195,10 +195,11 @@ type QsSpecialization with static member NewControlledAdjoint = QsSpecialization.New QsControlledAdjoint type QsCallable with - static member New kind (source, location) (name, attributes, argTuple, signature, specializations : IEnumerable<_>, documentation, comments) = { + static member New kind (source, location) (name, attributes, modifiers, argTuple, signature, specializations : IEnumerable<_>, documentation, comments) = { Kind = kind FullName = name Attributes = attributes + Modifiers = modifiers SourceFile = source Location = location Signature = signature @@ -212,9 +213,10 @@ type QsCallable with static member NewTypeConstructor = QsCallable.New QsCallableKind.TypeConstructor type QsCustomType with - static member New (source, location) (name, attributes, items, underlyingType, documentation, comments) = { + static member New (source, location) (name, attributes, modifiers, items, underlyingType, documentation, comments) = { FullName = name Attributes = attributes + Modifiers = modifiers SourceFile = source Location = location Type = underlyingType diff --git a/src/QsCompiler/Core/DeclarationHeaders.fs b/src/QsCompiler/Core/DeclarationHeaders.fs index ee46da56c0..6bf15d3f58 100644 --- a/src/QsCompiler/Core/DeclarationHeaders.fs +++ b/src/QsCompiler/Core/DeclarationHeaders.fs @@ -96,6 +96,7 @@ module DeclarationHeader = type TypeDeclarationHeader = { QualifiedName : QsQualifiedName Attributes : ImmutableArray + Modifiers : Modifiers SourceFile : NonNullable Position : DeclarationHeader.Offset SymbolRange : DeclarationHeader.Range @@ -111,6 +112,7 @@ type TypeDeclarationHeader = { static member New (customType : QsCustomType) = { QualifiedName = customType.FullName Attributes = customType.Attributes + Modifiers = customType.Modifiers SourceFile = customType.SourceFile Position = customType.Location |> DeclarationHeader.CreateOffset SymbolRange = customType.Location |> DeclarationHeader.CreateRange @@ -135,6 +137,7 @@ type CallableDeclarationHeader = { Kind : QsCallableKind QualifiedName : QsQualifiedName Attributes : ImmutableArray + Modifiers : Modifiers SourceFile : NonNullable Position : DeclarationHeader.Offset SymbolRange : DeclarationHeader.Range @@ -151,6 +154,7 @@ type CallableDeclarationHeader = { Kind = callable.Kind QualifiedName = callable.FullName Attributes = callable.Attributes + Modifiers = callable.Modifiers SourceFile = callable.SourceFile Position = callable.Location |> DeclarationHeader.CreateOffset SymbolRange = callable.Location |> DeclarationHeader.CreateRange diff --git a/src/QsCompiler/Core/NamespaceTransformation.fs b/src/QsCompiler/Core/NamespaceTransformation.fs index 471d6bf6d0..e258301ea6 100644 --- a/src/QsCompiler/Core/NamespaceTransformation.fs +++ b/src/QsCompiler/Core/NamespaceTransformation.fs @@ -178,7 +178,7 @@ type NamespaceTransformationBase internal (options : TransformationOptions, _int let specializations = c.Specializations |> Seq.sortBy (fun c -> c.Kind) |> Seq.map this.OnSpecializationDeclaration |> ImmutableArray.CreateRange let doc = this.OnDocumentation c.Documentation let comments = c.Comments - QsCallable.New c.Kind (source, loc) |> Node.BuildOr c (c.FullName, attributes, argTuple, signature, specializations, doc, comments) + QsCallable.New c.Kind (source, loc) |> Node.BuildOr c (c.FullName, attributes, c.Modifiers, argTuple, signature, specializations, doc, comments) abstract member OnOperation : QsCallable -> QsCallable default this.OnOperation c = this.OnCallableKind c @@ -205,7 +205,7 @@ type NamespaceTransformationBase internal (options : TransformationOptions, _int let typeItems = this.OnTypeItems t.TypeItems let doc = this.OnDocumentation t.Documentation let comments = t.Comments - QsCustomType.New (source, loc) |> Node.BuildOr t (t.FullName, attributes, typeItems, underlyingType, doc, comments) + QsCustomType.New (source, loc) |> Node.BuildOr t (t.FullName, attributes, t.Modifiers, typeItems, underlyingType, doc, comments) // transformation roots called on each namespace or namespace element diff --git a/src/QsCompiler/Core/SymbolResolution.fs b/src/QsCompiler/Core/SymbolResolution.fs index 66e4eebc19..02479b65ff 100644 --- a/src/QsCompiler/Core/SymbolResolution.fs +++ b/src/QsCompiler/Core/SymbolResolution.fs @@ -37,6 +37,7 @@ type internal Resolution<'T,'R> = internal { Resolved : QsNullable<'R> DefinedAttributes : ImmutableArray ResolvedAttributes : ImmutableArray + Modifiers : Modifiers Documentation : ImmutableArray } @@ -75,7 +76,99 @@ type SpecializationBundleProperties = internal { (fun group -> group.ToImmutableDictionary(getKind))) -module SymbolResolution = +/// Represents the outcome of resolving a symbol. +type ResolutionResult<'T> = + /// A result indicating that the symbol resolved successfully. + | Found of 'T + /// A result indicating that an unqualified symbol is ambiguous, and it is possible to resolve it to more than one + /// namespace. Includes the list of possible namespaces. + | Ambiguous of NonNullable seq + /// A result indicating that the symbol resolved to a declaration which is not accessible from the location + /// referencing it. + | Inaccessible + /// A result indicating that no declaration with that name was found. + | NotFound + +/// Tools for processing resolution results. +module internal ResolutionResult = + /// Applies the mapping function to the value of a Found result. If the result is not a Found, returns it unchanged. + let internal Map mapping = function + | Found value -> Found (mapping value) + | Ambiguous namespaces -> Ambiguous namespaces + | Inaccessible -> Inaccessible + | NotFound -> NotFound + + /// Converts the resolution result to an option containing the Found value. + let internal ToOption = function + | Found value -> Some value + | _ -> None + + /// Converts the resolution result to a 0- or 1-element list containing the Found value if the result is a Found. + let private ToList = ToOption >> Option.toList + + /// Sorts the sequence of results in order of Found, Ambiguous, Inaccessible, and NotFound. + /// + /// If the sequence contains a Found result, the rest of the sequence after it is not automatically evaluated. + /// Otherwise, the whole sequence may be evaluated by calling sort. + let private Sort results = + let choosers = [ + function | Found value -> Some (Found value) | _ -> None + function | Ambiguous namespaces -> Some (Ambiguous namespaces) | _ -> None + function | Inaccessible -> Some Inaccessible | _ -> None + function | NotFound -> Some NotFound | _ -> None + ] + choosers + |> Seq.map (fun chooser -> Seq.choose chooser results) + |> Seq.concat + + /// Returns the first item of the sequence of results if there is one, or NotFound if the sequence is empty. + let private TryHead<'T> : seq> -> ResolutionResult<'T> = + Seq.tryHead >> Option.defaultValue NotFound + + /// Returns the first Found result in the sequence if it exists. If not, returns the first Ambiguous result if it + /// exists. Repeats for Inaccessible and NotFound in this order. If the sequence is empty, returns NotFound. + let internal TryFirstBest<'T> : seq> -> ResolutionResult<'T> = + Sort >> TryHead + + /// Returns a Found result if there is only one in the sequence. If there is more than one, returns an Ambiguous + /// result containing the namespaces of all Found results given by applying nsGetter to each value. Otherwise, + /// returns the same value as TryFirstBest. + let internal TryAtMostOne<'T> (nsGetter : 'T -> NonNullable) (results : seq>) + : ResolutionResult<'T> = + let found = results |> Seq.filter (function | Found _ -> true | _ -> false) + if Seq.length found > 1 + then found + |> Seq.map (Map nsGetter >> ToList) + |> Seq.concat + |> Ambiguous + else found + |> Seq.tryExactlyOne + |> Option.defaultWith (fun () -> TryFirstBest results) + + /// Returns a Found result if there is only one in the sequence. If there is more than one, raises an exception. + /// Otherwise, returns the same value as ResolutionResult.TryFirstBest. + let internal AtMostOne<'T> : seq> -> ResolutionResult<'T> = + TryAtMostOne (fun _ -> NonNullable<_>.New "") >> function + | Ambiguous _ -> QsCompilerError.Raise "Resolution is ambiguous" + Exception () |> raise + | result -> result + + /// Returns true if the resolution result indicates that a resolution exists (Found, Ambiguous or Inaccessible). + let internal Exists = function + | Found _ + | Ambiguous _ + | Inaccessible -> true + | NotFound -> false + + /// Returns true if the resolution result indicates that a resolution is accessible (Found or Ambiguous). + let internal IsAccessible = function + | Found _ + | Ambiguous _ -> true + | Inaccessible + | NotFound -> false + + +module SymbolResolution = // routines for giving deprecation warnings @@ -270,10 +363,10 @@ module SymbolResolution = /// Verifies that all used type parameters are defined in the given list of type parameters, /// and generates suitable diagnostics if they are not, replacing them by the Q# type denoting an invalid type. /// Returns the resolved type as well as an array with diagnostics. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is inconsistent with the defined callables. - /// May throw an ArgumentException if no namespace with the given name exists, or the given source file is not listed as source of that namespace. - /// Throws a NonSupportedException if the QsType to resolve contains a MissingType. - let rec internal ResolveType (processUDT, processTypeParameter) (qsType : QsType) = + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is inconsistent with the defined callables. + /// May throw an ArgumentException if no namespace with the given name exists, or the given source file is not listed as source of that namespace. + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. + let rec internal ResolveType (processUDT, processTypeParameter) (qsType : QsType) = let resolve = ResolveType (processUDT, processTypeParameter) let asResolvedType t = ResolvedType.New (true, t) let buildWith builder ts = builder ts |> asResolvedType diff --git a/src/QsCompiler/Core/SymbolTable.fs b/src/QsCompiler/Core/SymbolTable.fs index 86e1ba026d..a0f1c1571c 100644 --- a/src/QsCompiler/Core/SymbolTable.fs +++ b/src/QsCompiler/Core/SymbolTable.fs @@ -18,7 +18,9 @@ open Microsoft.Quantum.QsCompiler.SyntaxTree open Newtonsoft.Json -/// Note that this class is *not* threadsafe! +/// Represents the partial declaration of a namespace in a single file. +/// +/// Note that this class is *not* thread-safe, and access modifiers are always ignored when looking up declarations. type private PartialNamespace private (name : NonNullable, source : NonNullable, @@ -30,13 +32,14 @@ type private PartialNamespace private let keySelector (item : KeyValuePair<'k,'v>) = item.Key let valueSelector (item : KeyValuePair<'k,'v>) = item.Value - let unresolved (location : QsLocation) (definition, attributes, doc) = { - Defined = definition; + let unresolved (location : QsLocation) (definition, attributes, modifiers, doc) = { + Defined = definition DefinedAttributes = attributes - Resolved = Null; + Resolved = Null ResolvedAttributes = ImmutableArray.Empty - Position = location.Offset; - Range = location.Range; + Modifiers = modifiers + Position = location.Offset + Range = location.Range Documentation = doc } @@ -130,7 +133,7 @@ type private PartialNamespace private /// Adds the corresponding type constructor to the dictionary of declared callables. /// The given location is associated with both the type constructor and the type itself and accessible via the record properties Position and SymbolRange. /// -> Note that this routine will fail with the standard dictionary.Add error if either a type or a callable with that name already exists. - member this.AddType (location : QsLocation) (tName, typeTuple, attributes, documentation) = + member this.AddType (location : QsLocation) (tName, typeTuple, attributes, modifiers, documentation) = let mutable anonItemId = 0 let withoutRange sym = {Symbol = sym; Range = Null} let replaceAnonymous (itemName : QsSymbol, itemType) = // positional info for types in type constructors is removed upon resolution @@ -165,17 +168,17 @@ type private PartialNamespace private if attributes |> Seq.exists (SymbolResolution.IndicatesDeprecation validDeprecatedQualification) then ImmutableArray.Create deprecationWithoutRedirect else ImmutableArray.Empty - TypeDeclarations.Add(tName, (typeTuple, attributes, documentation) |> unresolved location) - this.AddCallableDeclaration location (tName, (TypeConstructor, constructorSignature), constructorAttr, ImmutableArray.Empty) + TypeDeclarations.Add(tName, (typeTuple, attributes, modifiers, documentation) |> unresolved location) + this.AddCallableDeclaration location (tName, (TypeConstructor, constructorSignature), constructorAttr, modifiers, ImmutableArray.Empty) let bodyGen = {TypeArguments = Null; Generator = QsSpecializationGeneratorKind.Intrinsic; Range = Value location.Range} this.AddCallableSpecialization location QsBody (tName, bodyGen, ImmutableArray.Empty, ImmutableArray.Empty) /// Adds a callable declaration of the given kind (operation or function) /// with the given callable name and signature to the dictionary of declared callables. - /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. - /// -> Note that this routine will fail with the standard dictionary.Add error if a callable with that name already exists. - member this.AddCallableDeclaration location (cName, (kind, signature), attributes, documentation) = - CallableDeclarations.Add(cName, (kind, (signature, attributes, documentation) |> unresolved location)) + /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. + /// -> Note that this routine will fail with the standard dictionary.Add error if a callable with that name already exists. + member this.AddCallableDeclaration location (cName, (kind, signature), attributes, modifiers, documentation) = + CallableDeclarations.Add(cName, (kind, (signature, attributes, modifiers, documentation) |> unresolved location)) /// Adds the callable specialization defined by the given kind and generator for the callable of the given name to the dictionary of declared specializations. /// The given location is associated with the given specialization and accessible via the record properties Position and HeaderRange. @@ -183,10 +186,10 @@ type private PartialNamespace private /// *IMPORTANT*: both the verification of whether the length of the given array of type specialization /// matches the number of type parameters in the callable declaration, and whether a specialization that clashes with this one /// already exists is up to the calling routine! - member this.AddCallableSpecialization location kind (cName, generator : QsSpecializationGenerator, attributes, documentation) = - // NOTE: all types that are not specialized need to be resolved according to the file in which the callable is declared, - // but all specialized types need to be resolved according to *this* file - let spec = kind, (generator, attributes, documentation) |> unresolved location + member this.AddCallableSpecialization location kind (cName, generator : QsSpecializationGenerator, attributes, documentation) = + // NOTE: all types that are not specialized need to be resolved according to the file in which the callable is declared, + // but all specialized types need to be resolved according to *this* file + let spec = kind, (generator, attributes, {Access = DefaultAccess}, documentation) |> unresolved location match CallableSpecializations.TryGetValue cName with | true, specs -> specs.Add spec // it is up to the namespace to verify the type specializations | false, _ -> CallableSpecializations.Add(cName, new List<_>([spec])) @@ -230,12 +233,19 @@ type private PartialNamespace private | false, _ -> [||] -/// Note that this class is *not* threadsafe! +/// Represents a namespace and all of its declarations. +/// +/// This class is *not* thread-safe. +/// +/// Access modifiers are taken into consideration when resolving symbols. Some methods bypass this (e.g., when returning +/// a list of all declarations). Individual methods will mention if they adhere to symbol accessibility. and Namespace private - (name, parts : IEnumerable,PartialNamespace>>, - CallablesInReferences : ImmutableDictionary, CallableDeclarationHeader>, - SpecializationsInReferences : ILookup, SpecializationDeclarationHeader * SpecializationImplementation>, - TypesInReferences : ImmutableDictionary, TypeDeclarationHeader>) = + (name, + parts : IEnumerable,PartialNamespace>>, + CallablesInReferences : ILookup, CallableDeclarationHeader>, + SpecializationsInReferences : ILookup, + SpecializationDeclarationHeader * SpecializationImplementation>, + TypesInReferences : ILookup, TypeDeclarationHeader>) = /// dictionary containing a PartialNamespaces for each source file which implements a part of this namespace - /// the key is the source file where each part of the namespace is defined @@ -243,21 +253,29 @@ and Namespace private let mutable TypesDefinedInAllSourcesCache = null let mutable CallablesDefinedInAllSourcesCache = null - /// Given a non-nullable string, returns true if either a callable or a type with that name already exists - /// either in a source file, or in a referenced assembly. - let IsDefined arg = - CallablesInReferences.ContainsKey arg || TypesInReferences.ContainsKey arg || - Parts.Values.Any (fun partial -> partial.ContainsCallable arg || partial.ContainsType arg) - - /// Given a selector, determines the source files for which the given selector returns a Value. - /// Returns that Value if exactly one such source file exists, or Null if no such file exists. - /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! - /// Throws an ArgumentException if the selector returns a Value for more than one source file. - let FromSingleSource (selector : PartialNamespace -> _ Option) = - let sources = Parts.Values |> Seq.choose selector - if sources.Count() > 1 then ArgumentException "given selector selects more than one partial namespace" |> raise - if sources.Any() then Value (sources.Single()) else Null - + /// Returns true if the name is available for use in a new declaration. + let isNameAvailable name = + let isAvailableWith declarationsGetter accessibilityGetter sameAssembly = + declarationsGetter name + |> Seq.exists (fun name -> Namespace.IsDeclarationAccessible (sameAssembly, accessibilityGetter name)) + |> not + + let tryToList = function + | true, value -> [value] + | false, _ -> [] + + isAvailableWith (fun name -> CallablesInReferences.[name]) (fun c -> c.Modifiers.Access) false && + isAvailableWith (fun name -> TypesInReferences.[name]) (fun t -> t.Modifiers.Access) false && + Parts.Values.All (fun partial -> + isAvailableWith (partial.TryGetCallable >> tryToList) (fun c -> (snd c).Modifiers.Access) true && + isAvailableWith (partial.TryGetType >> tryToList) (fun t -> t.Modifiers.Access) true) + + /// Returns whether a declaration is accessible from the calling location, given whether the calling location is in + /// the same assembly as the declaration, and the declaration's access modifier. + static member IsDeclarationAccessible (sameAssembly, access) = + match access with + | DefaultAccess -> true + | Internal -> sameAssembly /// name of the namespace member this.Name = name @@ -280,18 +298,27 @@ and Namespace private let callablesInRefs = callablesInRefs.Where(fun (header : CallableDeclarationHeader) -> header.QualifiedName.Namespace = name) let specializationsInRefs = specializationsInRefs.Where(fun (header : SpecializationDeclarationHeader, _) -> header.Parent.Namespace = name) - // ignore ambiguous/clashing references - let FilterUnique (g : IGrouping<_,_>) = - if g.Count() > 1 then None - else g.Single() |> Some - let typesInRefs = typesInRefs.GroupBy(fun t -> t.QualifiedName.Name) |> Seq.choose FilterUnique - let callablesInRefs = callablesInRefs.GroupBy(fun c -> c.QualifiedName.Name) |> Seq.choose FilterUnique - - let types = typesInRefs.ToImmutableDictionary(fun t -> t.QualifiedName.Name) - let callables = callablesInRefs.ToImmutableDictionary(fun c -> c.QualifiedName.Name) - let specializations = specializationsInRefs.Where(fun (s, _) -> callables.ContainsKey s.Parent.Name).ToLookup(fun (s, _) -> s.Parent.Name) - new Namespace(name, initialSources, callables, specializations, types) - + let discardConflicts getAccess (_, nameGroup) = + // Only one externally accessible declaration with the same name is allowed. + let isAccessible header = Namespace.IsDeclarationAccessible (false, getAccess header) + if nameGroup |> Seq.filter isAccessible |> Seq.length > 1 + then nameGroup |> Seq.filter (not << isAccessible) + else nameGroup + + let createLookup getName getAccess headers = + headers + |> Seq.groupBy getName + |> Seq.map (discardConflicts getAccess) + |> Seq.concat + |> fun headers -> headers.ToLookup (Func<_, _> getName) + + let types = typesInRefs |> createLookup (fun t -> t.QualifiedName.Name) (fun t -> t.Modifiers.Access) + let callables = callablesInRefs |> createLookup (fun c -> c.QualifiedName.Name) (fun c -> c.Modifiers.Access) + let specializations = + specializationsInRefs + .Where(fun (s, _) -> callables.[s.Parent.Name].Any()) + .ToLookup(fun (s, _) -> s.Parent.Name) + Namespace (name, initialSources, callables, specializations, types) /// returns true if the namespace currently contains no source files or referenced content member this.IsEmpty = @@ -335,21 +362,39 @@ and Namespace private /// Throws an InvalidOperationExeception if the corresponding type has not been resolved. member internal this.TryGetAttributeDeclaredIn source (attName, possibleQualifications : _ seq) = let marksAttribute (t : QsDeclarationAttribute) = t.TypeId |> function - | Value id -> id.Namespace.Value = BuiltIn.Attribute.FullName.Namespace.Value && id.Name.Value = BuiltIn.Attribute.FullName.Name.Value + | Value id -> + id.Namespace.Value = BuiltIn.Attribute.FullName.Namespace.Value && + id.Name.Value = BuiltIn.Attribute.FullName.Name.Value | Null -> false + let missingResolutionException () = InvalidOperationException "cannot get unresolved attribute" |> raise - let compareAttributeName (att : AttributeAnnotation) = att.Id.Symbol |> function - | Symbol sym when sym.Value = BuiltIn.Attribute.FullName.Name.Value && possibleQualifications.Contains "" -> true - | QualifiedSymbol (ns, sym) when sym.Value = BuiltIn.Attribute.FullName.Name.Value && possibleQualifications.Contains ns.Value -> true + + let compareAttributeName (att : AttributeAnnotation) = + match att.Id.Symbol with + | Symbol sym when sym.Value = BuiltIn.Attribute.FullName.Name.Value && possibleQualifications.Contains "" -> + true + | QualifiedSymbol (ns, sym) when sym.Value = BuiltIn.Attribute.FullName.Name.Value && + possibleQualifications.Contains ns.Value -> + true | _ -> false - match TypesInReferences.TryGetValue attName with - | true, tDecl -> if tDecl.Attributes |> Seq.exists marksAttribute then Some tDecl.Type else None - | false, _ -> Parts.TryGetValue source |> function // ok only because/if we have covered that the type is not in a reference! - | false, _ -> ArgumentException "given source file is not listed as source of the namespace" |> raise - | true, partialNS -> partialNS.TryGetType attName |> function - | true, resolution when resolution.DefinedAttributes |> Seq.exists compareAttributeName -> - resolution.Resolved.ValueOrApply missingResolutionException |> fst |> Some - | _ -> None + + match Parts.TryGetValue source with + | true, partial -> + match partial.TryGetType attName with + | true, resolution when Seq.exists compareAttributeName resolution.DefinedAttributes -> + resolution.Resolved.ValueOrApply missingResolutionException |> fst |> Some + | _ -> None + | false, _ -> + let referenceType = + TypesInReferences.[attName] + |> Seq.filter (fun qsType -> qsType.SourceFile = source) + |> Seq.tryExactlyOne + match referenceType with + | Some qsType -> + if Seq.exists marksAttribute qsType.Attributes + then Some qsType.Type + else None + | None -> ArgumentException "given source file is not part of the namespace" |> raise /// Returns the type with the given name defined in the given source file within this namespace. /// Note that files contained in referenced assemblies are *not* considered to be source files for the namespace! @@ -413,44 +458,84 @@ and Namespace private /// This excludes specializations that are defined in files contained in referenced assemblies. /// Throws an ArgumentException if no callable with the given name is defined in this namespace. member internal this.SpecializationsDefinedInAllSources cName = - match this.ContainsCallable cName with - | Value _ -> (Parts.Values.SelectMany (fun partial -> - partial.GetSpecializations cName |> Seq.map (fun (kind, decl) -> kind, (partial.Source, decl)))).ToImmutableArray() - | Null -> ArgumentException "no callable with the given name exist within the namespace" |> raise - - /// If this namespace contains a declaration for the given type name, - /// returns a Value with the name of the source file or the name of the file within a referenced assembly - /// in which it is declared as well as a string option indicating the redirection for the type if it has been deprecated. - /// Returns Null otherwise. - /// Whether the type has been deprecated is determined by checking the associated attributes for an attribute with the corresponding name. - /// Note that if the type is declared in a source files, the *unresolved* attributes will be checked. - /// In that case checkDeprecation is used to validate the namespace qualification of the attribute. - /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and source file. - member this.ContainsType (tName, ?checkDeprecation : (string -> bool)) = - let checkDeprecation = defaultArg checkDeprecation (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) - match TypesInReferences.TryGetValue tName with - | true, tDecl -> Value (tDecl.SourceFile, tDecl.Attributes |> SymbolResolution.TryFindRedirect) - | false, _ -> FromSingleSource (fun partialNS -> partialNS.TryGetType tName |> function - | true, tDecl -> Some (partialNS.Source, tDecl.DefinedAttributes |> SymbolResolution.TryFindRedirectInUnresolved checkDeprecation) - | false, _ -> None) - - /// If this namespace contains the declaration for the given callable name, - /// returns a Value with the name of the source file or the name of the file within a referenced assembly - /// in which it is declared as well as a string option indicating the redirection for the callable if it has been deprecated. - /// Returns Null otherwise. - /// If the given callable corresponds to the (auto-generated) type constructor for a user defined type, - /// returns the file in which that type is declared as source. - /// Whether the callable has been deprecated is determined by checking the associated attributes for an attribute with the corresponding name. - /// Note that if the type is declared in a source files, the *unresolved* attributes will be checked. - /// In that case checkDeprecation is used to validate the namespace qualification of the attribute. - /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and source file. - member this.ContainsCallable (cName, ?checkDeprecation : (string -> bool)) = - let checkDeprecation = defaultArg checkDeprecation (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) - match CallablesInReferences.TryGetValue cName with - | true, cDecl -> Value (cDecl.SourceFile, cDecl.Attributes |> SymbolResolution.TryFindRedirect) - | false, _ -> FromSingleSource (fun partialNS -> partialNS.TryGetCallable cName |> function - | true, (_, cDecl) -> Some (partialNS.Source, cDecl.DefinedAttributes |> SymbolResolution.TryFindRedirectInUnresolved checkDeprecation) - | false, _ -> None) + let getSpecializationInPartial (partial : PartialNamespace) = + partial.GetSpecializations cName + |> Seq.map (fun (kind, decl) -> kind, (partial.Source, decl)) + + if this.TryFindCallable cName |> ResolutionResult.Exists + then (Parts.Values.SelectMany getSpecializationInPartial).ToImmutableArray() + else ArgumentException "no callable with the given name exist within the namespace" |> raise + + + /// Returns a resolution result for the type with the given name containing the name of the source file or + /// referenced assembly in which it is declared, a string indicating the redirection if it has been deprecated, and + /// its access modifier. Resolution is based on accessibility to source files in this compilation unit. + /// + /// Whether the type has been deprecated is determined by checking the associated attributes for an attribute with + /// the corresponding name. Note that if the type is declared in a source files, the *unresolved* attributes will be + /// checked. In that case checkDeprecation is used to validate the namespace qualification of the attribute. If + /// checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace and + /// source file. + member this.TryFindType (tName, ?checkDeprecation : (string -> bool)) = + let checkDeprecation = + defaultArg checkDeprecation + (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) + + let resolveReferenceType (typeHeader : TypeDeclarationHeader) = + if Namespace.IsDeclarationAccessible (false, typeHeader.Modifiers.Access) + then Found (typeHeader.SourceFile, + SymbolResolution.TryFindRedirect typeHeader.Attributes, + typeHeader.Modifiers.Access) + else Inaccessible + + let findInPartial (partial : PartialNamespace) = + match partial.TryGetType tName with + | true, qsType -> + if Namespace.IsDeclarationAccessible (true, qsType.Modifiers.Access) + then Found (partial.Source, + SymbolResolution.TryFindRedirectInUnresolved checkDeprecation qsType.DefinedAttributes, + qsType.Modifiers.Access) + else Inaccessible + | false, _ -> NotFound + + seq { yield Seq.map resolveReferenceType TypesInReferences.[tName] |> ResolutionResult.AtMostOne + yield Seq.map findInPartial Parts.Values |> ResolutionResult.AtMostOne } + |> ResolutionResult.TryFirstBest + + /// Returns a resolution result for the callable with the given name containing the name of the source file or + /// referenced assembly in which it is declared, and a string indicating the redirection if it has been deprecated. + /// Resolution is based on accessibility to source files in this compilation unit. + /// + /// If the given callable corresponds to the (auto-generated) type constructor for a user defined type, returns the + /// file in which that type is declared as the source. + /// + /// Whether the callable has been deprecated is determined by checking the associated attributes for an attribute + /// with the corresponding name. Note that if the type is declared in a source files, the *unresolved* attributes + /// will be checked. In that case checkDeprecation is used to validate the namespace qualification of the attribute. + /// If checkDeprecation is not specified, it is assumed that no qualification is needed in the relevant namespace + /// and source file. + member this.TryFindCallable (cName, ?checkDeprecation : (string -> bool)) = + let checkDeprecation = + defaultArg checkDeprecation + (fun qual -> String.IsNullOrWhiteSpace qual || qual = BuiltIn.Deprecated.FullName.Namespace.Value) + + let resolveReferenceCallable (callable : CallableDeclarationHeader) = + if Namespace.IsDeclarationAccessible (false, callable.Modifiers.Access) + then Found (callable.SourceFile, SymbolResolution.TryFindRedirect callable.Attributes) + else Inaccessible + + let findInPartial (partial : PartialNamespace) = + match partial.TryGetCallable cName with + | true, (_, callable) -> + if Namespace.IsDeclarationAccessible (true, callable.Modifiers.Access) + then Found (partial.Source, + SymbolResolution.TryFindRedirectInUnresolved checkDeprecation callable.DefinedAttributes) + else Inaccessible + | false, _ -> NotFound + + seq { yield Seq.map resolveReferenceCallable CallablesInReferences.[cName] |> ResolutionResult.AtMostOne + yield Seq.map findInPartial Parts.Values |> ResolutionResult.AtMostOne } + |> ResolutionResult.TryFirstBest /// Sets the resolution for the type with the given name in the given source file to the given type, /// and replaces the resolved attributes with the given values. @@ -535,15 +620,19 @@ and Namespace private /// The given location is associated with both the type constructor and the type itself and accessible via the record properties Position and SymbolRange. /// If a type or callable with that name already exists, returns an array of suitable diagnostics. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. - member this.TryAddType (source, location) ((tName, tRange), typeTuple, attributes, documentation) : QsCompilerDiagnostic[] = + member this.TryAddType (source, location) ((tName, tRange), typeTuple, attributes, modifiers, documentation) : QsCompilerDiagnostic[] = match Parts.TryGetValue source with - | true, partial when not (IsDefined tName) -> + | true, partial when isNameAvailable tName -> TypesDefinedInAllSourcesCache <- null CallablesDefinedInAllSourcesCache <- null - partial.AddType location (tName, typeTuple, attributes, documentation); [||] - | true, _ -> this.ContainsType tName |> function - | Value _ -> [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeRedefinition, [tName.Value]) |] - | Null -> [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeConstructorOverlapWithCallable, [tName.Value]) |] + partial.AddType location (tName, typeTuple, attributes, modifiers, documentation); [||] + | true, _ -> + match this.TryFindType tName with + | Found _ + | Ambiguous _ -> + [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeRedefinition, [tName.Value]) |] + | _ -> + [| tRange |> QsCompilerDiagnostic.Error (ErrorCode.TypeConstructorOverlapWithCallable, [tName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// If no callable (function, operation, or type constructor) with the given name exists in this namespace, @@ -552,14 +641,18 @@ and Namespace private /// The given location is associated with the callable declaration and accessible via the record properties Position and SymbolRange. /// If a callable with that name already exists, returns an array of suitable diagnostics. /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. - member this.TryAddCallableDeclaration (source, location) ((cName, cRange), (kind, signature), attributes, documentation) = + member this.TryAddCallableDeclaration (source, location) ((cName, cRange), (kind, signature), attributes, modifiers, documentation) = match Parts.TryGetValue source with - | true, partial when not (IsDefined cName) -> + | true, partial when isNameAvailable cName -> CallablesDefinedInAllSourcesCache <- null - partial.AddCallableDeclaration location (cName, (kind, signature), attributes, documentation); [||] - | true, _ -> this.ContainsType cName |> function - | Value _ -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableOverlapWithTypeConstructor, [cName.Value]) |] - | Null -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableRedefinition, [cName.Value]) |] + partial.AddCallableDeclaration location (cName, (kind, signature), attributes, modifiers, documentation); [||] + | true, _ -> + match this.TryFindType cName with + | Found _ + | Ambiguous _ -> + [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableOverlapWithTypeConstructor, [cName.Value]) |] + | _ -> + [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.CallableRedefinition, [cName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// If a declaration for a callable of the given name exists within this namespace, @@ -572,24 +665,30 @@ and Namespace private /// Throws an ArgumentException if the given source file is not listed as a source for (part of) the namespace. /// IMPORTANT: The verification of whether the given specialization kind (body, adjoint, controlled, or controlled adjoint) may exist /// for the given callable is up to the calling routine. - member this.TryAddCallableSpecialization kind (source, location : QsLocation) ((cName, cRange), generator : QsSpecializationGenerator, attributes, documentation) = + member this.TryAddCallableSpecialization kind (source, location : QsLocation) ((cName, cRange), generator : QsSpecializationGenerator, attributes, documentation) = let getRelevantDeclInfo (declSource : NonNullable) = let unitOrInvalid fct = function - | Item item -> fct item |> function | UnitType | InvalidType -> true | _ -> false + | Item item -> match fct item with + | UnitType + | InvalidType -> true + | _ -> false | _ -> false - match CallablesInReferences.TryGetValue cName with - | true, cDecl -> - let unitReturn = cDecl.Signature.ReturnType |> unitOrInvalid (fun (t : ResolvedType) -> t.Resolution) - unitReturn, cDecl.Signature.TypeParameters.Length - | false, _ -> - let _, cDecl = Parts.[declSource].GetCallable cName // ok only because/if we have covered that the callable is not in a reference! + + // Check if the declaration's source file is local first, then look in references. + match Parts.TryGetValue declSource with + | true, partial -> + let _, cDecl = partial.GetCallable cName let unitReturn = cDecl.Defined.ReturnType |> unitOrInvalid (fun (t : QsType) -> t.Type) unitReturn, cDecl.Defined.TypeParameters.Length + | false, _ -> + let cDecl = CallablesInReferences.[cName] |> Seq.filter (fun c -> c.SourceFile = source) |> Seq.exactlyOne + let unitReturn = cDecl.Signature.ReturnType |> unitOrInvalid (fun (t : ResolvedType) -> t.Resolution) + unitReturn, cDecl.Signature.TypeParameters.Length match Parts.TryGetValue source with | true, partial -> - match this.ContainsCallable cName with - | Value (declSource, _) -> + match this.TryFindCallable cName with + | Found (declSource, _) -> let AddAndClearCache () = CallablesDefinedInAllSourcesCache <- null partial.AddCallableSpecialization location kind (cName, generator, attributes, documentation) @@ -605,7 +704,7 @@ and Namespace private | QsControlled -> [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.RequiredUnitReturnForControlled, []) |] | QsControlledAdjoint -> [| location.Range |> QsCompilerDiagnostic.Error (ErrorCode.RequiredUnitReturnForControlledAdjoint, []) |] else AddAndClearCache(); [||] - | Null -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.SpecializationForUnknownCallable, [cName.Value]) |] + | _ -> [| cRange |> QsCompilerDiagnostic.Error (ErrorCode.SpecializationForUnknownCallable, [cName.Value]) |] | false, _ -> ArgumentException "given source is not listed as a source of (parts of) the namespace" |> raise /// Adds an auto-generated specialization of the given kind to the callable with the given name and declaration in the specified source file. @@ -629,15 +728,20 @@ and Namespace private /// Threadsafe class for global symbol management. -/// Takes a lookup for all callables and for all types declared within one of the assemblies -/// referenced by the compilation unit this namespace manager belongs to. -/// The key for the given lookups is the name of the namespace the declarations belongs to. +/// +/// Takes a lookup for all callables and for all types declared within one of the assemblies referenced by the +/// compilation unit this namespace manager belongs to. The key for the given lookups is the name of the namespace the +/// declarations belongs to. +/// +/// The namespace manager takes access modifiers into consideration when resolving symbols. Some methods bypass this +/// (e.g., when returning a list of all declarations). Individual methods document whether they follow or ignore access +/// modifiers. and NamespaceManager (syncRoot : IReaderWriterLock, callablesInRefs : IEnumerable, specializationsInRefs : IEnumerable, typesInRefs : IEnumerable) = - // This class itself does not use any concurrency, + // This class itself does not use any concurrency, // so anything that is accessible within the class only does not apply any locks. // IMPORTANT: the syncRoot is intentionally not exposed externally, since with this class supporting mutation // access to that lock needs to be coordinated by whatever coordinates the mutations. @@ -682,19 +786,15 @@ and NamespaceManager | true, ns -> ns, ns.ImportedNamespaces source |> Seq.choose isKnownAndNotAliased |> Seq.toList | false, _ -> ArgumentException("no namespace with the given name exists") |> raise - /// If the given function containsSymbol returns a Value for the namespace with the given name, - /// returns a list containing only a single tuple with the given namespace name and the returned value. - /// Otherwise returns a list with all possible (namespaceName, returnedValue) tuples that yielded non-Null values - /// for namespaces opened within the given namespace and source file. - /// Throws an ArgumentException if no namespace with the given name exists, - /// or the given source file is not listed as source of that namespace. - let PossibleResolutions containsSymbol (nsName, source) = - let containsSource (arg : Namespace) = - containsSymbol arg |> QsNullable<_>.Map (fun source -> (arg.Name, source)) - let currentNS, importedNS = OpenNamespaces (nsName, source) - currentNS |> containsSource |> function - | Value (nsName, source) -> [(nsName, source)] - | Null -> importedNS |> List.choose (containsSource >> function | Value v -> Some v | Null -> None) + /// Calls the resolver function on each namespace opened within the given namespace name and source file, and + /// attempts to find an unambiguous resolution. + let resolveInOpenNamespaces resolver (nsName, source) = + let resolveWithNsName (ns : Namespace) = + resolver ns |> ResolutionResult.Map (fun value -> (ns.Name, value)) + let currentNs, importedNs = OpenNamespaces (nsName, source) + seq { yield resolveWithNsName currentNs + yield Seq.map resolveWithNsName importedNs |> ResolutionResult.TryAtMostOne fst } + |> ResolutionResult.TryFirstBest /// Given a qualifier for a symbol name, returns the corresponding namespace as Some /// if such a namespace or such a namespace short name within the given parent namespace and source file exists. @@ -717,57 +817,125 @@ and NamespaceManager /// Throws an ArgumentException if no namespace with the given name exists. let PossibleQualifications (nsName, source) (builtIn : BuiltIn) = match Namespaces.TryGetValue nsName with - | true, ns when ns.Sources.Contains source -> (ns.ImportedNamespaces source).TryGetValue builtIn.FullName.Namespace |> function - | true, null when ns.ContainsType builtIn.FullName.Name = Null || nsName.Value = builtIn.FullName.Namespace.Value -> [""; builtIn.FullName.Namespace.Value] + | true, ns when ns.Sources.Contains source -> + match (ns.ImportedNamespaces source).TryGetValue builtIn.FullName.Namespace with + | true, null when not (ns.TryFindType builtIn.FullName.Name |> ResolutionResult.IsAccessible) || + nsName.Value = builtIn.FullName.Namespace.Value -> + [""; builtIn.FullName.Namespace.Value] | true, null -> [builtIn.FullName.Namespace.Value] // the built-in type or callable is shadowed | true, alias -> [alias; builtIn.FullName.Namespace.Value] | false, _ -> [builtIn.FullName.Namespace.Value] | true, _ -> [builtIn.FullName.Namespace.Value]; | false, _ -> ArgumentException "no namespace with the given name exists" |> raise - /// Given the qualified or unqualified name of a type used within the given parent namespace and source file, determines if such a type exists - /// and returns its full name and the source file or referenced assembly in which it is defined as Some if it does. - /// Returns None if no such type exist, or if the type name is unqualified and ambiguous. + /// Given the qualified or unqualified name of a type used within the given parent namespace and source file, + /// determines if such a type is accessible, and returns its namespace name and the source file or referenced + /// assembly in which it is defined as Some if it is. + /// + /// Returns None if no such type exists, the type is inaccessible, or if the type name is unqualified and ambiguous. + /// /// Generates and returns an array with suitable diagnostics. - /// Throws an ArgumentException if the given parent namespace does not exist, - /// or if no source file with the given name is listed as source of that namespace. - let TryResolveTypeName (parentNS, source) ((nsName, symName), symRange) = - let orDefault (range : QsNullable<_>) = range.ValueOr QsCompilerDiagnostic.DefaultRange - let checkQualificationForDeprecation qual = BuiltIn.Deprecated |> PossibleQualifications (parentNS, source) |> Seq.contains qual - let buildAndReturn (ns, declSource, deprecation, errs) = - let deprecatedWarnings = deprecation |> SymbolResolution.GenerateDeprecationWarning ({Namespace = ns; Name = symName}, symRange |> orDefault) - Some ({Namespace = ns; Name = symName; Range = symRange}, declSource), deprecatedWarnings |> Array.append errs - let tryFind (parentNS, source) (tName, tRange) = - match (parentNS, source) |> PossibleResolutions (fun ns -> ns.ContainsType (tName, checkQualificationForDeprecation)) with - | [] -> Null, [| tRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownType, [tName.Value]) |] - | [(nsName, (declSource, deprecated))] -> Value (nsName, declSource, deprecated), [||] - | resolutions -> - let diagArg = String.Join(", ", resolutions.Select (fun (ns,_) -> ns.Value)) - Null, [| tRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.AmbiguousType, [tName.Value; diagArg]) |] + /// + /// Throws an ArgumentException if the given parent namespace does not exist, or if no source file with the given + /// name is listed as source of that namespace. + let tryResolveTypeName (parentNS, source) ((nsName, symName), symRange : QsRangeInfo) = + let checkQualificationForDeprecation qual = + BuiltIn.Deprecated |> PossibleQualifications (parentNS, source) |> Seq.contains qual + + let success ns declSource deprecation access errs = + let warnings = + SymbolResolution.GenerateDeprecationWarning + ({Namespace = ns; Name = symName}, symRange.ValueOr QsCompilerDiagnostic.DefaultRange) + deprecation + Some ({Namespace = ns; Name = symName; Range = symRange}, declSource, access), Array.append errs warnings + + let error code args = + None, [| QsCompilerDiagnostic.Error (code, args) (symRange.ValueOr QsCompilerDiagnostic.DefaultRange) |] + + let findUnqualified () = + match resolveInOpenNamespaces (fun ns -> ns.TryFindType (symName, checkQualificationForDeprecation)) + (parentNS, source) with + | Found (nsName, (declSource, deprecation, access)) -> success nsName declSource deprecation access [||] + | Ambiguous namespaces -> + let names = String.Join(", ", Seq.map (fun (ns : NonNullable) -> ns.Value) namespaces) + error ErrorCode.AmbiguousType [symName.Value; names] + | Inaccessible -> error ErrorCode.InaccessibleType [symName.Value] + | NotFound -> error ErrorCode.UnknownType [symName.Value] + + let findQualified (ns : Namespace) qualifier = + match ns.TryFindType (symName, checkQualificationForDeprecation) with + | Found (declSource, deprecation, access) -> success ns.Name declSource deprecation access [||] + | Ambiguous _ -> QsCompilerError.Raise "Qualified name should not be ambiguous" + Exception () |> raise + | Inaccessible -> error ErrorCode.InaccessibleTypeInNamespace [symName.Value; qualifier] + | NotFound -> error ErrorCode.UnknownTypeInNamespace [symName.Value; qualifier] + match nsName with - | None -> tryFind (parentNS, source) (symName, symRange) |> function - | Value (ns, declSource, deprecation), errs -> buildAndReturn (ns, declSource, deprecation, errs) - | Null, errs -> None, errs - | Some qualifier -> (parentNS, source) |> TryResolveQualifier qualifier |> function - | None -> None, [| symRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownNamespace, [qualifier.Value]) |] - | Some ns -> - ns.ContainsType (symName, checkQualificationForDeprecation) |> function - | Value (declSource, deprecation) -> buildAndReturn (ns.Name, declSource, deprecation, [||]) - | Null -> None, [| symRange |> orDefault |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeInNamespace, [symName.Value; qualifier.Value]) |] - - /// Given the name of the namespace as well as the source file in which the attribute occurs, resolves the given attribute. - /// Generates suitable diagnostics if a suitable attribute cannot be determined, - /// or if the attribute argument contains expressions that are not supported, - /// or if the resolved argument type does not match the expected argument type. + | None -> findUnqualified () + | Some qualifier -> + match TryResolveQualifier qualifier (parentNS, source) with + | None -> error ErrorCode.UnknownNamespace [qualifier.Value] + | Some ns -> findQualified ns qualifier.Value + + /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. The + /// resolution consists of replacing all unqualified names for user defined types by their qualified name. + /// + /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or + /// unqualified) can be found, or if the type is inaccessible. In that case, resolves the user defined type by + /// replacing it with the Q# type denoting an invalid type. + /// + /// Diagnostics can be generated in additional cases when UDTs are referenced by returning an array of diagnostics + /// from the given checkUdt function. + /// + /// Verifies that all used type parameters are defined in the given list of type parameters, and generates suitable + /// diagnostics if they are not, replacing them by the Q# type denoting an invalid type. Returns the resolved type + /// as well as an array with diagnostics. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined declarations. + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. + let resolveType (parent : QsQualifiedName, tpNames, source) qsType checkUdt = + let processUDT = tryResolveTypeName (parent.Namespace, source) >> function + | Some (udt, _, access), errs -> UserDefinedType udt, Array.append errs (checkUdt (udt, access)) + | None, errs -> InvalidType, errs + let processTP (symName, symRange) = + if tpNames |> Seq.contains symName + then TypeParameter {Origin = parent; TypeName = symName; Range = symRange}, [||] + else InvalidType, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange + |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeParameterName, [symName.Value]) |] + syncRoot.EnterReadLock() + try SymbolResolution.ResolveType (processUDT, processTP) qsType + finally syncRoot.ExitReadLock() + + /// Compares the accessibility of the parent declaration with the accessibility of the UDT being referenced. If the + /// accessibility of a referenced type is less than the accessibility of the parent, returns a diagnostic using the + /// given error code. Otherwise, returns an empty array. + let checkUdtAccessibility code (parent : NonNullable, parentAccess) (udt : UserDefinedType, udtAccess) = + if parentAccess = DefaultAccess && udtAccess = Internal + then [| QsCompilerDiagnostic.Error (code, [udt.Name.Value; parent.Value]) (udt.Range.ValueOr QsCompilerDiagnostic.DefaultRange) |] + else [||] + + + /// Given the name of the namespace as well as the source file in which the attribute occurs, resolves the given + /// attribute. + /// + /// Generates suitable diagnostics if a suitable attribute cannot be found or is not accessible, if the attribute + /// argument contains expressions that are not supported, or if the resolved argument type does not match the + /// expected argument type. + /// /// Returns the resolved attribute as well as the generated diagnostics. - /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier - /// or if the correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. - /// May throw an ArgumentException if the given parent namespace does not exist, - /// or if no source file with the given name is listed as source of that namespace. - member private this.ResolveAttribute (parentNS, source) attribute = - let getAttribute ((nsName, symName), symRange) = - match TryResolveTypeName (parentNS, source) ((nsName, symName), symRange) with - | Some (udt, declSource), errs -> // declSource may be the name of an assembly! + /// + /// The TypeId in the resolved attribute is set to Null if the unresolved Id is not a valid identifier or if the + /// correct attribute cannot be determined, and is set to the corresponding type identifier otherwise. + /// + /// May throw an ArgumentException if the given parent namespace does not exist or if no source file with the given + /// name is listed as source of that namespace. + member private this.ResolveAttribute (parentNS, source) attribute = + let getAttribute ((nsName, symName), symRange) = + match tryResolveTypeName (parentNS, source) ((nsName, symName), symRange) with + | Some (udt, declSource, _), errs -> // declSource may be the name of an assembly! let fullName = sprintf "%s.%s" udt.Namespace.Value udt.Name.Value let validQualifications = BuiltIn.Attribute |> PossibleQualifications (udt.Namespace, declSource) match Namespaces.TryGetValue udt.Namespace with @@ -858,46 +1026,63 @@ and NamespaceManager let resAttr = attr |> List.fold validateAttributes ([], []) |> snd resAttr.Reverse() |> ImmutableArray.CreateRange, errs.ToArray() - /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. - /// The resolution consists of replacing all unqualified names for user defined types by their qualified name. - /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or unqualified) can be found. - /// In that case, resolves the user defined type by replacing it with the Q# type denoting an invalid type. - /// Verifies that all used type parameters are defined in the given list of type parameters, - /// and generates suitable diagnostics if they are not, replacing them by the Q# type denoting an invalid type. - /// Returns the resolved type as well as an array with diagnostics. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined callables. + /// Fully (i.e. recursively) resolves the given Q# type used within the given parent in the given source file. The + /// resolution consists of replacing all unqualified names for user defined types by their qualified name. + /// + /// Generates an array of diagnostics for the cases where no user defined type of the specified name (qualified or + /// unqualified) can be found or the type is inaccessible. In that case, resolves the user defined type by replacing + /// it with the Q# type denoting an invalid type. + /// + /// Verifies that all used type parameters are defined in the given list of type parameters, and generates suitable + /// diagnostics if they are not, replacing them by the Q# type denoting an invalid type. Returns the resolved type + /// as well as an array with diagnostics. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// /// May throw an exception if the given parent and/or source file is inconsistent with the defined declarations. - /// Throws a NonSupportedException if the QsType to resolve contains a MissingType. - member this.ResolveType (parent : QsQualifiedName, tpNames : ImmutableArray<_>, source : NonNullable) (qsType : QsType) : ResolvedType * QsCompilerDiagnostic[] = - let processUDT = TryResolveTypeName (parent.Namespace, source) >> function - | Some (udt, _), errs -> UserDefinedType udt, errs - | None, errs -> InvalidType, errs - let processTP (symName, symRange) = - if tpNames |> Seq.contains symName then TypeParameter {Origin = parent; TypeName = symName; Range = symRange}, [||] - else InvalidType, [| symRange.ValueOr QsCompilerDiagnostic.DefaultRange |> QsCompilerDiagnostic.Error (ErrorCode.UnknownTypeParameterName, [symName.Value]) |] - syncRoot.EnterReadLock() - try SymbolResolution.ResolveType (processUDT, processTP) qsType - finally syncRoot.ExitReadLock() - - /// Resolves the underlying type as well as all named and unnamed items for the given type declaration in the specified source file. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined types. - /// May throw an exception if the given parent and/or source file is inconsistent with the defined types. - /// Throws an ArgumentException if the given type tuple is an empty QsTuple. - member private this.ResolveTypeDeclaration (fullName : QsQualifiedName, source) typeTuple = - let resolveType = this.ResolveType (fullName, ImmutableArray<_>.Empty, source) // currently type parameters for udts are not supported + /// Throws a NotSupportedException if the QsType to resolve contains a MissingType. + member this.ResolveType (parent : QsQualifiedName, tpNames : ImmutableArray<_>, source : NonNullable) (qsType : QsType) = + resolveType (parent, tpNames, source) qsType (fun _ -> [||]) + + /// Resolves the underlying type as well as all named and unnamed items for the given type declaration in the + /// specified source file using ResolveType. + /// + /// Generates the same diagnostics as ResolveType, as well as additional diagnostics when the accessibility of the + /// type declaration is greater than the accessibility of any part of its underlying type. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined types. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined types. Throws an + /// ArgumentException if the given type tuple is an empty QsTuple. + member private this.ResolveTypeDeclaration (fullName : QsQualifiedName, source, modifiers) typeTuple = + // Currently, type parameters for UDTs are not supported. + let checkAccessibility = checkUdtAccessibility ErrorCode.TypeLessAccessibleThanParentType (fullName.Name, modifiers.Access) + let resolveType qsType = resolveType (fullName, ImmutableArray<_>.Empty, source) qsType checkAccessibility SymbolResolution.ResolveTypeDeclaration resolveType typeTuple - /// Given the namespace and the name of the callable that the given signature belongs to, as well as its kind and the source file it is declared in, - /// fully resolves all Q# types in the signature using ResolveType. - /// Returns a new signature with the resolved types, the resolved argument tuple, as well as the array of diagnostics created during type resolution. - /// The position offset information for the variables declared in the argument tuple will be set to Null. - /// Positional information within types is set to Null if the parent callable is a type constructor. - /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is consistent with the defined callables. - /// May throw an exception if the given parent and/or source file is inconsistent with the defined callables. - /// Throws an ArgumentException if the given list of characteristics is empty. - member private this.ResolveCallableSignature (parentKind, parentName : QsQualifiedName, source) (signature : CallableSignature, specBundleCharacteristics) = - let resolveType tpNames t = - let res, errs = this.ResolveType (parentName, tpNames, source) t + /// Given the namespace and the name of the callable that the given signature belongs to, as well as its kind and + /// the source file it is declared in, fully resolves all Q# types in the signature using ResolveType. + /// + /// Generates the same diagnostics as ResolveType, as well as additional diagnostics when the accessibility of the + /// callable declaration is greater than the accessibility of any type in its signature. + /// + /// Returns a new signature with the resolved types, the resolved argument tuple, as well as the array of + /// diagnostics created during type resolution. + /// + /// The position offset information for the variables declared in the argument tuple will be set to Null. Positional + /// information within types is set to Null if the parent callable is a type constructor. + /// + /// IMPORTANT: for performance reasons does *not* verify if the given the given parent and/or source file is + /// consistent with the defined callables. + /// + /// May throw an exception if the given parent and/or source file is inconsistent with the defined callables. Throws + /// an ArgumentException if the given list of characteristics is empty. + member private this.ResolveCallableSignature (parentKind, parentName : QsQualifiedName, source, access) (signature, specBundleCharacteristics) = + let checkAccessibility = checkUdtAccessibility ErrorCode.TypeLessAccessibleThanParentCallable (parentName.Name, access) + let resolveType tpNames qsType = + let res, errs = resolveType (parentName, tpNames, source) qsType checkAccessibility if parentKind <> TypeConstructor then res, errs else res.WithoutRangeInfo, errs // strip positional info for auto-generated type constructors SymbolResolution.ResolveCallableSignature (resolveType, specBundleCharacteristics) signature @@ -925,8 +1110,8 @@ and NamespaceManager ns.TypesDefinedInAllSources() |> Seq.collect (fun kvPair -> let tName, (source, qsType) = kvPair.Key, kvPair.Value let fullName = {Namespace = ns.Name; Name = tName} - let resolved, msgs = qsType.Defined |> this.ResolveTypeDeclaration (fullName, source) - ns.SetTypeResolution source (tName, resolved |> Value, ImmutableArray.Empty) + let resolved, msgs = qsType.Defined |> this.ResolveTypeDeclaration (fullName, source, qsType.Modifiers) + ns.SetTypeResolution source (tName, resolved |> Value, ImmutableArray.Empty) msgs |> Array.map (fun msg -> source, (qsType.Position, msg)))) // ... before we can resolve the corresponding attributes. let attributeDiagnostics = sortedNamespaces |> Seq.collect (fun ns -> @@ -980,7 +1165,7 @@ and NamespaceManager // and finally we resolve the overall signature (whose characteristics are the intersection of the one of all bundles) let characteristics = props.Values |> Seq.map (fun bundle -> bundle.BundleInfo) |> Seq.toList - let resolved, msgs = (signature.Defined, characteristics) |> this.ResolveCallableSignature (kind, parent, source) // no positional info for type constructors + let resolved, msgs = (signature.Defined, characteristics) |> this.ResolveCallableSignature (kind, parent, source, signature.Modifiers.Access) // no positional info for type constructors ns.SetCallableResolution source (parent.Name, resolved |> Value, callableAttributes) errs <- (attrErrs |> Array.map (fun m -> source, m)) :: (msgs |> Array.map (fun m -> source, (signature.Position, m))) :: errs @@ -1083,17 +1268,20 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the source file and CallableDeclarationHeader of all callables imported from referenced assemblies. - member this.ImportedCallables () = + /// Returns the source file and CallableDeclarationHeader of all callables imported from referenced assemblies, + /// regardless of accessibility. + member this.ImportedCallables () = // TODO: this needs to be adapted if we support external specializations syncRoot.EnterReadLock() - try let imported = Namespaces.Values |> Seq.collect (fun ns -> ns.CallablesInReferencedAssemblies.Values) - imported.ToImmutableArray() + try Namespaces.Values + |> Seq.collect (fun ns -> ns.CallablesInReferencedAssemblies.SelectMany (fun g -> g.AsEnumerable ())) + |> fun callables -> callables.ToImmutableArray () finally syncRoot.ExitReadLock() - /// Returns the declaration headers for all callables defined in source files. + /// Returns the declaration headers for all callables defined in source files, regardless of accessibility. + /// /// Throws an InvalidOperationException if the symbols are not currently resolved. - member this.DefinedCallables () = + member this.DefinedCallables () = let notResolvedException = InvalidOperationException "callables are not resolved" syncRoot.EnterReadLock() try if not this.ContainsResolutions then notResolvedException |> raise @@ -1106,6 +1294,7 @@ and NamespaceManager Kind = kind QualifiedName = {Namespace = ns.Name; Name = cName} Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position SymbolRange = DeclarationHeader.Range.Defined declaration.Range @@ -1116,16 +1305,31 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() - /// Returns the source file and TypeDeclarationHeader of all types imported from referenced assemblies. + /// Returns the declaration headers for all callables (either defined in source files or imported from referenced + /// assemblies) that are accessible from source files in the compilation unit. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. + member this.AccessibleCallables () = + Seq.append + (Seq.map (fun callable -> callable, true) (this.DefinedCallables())) + (Seq.map (fun callable -> callable, false) (this.ImportedCallables())) + |> Seq.filter (fun (callable, sameAssembly) -> + Namespace.IsDeclarationAccessible (sameAssembly, callable.Modifiers.Access)) + |> Seq.map fst + + /// Returns the source file and TypeDeclarationHeader of all types imported from referenced assemblies, regardless + /// of accessibility. member this.ImportedTypes() = syncRoot.EnterReadLock() - try let imported = Namespaces.Values |> Seq.collect (fun ns -> ns.TypesInReferencedAssemblies.Values) - imported.ToImmutableArray() + try Namespaces.Values + |> Seq.collect (fun ns -> ns.TypesInReferencedAssemblies.SelectMany (fun g -> g.AsEnumerable ())) + |> fun types -> types.ToImmutableArray () finally syncRoot.ExitReadLock() - /// Returns the declaration headers for all types defined in source files. + /// Returns the declaration headers for all types defined in source files, regardless of accessibility. + /// /// Throws an InvalidOperationException if the symbols are not currently resolved. - member this.DefinedTypes () = + member this.DefinedTypes () = let notResolvedException = InvalidOperationException "types are not resolved" syncRoot.EnterReadLock() try if not this.ContainsResolutions then notResolvedException |> raise @@ -1137,6 +1341,7 @@ and NamespaceManager | Value (underlyingType, items) -> Some { QualifiedName = {Namespace = ns.Name; Name = tName} Attributes = qsType.ResolvedAttributes + Modifiers = qsType.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined qsType.Position SymbolRange = DeclarationHeader.Range.Defined qsType.Range @@ -1147,7 +1352,19 @@ and NamespaceManager defined.ToImmutableArray() finally syncRoot.ExitReadLock() - /// removes the given source file and all its content from all namespaces + /// Returns the declaration headers for all types (either defined in source files or imported from referenced + /// assemblies) that are accessible from source files in the compilation unit. + /// + /// Throws an InvalidOperationException if the symbols are not currently resolved. + member this.AccessibleTypes () = + Seq.append + (Seq.map (fun qsType -> qsType, true) (this.DefinedTypes())) + (Seq.map (fun qsType -> qsType, false) (this.ImportedTypes())) + |> Seq.filter (fun (qsType, sameAssembly) -> + Namespace.IsDeclarationAccessible (sameAssembly, qsType.Modifiers.Access)) + |> Seq.map fst + + /// removes the given source file and all its content from all namespaces member this.RemoveSource source = syncRoot.EnterWriteLock() versionNumber <- versionNumber + 1 @@ -1219,19 +1436,27 @@ and NamespaceManager | false, _ -> ArgumentException "no such namespace exists" |> raise finally syncRoot.ExitWriteLock() - /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a callable indeed exists. - /// If the callable is not defined an any of the references and the source file containing the callable declaration is specified (i.e. declSource is Some), - /// throws the corresponding exception if no such callable exists in that file. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. + /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be resolved within the given parent namespace and source file, and the callable is accessible. + /// + /// If the callable is not defined an any of the references and the source file containing the callable declaration + /// is specified (i.e. declSource is Some), throws the corresponding exception if no such callable exists in that + /// file. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. member private this.TryGetCallableHeader (callableName : QsQualifiedName, declSource) (nsName, source) = - let BuildHeader fullName (source, kind, declaration : Resolution<_,_>) = - let fallback () = (declaration.Defined, [CallableInformation.Invalid]) |> this.ResolveCallableSignature (kind, callableName, source) |> fst + let buildHeader fullName (source, kind, declaration) = + let fallback () = + (declaration.Defined, [CallableInformation.Invalid]) + |> this.ResolveCallableSignature (kind, callableName, source, declaration.Modifiers.Access) + |> fst let resolvedSignature, argTuple = declaration.Resolved.ValueOrApply fallback - Value { + { Kind = kind QualifiedName = fullName Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position SymbolRange = DeclarationHeader.Range.Defined declaration.Range @@ -1239,50 +1464,84 @@ and NamespaceManager ArgumentTuple = argTuple Documentation = declaration.Documentation } + + let findInReferences (ns : Namespace) = + ns.CallablesInReferencedAssemblies.[callableName.Name] + |> Seq.map (fun callable -> + if Namespace.IsDeclarationAccessible (false, callable.Modifiers.Access) + then Found callable + else Inaccessible) + |> ResolutionResult.AtMostOne + + let findInSources (ns : Namespace) = function + | Some source -> + // OK to use CallableInSource because this is only evaluated if the callable is not in a + // reference. + let kind, declaration = ns.CallableInSource source callableName.Name + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {callableName with Namespace = ns.Name} (source, kind, declaration)) + else Inaccessible + | None -> + match ns.CallablesDefinedInAllSources().TryGetValue callableName.Name with + | true, (source, (kind, declaration)) -> + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {callableName with Namespace = ns.Name} (source, kind, declaration)) + else Inaccessible + | false, _ -> NotFound + syncRoot.EnterReadLock() try match (nsName, source) |> TryResolveQualifier callableName.Namespace with - | None -> Null - | Some ns -> ns.CallablesInReferencedAssemblies.TryGetValue callableName.Name |> function - | true, cDecl -> Value cDecl - | false, _ -> declSource |> function - | Some source -> - let kind, decl = ns.CallableInSource source callableName.Name // ok only because/if we have covered that the callable is not in a reference! - BuildHeader {callableName with Namespace = ns.Name} (source, kind, decl) - | None -> - ns.CallablesDefinedInAllSources().TryGetValue callableName.Name |> function - | true, (source, (kind, decl)) -> BuildHeader {callableName with Namespace = ns.Name} (source, kind, decl) - | false, _ -> Null + | None -> NotFound + | Some ns -> + seq { yield findInReferences ns + yield findInSources ns declSource } + |> ResolutionResult.TryFirstBest finally syncRoot.ExitReadLock() - /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a callable indeed exists. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. - member this.TryGetCallable (callableName : QsQualifiedName) (nsName, source) = this.TryGetCallableHeader (callableName, None) (nsName, source) - - /// If the given callable name can be uniquely resolved within the given namespace and source file, - /// returns the CallableDeclarationHeader with the information on that callable as Value. - /// Returns Null as well as a list with namespaces containing a callable with that name if this is not the case. + /// Given a qualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be resolved within the given parent namespace and source file, and the callable is accessible. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. + member this.TryGetCallable (callableName : QsQualifiedName) (nsName, source) = + this.TryGetCallableHeader (callableName, None) (nsName, source) + + /// Given an unqualified callable name, returns the corresponding CallableDeclarationHeader in a ResolutionResult if + /// the qualifier can be uniquely resolved within the given parent namespace and source file, and the callable is + /// accessible. + /// + /// Returns an Ambiguous result with a list with namespaces containing a type with that name if the name cannot be + /// uniquely resolved. member this.TryResolveAndGetCallable cName (nsName, source) = + let toHeader (declaredNs, (declaredSource, _)) = + match this.TryGetCallableHeader ({Namespace = declaredNs; Name = cName}, Some declaredSource) + (nsName, source) with + | Found value -> value + | _ -> QsCompilerError.Raise "Expected to find the header corresponding to a possible resolution" + Exception () |> raise + syncRoot.EnterReadLock() - try match (nsName, source) |> PossibleResolutions (fun ns -> ns.ContainsCallable cName) with - | [(declNS, (declSource, _))] -> this.TryGetCallableHeader ({Namespace = declNS; Name = cName}, Some declSource) (nsName, source) |> function - | Null -> QsCompilerError.Raise "failed to get the callable information about a resolved callable"; Null, Seq.empty - | info -> info, seq {yield declNS} - | resolutions -> Null, resolutions.Select fst + try resolveInOpenNamespaces (fun ns -> ns.TryFindCallable cName) (nsName, source) + |> ResolutionResult.Map toHeader finally syncRoot.ExitReadLock() - /// Given a qualified type name, returns the corresponding TypeDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a type indeed exists. - /// If the type is not defined an any of the references and the source file containing the type declaration is specified (i.e. declSource is Some), - /// throws the corresponding exception if no such type exists in that file. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. + /// Given a qualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be resolved within the given parent namespace and source file, and the type is accessible. + /// + /// If the type is not defined an any of the references and the source file containing the type declaration is + /// specified (i.e. declSource is Some), throws the corresponding exception if no such type exists in that file. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. member private this.TryGetTypeHeader (typeName : QsQualifiedName, declSource) (nsName, source) = - let BuildHeader fullName (source, declaration) = - let fallback () = declaration.Defined |> this.ResolveTypeDeclaration (typeName, source) |> fst + let buildHeader fullName (source, declaration) = + let fallback () = + declaration.Defined |> this.ResolveTypeDeclaration (typeName, source, declaration.Modifiers) |> fst let underlyingType, items = declaration.Resolved.ValueOrApply fallback - Value { + { QualifiedName = fullName Attributes = declaration.ResolvedAttributes + Modifiers = declaration.Modifiers SourceFile = source Position = DeclarationHeader.Offset.Defined declaration.Position SymbolRange = DeclarationHeader.Range.Defined declaration.Range @@ -1290,40 +1549,67 @@ and NamespaceManager TypeItems = items Documentation = declaration.Documentation } + + let findInReferences (ns : Namespace) = + ns.TypesInReferencedAssemblies.[typeName.Name] + |> Seq.map (fun typeHeader -> + if Namespace.IsDeclarationAccessible (false, typeHeader.Modifiers.Access) + then Found typeHeader + else Inaccessible) + |> ResolutionResult.AtMostOne + + let findInSources (ns : Namespace) = function + | Some source -> + // OK to use TypeInSource because this is only evaluated if the type is not in a reference. + let declaration = ns.TypeInSource source typeName.Name + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {typeName with Namespace = ns.Name} (source, declaration)) + else Inaccessible + | None -> + match ns.TypesDefinedInAllSources().TryGetValue typeName.Name with + | true, (source, declaration) -> + if Namespace.IsDeclarationAccessible (true, declaration.Modifiers.Access) + then Found (buildHeader {typeName with Namespace = ns.Name} (source, declaration)) + else Inaccessible + | false, _ -> NotFound + syncRoot.EnterReadLock() try match (nsName, source) |> TryResolveQualifier typeName.Namespace with - | None -> Null - | Some ns -> ns.TypesInReferencedAssemblies.TryGetValue typeName.Name |> function - | true, tDecl -> Value tDecl - | false, _ -> declSource |> function - | Some source -> - let decl = ns.TypeInSource source typeName.Name // ok only because/if we have covered that the type is not in a reference! - BuildHeader {typeName with Namespace = ns.Name} (source, decl) - | None -> - ns.TypesDefinedInAllSources().TryGetValue typeName.Name |> function - | true, (source, decl) -> BuildHeader {typeName with Namespace = ns.Name} (source, decl) - | false, _ -> Null + | None -> NotFound + | Some ns -> + seq { yield findInReferences ns + yield findInSources ns declSource } + |> ResolutionResult.TryFirstBest finally syncRoot.ExitReadLock() - /// Given a qualified type name, returns the corresponding TypeDeclarationHeader as Value, - /// if the qualifier can be resolved within the given parent namespace and source file, and such a type indeed exists. - /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent namespace does not exist. - member this.TryGetType (typeName : QsQualifiedName) (nsName, source) = this.TryGetTypeHeader (typeName, None) (nsName, source) - - /// If the given type name can be uniquely resolved within the given namespace and source file, - /// returns the TypeDeclarationHeader with the information on that type as Value. - /// Returns Null as well as a list with namespaces containing a type with that name if this is not the case. + /// Given a qualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be resolved within the given parent namespace and source file, and the type is accessible. + /// + /// Throws an ArgumentException if the qualifier does not correspond to a known namespace and the given parent + /// namespace does not exist. + member this.TryGetType (typeName : QsQualifiedName) (nsName, source) = + this.TryGetTypeHeader (typeName, None) (nsName, source) + + /// Given an unqualified type name, returns the corresponding TypeDeclarationHeader in a ResolutionResult if the + /// qualifier can be uniquely resolved within the given parent namespace and source file, and the type is + /// accessible. + /// + /// Returns an Ambiguous result with a list with namespaces containing a type with that name if the name cannot be + /// uniquely resolved. member this.TryResolveAndGetType tName (nsName, source) = + let toHeader (declaredNs, (declaredSource, _, _)) = + match this.TryGetTypeHeader ({Namespace = declaredNs; Name = tName}, Some declaredSource) + (nsName, source) with + | Found value -> value + | _ -> QsCompilerError.Raise "Expected to find the header corresponding to a possible resolution" + Exception () |> raise + syncRoot.EnterReadLock() - try match (nsName, source) |> PossibleResolutions (fun ns -> ns.ContainsType tName) with - | [(declNS, (declSource, _))] -> this.TryGetTypeHeader ({Namespace = declNS; Name = tName}, Some declSource) (nsName, source) |> function - | Null -> QsCompilerError.Raise "failed to get the type information about a resolved type"; Null, Seq.empty - | info -> info, seq {yield declNS} - | resolutions -> Null, resolutions.Select fst + try resolveInOpenNamespaces (fun ns -> ns.TryFindType tName) (nsName, source) + |> ResolutionResult.Map toHeader finally syncRoot.ExitReadLock() - - /// Returns the fully qualified namespace name of the given namespace alias (short name). If the alias is already a fully qualified name, + /// Returns the fully qualified namespace name of the given namespace alias (short name). If the alias is already a fully qualified name, /// returns the name unchanged. Returns null if no such name exists within the given parent namespace and source file. /// Throws an ArgumentException if the given parent namespace does not exist. member this.TryResolveNamespaceAlias alias (nsName, source) = @@ -1333,20 +1619,26 @@ and NamespaceManager | Some ns -> ns.Name.Value finally syncRoot.ExitReadLock() - /// Returns the names of all namespaces in which a callable with the given name is declared. + /// Returns the names of all namespaces in which a callable is declared that has the given name and is accessible + /// from source files in the compilation unit. member this.NamespacesContainingCallable cName = // FIXME: we need to handle the case where a callable/type with the same qualified name is declared in several references! syncRoot.EnterReadLock() - try let containsCallable (ns : Namespace) = ns.ContainsCallable cName |> QsNullable<_>.Map (fun _ -> ns.Name) - (Namespaces.Values |> QsNullable<_>.Choose containsCallable).ToImmutableArray() + try Namespaces.Values + |> Seq.choose (fun ns -> + ns.TryFindCallable cName |> ResolutionResult.ToOption |> Option.map (fun _ -> ns.Name)) + |> fun namespaces -> namespaces.ToImmutableArray () finally syncRoot.ExitReadLock() - /// Returns the names of all namespaces in which a type with the given name is declared. + /// Returns the names of all namespaces in which a type is declared that has the given name and is accessible from + /// source files in the compilation unit. member this.NamespacesContainingType tName = // FIXME: we need to handle the case where a callable/type with the same qualified name is declared in several references! syncRoot.EnterReadLock() - try let containsType (ns : Namespace) = ns.ContainsType tName |> QsNullable<_>.Map (fun _ -> ns.Name) - (Namespaces.Values |> QsNullable<_>.Choose containsType).ToImmutableArray() + try Namespaces.Values + |> Seq.choose (fun ns -> + ns.TryFindType tName |> ResolutionResult.ToOption |> Option.map (fun _ -> ns.Name)) + |> fun namespaces -> namespaces.ToImmutableArray () finally syncRoot.ExitReadLock() /// Returns the name of all namespaces declared in source files or referenced assemblies. @@ -1438,5 +1730,4 @@ and NamespaceManager let typesHash = types |> Seq.map (fun (ns, name, t) -> ns, name, typeHash t) |> Seq.toList |> hash let importsHash = imports |> Seq.toList |> hash hash (callablesHash, typesHash), importsHash - finally syncRoot.ExitReadLock() - + finally syncRoot.ExitReadLock() 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/DataTypes.fs b/src/QsCompiler/DataStructures/DataTypes.fs index 92b86673ed..1bbe527321 100644 --- a/src/QsCompiler/DataStructures/DataTypes.fs +++ b/src/QsCompiler/DataStructures/DataTypes.fs @@ -13,11 +13,16 @@ type QsNullable<'T> = // to avoid having to include the F# core in the C# part o | Null | Value of 'T + /// If the given nullable has a value, applies the given function to it and returns the result, which must be + /// another nullable. Returns Null otherwise. + static member Bind fct = function + | Null -> Null + | Value v -> fct v + /// If the given nullable has a value, applies the given function to it and returns the result as Value, /// and returns Null otherwise. - static member Map fct = function - | Null -> Null - | Value v -> Value (fct v) + static member Map fct = + QsNullable<_>.Bind (fct >> Value) /// If the given nullable has a value, applies the given function to it. static member Iter fct = function diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index e960a53ee0..92bd670a0d 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -187,6 +187,8 @@ type ErrorCode = | AliasForOpenedNamespace = 6019 | InvalidNamespaceAliasName = 6020 // i.e. the chosen alias already exists | ConflictInReferences = 6021 + | InaccessibleType = 6022 + | InaccessibleCallable = 6023 | ExpectingUnqualifiedSymbol = 6101 | ExpectingItemName = 6102 @@ -197,6 +199,8 @@ type ErrorCode = | UnknownTypeParameterName = 6107 | UnknownItemName = 6108 | NotMarkedAsAttribute = 6109 + | InaccessibleTypeInNamespace = 6110 + | InaccessibleCallableInNamespace = 6111 | ArgumentTupleShapeMismatch = 6201 | ArgumentTupleMismatch = 6202 @@ -251,6 +255,8 @@ type ErrorCode = | RUSloopWithinAutoInversion = 6315 | QuantumDependencyOutsideExprStatement = 6316 | InvalidReassignmentInApplyBlock = 6317 + | TypeLessAccessibleThanParentType = 6318 + | TypeLessAccessibleThanParentCallable = 6319 | UnexpectedCommandLineCompilerException = 7001 | MissingInputFileOrSnippet = 7002 @@ -526,6 +532,8 @@ type DiagnosticItem = | ErrorCode.TypeConstructorOverlapWithCallable -> "Invalid type declaration. A function or operation with the name \"{0}\" already exists." | ErrorCode.UnknownType -> "No type with the name \"{0}\" exists in any of the open namespaces." | ErrorCode.AmbiguousType -> "Multiple open namespaces contain a type with the name \"{0}\". Use a fully qualified name instead. Open namespaces containing a type with that name are {1}." + | ErrorCode.InaccessibleType -> "The type {0} exists in an open namespace, but is not accessible from here." + | ErrorCode.InaccessibleCallable -> "The callable {0} exists in an open namespace, but is not accessible from here." | ErrorCode.AmbiguousCallable -> "Multiple open namespaces contain a callable with the name \"{0}\". Use a fully qualified name instead. Open namespaces containing a callable with that name are {1}." | ErrorCode.TypeSpecializationMismatch -> "Invalid specialization declaration. The type specializations do not match the expected number of type parameters. Expecting {0} type argument(s)." | ErrorCode.SpecializationForUnknownCallable -> "No callable with the name \"{0}\" exists in the current namespace. Specializations need to be declared in the same namespace as the callable they extend." @@ -550,6 +558,8 @@ type DiagnosticItem = | ErrorCode.UnknownTypeParameterName -> "No type parameter with the name \"{0}\" exists." | ErrorCode.UnknownItemName -> "The type {0} does not define an item with name \"{1}\"." | ErrorCode.NotMarkedAsAttribute -> "The type {0} is not marked as an attribute. Add \"@Attribute()\" above its declaration to indicate that it may be used as attribute." + | ErrorCode.InaccessibleTypeInNamespace -> "The type {0} in namespace {1} is not accessible from here." + | ErrorCode.InaccessibleCallableInNamespace -> "The callable {0} in namespace {1} is not accessible from here." | ErrorCode.ArgumentTupleShapeMismatch -> "The shape of the given tuple does not match the expected type. Got an argument of type {0}, expecting one of type {1} instead." | ErrorCode.ArgumentTupleMismatch -> "The type of the given tuple does not match the expected type. Got an argument of type {0}, expecting one of type {1} instead." @@ -604,6 +614,8 @@ type DiagnosticItem = | ErrorCode.RUSloopWithinAutoInversion -> "Auto-generation of inversions is not supported for operations that contain repeat-until-success-loops." | ErrorCode.QuantumDependencyOutsideExprStatement -> "Auto-generation of inversions is not supported for operations that contain operation calls outside expression statements." | ErrorCode.InvalidReassignmentInApplyBlock -> "Variables that are used in the within-block (specifying the outer transformation) cannot be reassigned in the apply-block (specifying the inner transformation)." + | ErrorCode.TypeLessAccessibleThanParentType -> "The type {0} is less accessible than the parent type {1}." + | ErrorCode.TypeLessAccessibleThanParentCallable -> "The type {0} is less accessible than the callable {1}." | ErrorCode.UnexpectedCommandLineCompilerException -> "The command line compiler threw an exception." | ErrorCode.MissingInputFileOrSnippet -> "The command line compiler needs a list of files or a code snippet to process." diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index c6dfb3462b..5afde67b98 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -155,6 +155,9 @@ module Declarations = /// keyword for a Q# declaration let Namespace = "namespace" + + /// keyword for a Q# declaration modifier + let Internal = "internal" /// contains keywords for Q# directives diff --git a/src/QsCompiler/DataStructures/SyntaxTokens.fs b/src/QsCompiler/DataStructures/SyntaxTokens.fs index 4af35c0fed..d1d85df8fd 100644 --- a/src/QsCompiler/DataStructures/SyntaxTokens.fs +++ b/src/QsCompiler/DataStructures/SyntaxTokens.fs @@ -192,6 +192,23 @@ type CallableSignature = { Characteristics : Characteristics } +/// Defines where a global declaration may be accessed. +[] +type AccessModifier = + /// For callables and types, the default access modifier is public, which means the type or callable can be used + /// from anywhere. For specializations, the default access modifier is the same as the parent callable. + | DefaultAccess + /// Internal access means that a type or callable may only be used from within the compilation unit in which it is + /// declared. + | Internal + +/// Used to represent Q# keywords that may be attached to a declaration to modify its visibility or behavior. +[] +type Modifiers = { + /// Defines where a global declaration may be accessed. + Access : AccessModifier +} + type QsFragmentKind = | ExpressionStatement of QsExpression | ReturnStatement of QsExpression @@ -214,9 +231,9 @@ type QsFragmentKind = | AdjointDeclaration of QsSpecializationGenerator | ControlledDeclaration of QsSpecializationGenerator | ControlledAdjointDeclaration of QsSpecializationGenerator -| OperationDeclaration of QsSymbol * CallableSignature -| FunctionDeclaration of QsSymbol * CallableSignature -| TypeDefinition of QsSymbol * QsTuple +| OperationDeclaration of Modifiers * QsSymbol * CallableSignature +| FunctionDeclaration of Modifiers * QsSymbol * CallableSignature +| TypeDefinition of Modifiers * QsSymbol * QsTuple | DeclarationAttribute of QsSymbol * QsExpression | OpenDirective of QsSymbol * QsNullable | NamespaceDeclaration of QsSymbol diff --git a/src/QsCompiler/DataStructures/SyntaxTree.fs b/src/QsCompiler/DataStructures/SyntaxTree.fs index aa2c70be09..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 @@ -671,6 +672,8 @@ type QsCallable = { FullName : QsQualifiedName /// contains all attributes associated with the callable Attributes : ImmutableArray + /// Represents the Q# keywords attached to the declaration that modify its behavior. + Modifiers : Modifiers /// identifier for the file the callable is declared in SourceFile : NonNullable /// Contains the location information for the declared callable. @@ -714,6 +717,8 @@ type QsCustomType = { FullName : QsQualifiedName /// contains all attributes associated with the type Attributes : ImmutableArray + /// Represents the Q# keywords attached to the declaration that modify its behavior. + Modifiers : Modifiers /// identifier for the file the type is declared in SourceFile : NonNullable /// Contains the location information for the declared type. @@ -735,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. @@ -780,8 +786,3 @@ type QsCompilation = { /// In the case of a library the array is empty. EntryPoints : ImmutableArray } - - - - - diff --git a/src/QsCompiler/DocumentationParser/DocNamespace.cs b/src/QsCompiler/DocumentationParser/DocNamespace.cs index d32cc887b4..4f70b62d99 100644 --- a/src/QsCompiler/DocumentationParser/DocNamespace.cs +++ b/src/QsCompiler/DocumentationParser/DocNamespace.cs @@ -3,11 +3,11 @@ #nullable enable using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; using YamlDotNet.RepresentationModel; @@ -34,11 +34,11 @@ internal class DocNamespace internal DocNamespace(QsNamespace ns, IEnumerable? sourceFiles = null) { var sourceFileSet = sourceFiles == null ? null : new HashSet(sourceFiles); - bool IsVisible(NonNullable qualifiedName, NonNullable source) + bool IsVisible(NonNullable source, AccessModifier access, NonNullable qualifiedName) { var name = qualifiedName.Value; var includeInDocs = sourceFileSet == null || sourceFileSet.Contains(source.Value); - return includeInDocs && !(name.StartsWith("_") || name.EndsWith("_") + return includeInDocs && access.IsDefaultAccess && !(name.StartsWith("_") || name.EndsWith("_") || name.EndsWith("Impl", StringComparison.InvariantCultureIgnoreCase) || name.EndsWith("ImplA", StringComparison.InvariantCultureIgnoreCase) || name.EndsWith("ImplC", StringComparison.InvariantCultureIgnoreCase) @@ -65,7 +65,7 @@ bool IsVisible(NonNullable qualifiedName, NonNullable source) if (item is QsNamespaceElement.QsCallable c) { var callable = c.Item; - if (IsVisible(callable.FullName.Name, callable.SourceFile) && + if (IsVisible(callable.SourceFile, callable.Modifiers.Access, callable.FullName.Name) && (callable.Kind != QsCallableKind.TypeConstructor)) { items.Add(new DocCallable(name, callable)); @@ -74,7 +74,7 @@ bool IsVisible(NonNullable qualifiedName, NonNullable source) else if (item is QsNamespaceElement.QsCustomType u) { var udt = u.Item; - if (IsVisible(udt.FullName.Name, udt.SourceFile)) + if (IsVisible(udt.SourceFile, udt.Modifiers.Access, udt.FullName.Name)) { items.Add(new DocUdt(name, udt)); } @@ -213,10 +213,13 @@ void WriteItem(DocItem i) } /// - /// Writes the YAML file for this namespace. + /// Writes the YAML file for this namespace to a stream. /// - /// The directory to write the file to - internal void WriteToFile(string directoryPath) + /// The stream to write to. + /// + /// The mapping node representing the preexisting contents of this namespace's YAML file. + /// + internal void WriteToStream(Stream stream, YamlMappingNode? rootNode = null) { string ToSequenceKey(string itemTypeName) { @@ -239,8 +242,7 @@ string ToSequenceKey(string itemTypeName) return ""; } - var rootNode = Utils.ReadYamlFile(directoryPath, name) as YamlMappingNode ?? new YamlMappingNode(); - + rootNode ??= new YamlMappingNode(); rootNode.AddStringMapping(Utils.UidKey, uid); rootNode.AddStringMapping(Utils.NameKey, name); if (!String.IsNullOrEmpty(summary)) @@ -251,7 +253,7 @@ string ToSequenceKey(string itemTypeName) var itemTypeNodes = new Dictionary>(); // Collect existing items - foreach (var itemType in new []{Utils.FunctionKind, Utils.OperationKind, Utils.UdtKind}) + foreach (var itemType in new[] { Utils.FunctionKind, Utils.OperationKind, Utils.UdtKind }) { var seqKey = ToSequenceKey(itemType); var thisList = new SortedDictionary(); @@ -308,14 +310,22 @@ string ToSequenceKey(string itemTypeName) } var doc = new YamlDocument(rootNode); - var stream = new YamlStream(doc); + var yamlStream = new YamlStream(doc); + using var output = new StreamWriter(stream); + output.WriteLine("### " + Utils.QsNamespaceYamlMime); + yamlStream.Save(output, false); + } + + /// + /// Writes the YAML file for this namespace. + /// + /// The directory to write the file to + internal void WriteToFile(string directoryPath) + { + var rootNode = Utils.ReadYamlFile(directoryPath, name) as YamlMappingNode; var tocFileName = Path.Combine(directoryPath, name + Utils.YamlExtension); - using (var text = new StreamWriter(File.Open(tocFileName, FileMode.Create))) - { - text.WriteLine("### " + Utils.QsNamespaceYamlMime); - stream.Save(text, false); - } + WriteToStream(File.Open(tocFileName, FileMode.Create), rootNode); } } } diff --git a/src/QsCompiler/DocumentationParser/Utils.cs b/src/QsCompiler/DocumentationParser/Utils.cs index 1b79293474..1b65c46103 100644 --- a/src/QsCompiler/DocumentationParser/Utils.cs +++ b/src/QsCompiler/DocumentationParser/Utils.cs @@ -340,6 +340,7 @@ internal static string CallableToSyntax(QsCallable callable) /// The syntax string internal static string CustomTypeToSyntax(QsCustomType customType) { + // TODO: Include modifiers. var sb = new StringBuilder(); sb.Append("newtype "); sb.Append(customType.FullName.Name.Value); diff --git a/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs b/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs index 05045779bc..9f8c6a0961 100644 --- a/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/DeclarationVerification.fs @@ -66,12 +66,12 @@ let public OpenedNamespaceName this onInvalid = this |> OpenedNamespace |> NameOnly onInvalid /// If the given fragment kind is a type declaration, -/// returns the symbol for the type as well as the declared underlying type as Value. +/// returns the symbol for the type as well as the declared underlying type and modifiers as Value. /// Returns Null otherwise. [] let public DeclaredType this = match this with - | TypeDefinition (sym, decl) -> (sym, decl) |> Value + | TypeDefinition (mods, sym, decl) -> (sym, (mods, decl)) |> Value | _ -> Null /// If the given fragment kind is a type declaration, @@ -83,13 +83,13 @@ let public DeclaredTypeName this onInvalid = this |> DeclaredType |> NameOnly onInvalid /// If the given fragment kind is a callable declaration, -/// returns the symbol for the callable as well as its declared kind and signature as Value. +/// returns the symbol for the callable as well as its declared kind, signature and modifiers as Value. /// Returns Null otherwise. [] let public DeclaredCallable this = match this with - | FunctionDeclaration (sym, decl) -> (sym, (QsCallableKind.Function, decl)) |> Value - | OperationDeclaration (sym, decl) -> (sym, (QsCallableKind.Operation, decl)) |> Value + | FunctionDeclaration (mods, sym, decl) -> (sym, (QsCallableKind.Function, mods, decl)) |> Value + | OperationDeclaration (mods, sym, decl) -> (sym, (QsCallableKind.Operation, mods, decl)) |> Value | _ -> Null /// If the given fragment kind is a callable declaration, diff --git a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs b/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs index ad1333f429..3b3aec8f50 100644 --- a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs +++ b/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs @@ -6,7 +6,6 @@ namespace Microsoft.Quantum.QsCompiler.SymbolTracker open System open System.Collections.Generic open System.Collections.Immutable -open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics @@ -71,11 +70,11 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// IMPORTANT: these need to be adapted if we want to support type specializations and/or external specializations! let parentIsOperation, typeParameters, expectedReturnType = match GlobalSymbols().TryGetCallable parent (parent.Namespace, sourceFile) with - | Null -> ArgumentException "the given NamespaceManager does not contain a callable with the given parent name" |> raise - | Value decl -> + | Found decl -> let isOperation = decl.Kind |> function | QsCallableKind.Operation -> true | _ -> false let validTypeParams = decl.Signature.TypeParameters |> Seq.choose (function | ValidName name -> Some name | InvalidName -> None) isOperation, validTypeParams.ToImmutableArray(), decl.Signature.ReturnType |> StripPositionInfo.Apply // NOTE: valid only since we do not yet support external and/or type specializations + | _ -> ArgumentException "the given NamespaceManager does not contain a callable with the given parent name" |> raise /// If a local variable with the given name is visible on the current scope, /// returns the dictionary that contains its declaration as Value. @@ -93,7 +92,7 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// source file, namespace, and callable associated with this symbol tracker instance. let globalTypeWithName (ns, name : NonNullable) = ns |> function | None -> GlobalSymbols().TryResolveAndGetType name (parent.Namespace, sourceFile) - | Some nsName -> GlobalSymbols().TryGetType (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile), Seq.empty + | Some nsName -> GlobalSymbols().TryGetType (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile) /// If a callable declaration (including type constructors!) for a callable with the given name exists in GlobalSymbols, /// returns a its header information as Value. Returns Null otherwise. @@ -101,7 +100,7 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// source file, namespace, and callable associated with this symbol tracker instance. let globalCallableWithName (ns, name : NonNullable) = ns |> function | None -> GlobalSymbols().TryResolveAndGetCallable name (parent.Namespace, sourceFile) - | Some nsName -> GlobalSymbols().TryGetCallable (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile), Seq.empty + | Some nsName -> GlobalSymbols().TryGetCallable (QsQualifiedName.New (nsName, name)) (parent.Namespace, sourceFile) /// the namespace and callable declaration within which the symbols tracked by this SymbolTracker instance are used member this.Parent = parent @@ -150,8 +149,8 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// Throws an InvalidOperationException if no scope is currently open. member this.TryAddVariableDeclartion (decl : LocalVariableDeclaration>) = if pushedScopes.Length = 0 then InvalidOperationException "no scope is currently open" |> raise - if (globalTypeWithName (None, decl.VariableName)) |> fst <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalTypeAlreadyExists, [decl.VariableName.Value]) |] - elif (globalCallableWithName (None, decl.VariableName)) |> fst <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalCallableAlreadyExists, [decl.VariableName.Value]) |] + if (globalTypeWithName (None, decl.VariableName)) <> NotFound then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalTypeAlreadyExists, [decl.VariableName.Value]) |] + elif (globalCallableWithName (None, decl.VariableName)) <> NotFound then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.GlobalCallableAlreadyExists, [decl.VariableName.Value]) |] elif (localVariableWithName decl.VariableName) <> Null then false, [| decl.Range |> QsCompilerDiagnostic.Error (ErrorCode.LocalVariableAlreadyExists, [decl.VariableName.Value]) |] else pushedScopes.Head.LocalVariables.Add(decl.VariableName, decl); true, [||] @@ -210,18 +209,24 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif let idType = kind ((argType, returnType), decl.Information) |> ResolvedType.New LocalVariableDeclaration.New false (defaultLoc, GlobalCallable fullName, idType, false), decl.TypeParameters - let resolveGlobal (sym : NonNullable) input = + let addDiagnosticForSymbol code args = + qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error (code, args) |> addDiagnostic + invalid + + let resolveGlobal (ns : NonNullable option, sym : NonNullable) input = match input with - | Value (decl : CallableDeclarationHeader), _ -> decl.Kind |> function + | Found (decl : CallableDeclarationHeader) -> decl.Kind |> function | QsCallableKind.Operation -> buildCallable QsTypeKind.Operation decl.QualifiedName decl.Signature decl.Attributes | QsCallableKind.TypeConstructor | QsCallableKind.Function -> buildCallable (fst >> QsTypeKind.Function) decl.QualifiedName decl.Signature decl.Attributes - | Null, (possibleResolutions : NonNullable seq) -> - let resolutionStrings = String.Join(", ", possibleResolutions |> Seq.map (fun nsName -> nsName.Value)) - let ambiguousCallableErr = (ErrorCode.AmbiguousCallable, [sym.Value; resolutionStrings]) - let errCode = if possibleResolutions.Count() > 1 then ambiguousCallableErr else (ErrorCode.UnknownIdentifier, [sym.Value]) - qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error errCode |> addDiagnostic; - invalid + | Ambiguous possibilities -> + let possibleNames = String.Join(", ", possibilities |> Seq.map (fun nsName -> nsName.Value)) + addDiagnosticForSymbol ErrorCode.AmbiguousCallable [sym.Value; possibleNames] + | Inaccessible -> + match ns with + | None -> addDiagnosticForSymbol ErrorCode.InaccessibleCallable [sym.Value] + | Some ns -> addDiagnosticForSymbol ErrorCode.InaccessibleCallableInNamespace [sym.Value; ns.Value] + | NotFound -> addDiagnosticForSymbol ErrorCode.UnknownIdentifier [sym.Value] let resolveNative sym = match localVariableWithName sym with @@ -229,14 +234,13 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif let decl = dict.[sym] let properties = (defaultLoc, LocalVariable sym, decl.Type |> StripPositionInfo.Apply, decl.InferredInformation.HasLocalQuantumDependency) properties |> LocalVariableDeclaration.New decl.InferredInformation.IsMutable, ImmutableArray<_>.Empty - | Null -> globalCallableWithName (None, sym) |> resolveGlobal sym + | Null -> globalCallableWithName (None, sym) |> resolveGlobal (None, sym) match qsSym.Symbol with | InvalidSymbol -> invalid | Symbol sym -> resolveNative sym - | QualifiedSymbol (ns, sym) -> globalCallableWithName (Some ns, sym) |> resolveGlobal sym - | _ -> qsSym.RangeOrDefault |> QsCompilerDiagnostic.Error (ErrorCode.ExpectingIdentifier, []) |> addDiagnostic; - invalid + | QualifiedSymbol (ns, sym) -> globalCallableWithName (Some ns, sym) |> resolveGlobal (Some ns, sym) + | _ -> addDiagnosticForSymbol ErrorCode.ExpectingIdentifier [] /// Given a Q# type, resolves it calling the NamespaceManager associated with this symbol tracker. /// For each diagnostic generated during the resolution, calls the given addDiagnostics function on it. @@ -250,9 +254,11 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// Adds a suitable error using the given function and returns None if no declaration can be found. member private this.TryGetTypeDeclaration addError (udt : UserDefinedType) = match globalTypeWithName (Some udt.Namespace, udt.Name) with - | Value decl, _ -> Value decl - | Null, _ -> // may occur when the return type of a referenced callable is defined in an assembly that is not referenced - addError (ErrorCode.IndirectlyReferencedExpressionType, [sprintf "%s.%s" udt.Namespace.Value udt.Name.Value]); Null + | Found decl -> Value decl + | _ -> + // may occur when the return type of a referenced callable is defined in an assembly that is not referenced + addError (ErrorCode.IndirectlyReferencedExpressionType, [sprintf "%s.%s" udt.Namespace.Value udt.Name.Value]) + Null /// Given the fully qualified name of a user defined type, returns its underlying type where all range information is stripped. /// Adds a suitable diagnostic and returns an invalid type if the underlying type could not be determined. diff --git a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs index 1ae3433ed8..3158e6fda2 100644 --- a/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs +++ b/src/QsCompiler/SyntaxProcessor/SyntaxExtensions.fs @@ -152,34 +152,34 @@ let public SymbolInformation fragmentKind = let chooseValues = QsNullable<_>.Choose id >> Seq.toList let addVariable var (syms, ts, exs) = var :: syms, ts, exs fragmentKind |> function - | QsFragmentKind.ExpressionStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ReturnStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.FailStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.MutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ImmutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ValueUpdate (lhs, rhs) -> [], ([lhs;rhs], []) |> collectWith SymbolsFromExpr - | QsFragmentKind.IfClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ElifClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.ElseClause -> [], ([] , [], []) - | QsFragmentKind.ForLoopIntro (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.WhileLoopIntro ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.RepeatIntro -> [], ([] , [], []) - | QsFragmentKind.UntilSuccess (ex,_) -> [], ([ex] , []) |> collectWith SymbolsFromExpr - | QsFragmentKind.WithinBlockIntro -> [], ([] , [], []) - | QsFragmentKind.ApplyBlockIntro -> [], ([] , [], []) - | QsFragmentKind.UsingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer - | QsFragmentKind.BorrowingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer - | QsFragmentKind.BodyDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.AdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.ControlledDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.ControlledAdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) - | QsFragmentKind.OperationDeclaration (n, signature) -> (n, signature) |> SymbolsInCallableDeclaration - | QsFragmentKind.FunctionDeclaration (n, signature) -> (n, signature) |> SymbolsInCallableDeclaration - | QsFragmentKind.TypeDefinition (sym, t) -> (sym, t) |> SymbolsInArgumentTuple - | QsFragmentKind.DeclarationAttribute (sym, ex) -> [], ([AttributeAsCallExpr (sym, ex)], []) |> collectWith SymbolsFromExpr |> addVariable sym - | QsFragmentKind.NamespaceDeclaration sym -> sym |> SymbolDeclarations, ([], [], []) - | QsFragmentKind.OpenDirective (nsName, alias) -> [alias] |> chooseValues, ([nsName], [], []) - | QsFragmentKind.InvalidFragment _ -> [], ([], [], []) + | QsFragmentKind.ExpressionStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ReturnStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.FailStatement ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.MutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ImmutableBinding (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ValueUpdate (lhs, rhs) -> [], ([lhs;rhs], []) |> collectWith SymbolsFromExpr + | QsFragmentKind.IfClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ElifClause ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.ElseClause -> [], ([] , [], []) + | QsFragmentKind.ForLoopIntro (sym, ex) -> sym |> SymbolDeclarations, ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.WhileLoopIntro ex -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.RepeatIntro -> [], ([] , [], []) + | QsFragmentKind.UntilSuccess (ex,_) -> [], ([ex] , []) |> collectWith SymbolsFromExpr + | QsFragmentKind.WithinBlockIntro -> [], ([] , [], []) + | QsFragmentKind.ApplyBlockIntro -> [], ([] , [], []) + | QsFragmentKind.UsingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer + | QsFragmentKind.BorrowingBlockIntro (sym, init) -> sym |> SymbolDeclarations, init |> VariablesInInitializer + | QsFragmentKind.BodyDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.AdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.ControlledDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.ControlledAdjointDeclaration gen -> gen |> SymbolsInGenerator, ([], [], []) + | QsFragmentKind.OperationDeclaration (_, n, signature) -> (n, signature) |> SymbolsInCallableDeclaration + | QsFragmentKind.FunctionDeclaration (_, n, signature) -> (n, signature) |> SymbolsInCallableDeclaration + | QsFragmentKind.TypeDefinition (_, sym, t) -> (sym, t) |> SymbolsInArgumentTuple + | QsFragmentKind.DeclarationAttribute (sym, ex) -> [], ([AttributeAsCallExpr (sym, ex)], []) |> collectWith SymbolsFromExpr |> addVariable sym + | QsFragmentKind.NamespaceDeclaration sym -> sym |> SymbolDeclarations, ([], [], []) + | QsFragmentKind.OpenDirective (nsName, alias) -> [alias] |> chooseValues, ([nsName], [], []) + | QsFragmentKind.InvalidFragment _ -> [], ([], [], []) |> SymbolInformation.New let rec private ExpressionsInInitializer item = item.Initializer |> function @@ -211,13 +211,13 @@ let public CallExpressions fragmentKind = let private tryResolveWith resolve extract (currentNS, source) = function | QsSymbolKind.Symbol sym -> try resolve sym (currentNS, source) |> function - | Value decl, _ -> Some decl, Some sym - | Null, _ -> None, Some sym + | Found decl -> Some decl, Some sym + | _ -> None, Some sym with | :? ArgumentException -> None, Some sym | QsSymbolKind.QualifiedSymbol (ns, sym) -> try extract {Namespace = ns; Name = sym} (currentNS, source) |> function - | Value decl -> Some decl, Some sym - | Null -> None, None + | Found decl -> Some decl, Some sym + | _ -> None, None with | :? ArgumentException -> None, None | _ -> None, None @@ -230,6 +230,10 @@ let private globalCallableResolution (symbolTable : NamespaceManager) (currentNS let private newLine = " \n" // spaces first here so it will work with markdown as well let private withNewLine line = sprintf "%s%s" line newLine +/// Converts the first character of the string to uppercase. +let private toUpperFirst (s : string) = + s.[0..0].ToUpper() + s.[1..] + let private AsDocComment (doc : string seq) = if doc = null then null elif doc.Any() then @@ -259,6 +263,12 @@ let private namespaceDocumentation (docs : ILookup, Immutabl let allDoc = docs.SelectMany(fun entry -> entry.SelectMany(fun d -> d.AsEnumerable())) // the key is the source file PrintSummary allDoc markdown +/// Adds a string describing the modifiers in front of the string describing a kind of declaration. +let private showModifiers kind modifiers = + match modifiers.Access with + | DefaultAccess -> kind + | Internal -> "internal " + kind + type private TName () = inherit SyntaxTreeToQsharp.TypeTransformation() override this.OnCharacteristicsExpression characteristics = @@ -283,12 +293,13 @@ let private CharacteristicsAnnotation (ex, format) = let public TypeInfo (symbolTable : NamespaceManager) (currentNS, source) (qsType : QsType) markdown = let udtInfo udt = match udt |> globalTypeResolution symbolTable (currentNS, source) with - | Some decl, _ -> + | Some decl, _ -> + let kind = showModifiers "user-defined type" decl.Modifiers |> toUpperFirst let name = decl.QualifiedName.Name.Value |> withNewLine - let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine + let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let info = sprintf "Underlying type: %s" (TypeName decl.Type) let doc = PrintSummary decl.Documentation markdown - sprintf "User defined type %s%s%s%s" name ns info doc + sprintf "%s %s%s%s%s" kind name ns info doc | None, Some sym -> sprintf "Type %s" sym.Value | _ -> "?" let typeParamName onUnknown (sym : QsSymbol) = @@ -331,10 +342,10 @@ let public TypeInfo (symbolTable : NamespaceManager) (currentNS, source) (qsType | _ -> sprintf "Built-in type %s%s" (typeName qsType.Type) doc |> NonNullable.New -let private printCallableKind capitalize = function - | QsCallableKind.Function -> if capitalize then "Function" else "function" - | QsCallableKind.Operation -> if capitalize then "Operation" else "operation" - | QsCallableKind.TypeConstructor -> if capitalize then "Type constructor" else "type constructor" +let private printCallableKind = function + | QsCallableKind.Function -> "function" + | QsCallableKind.Operation -> "operation" + | QsCallableKind.TypeConstructor -> "type constructor" [] let public PrintArgumentTuple item = @@ -343,8 +354,14 @@ let public PrintArgumentTuple item = [] let public PrintSignature (header : CallableDeclarationHeader) = let callable = - QsCallable.New header.Kind (header.SourceFile, Null) - (header.QualifiedName, header.Attributes, header.ArgumentTuple, header.Signature, ImmutableArray.Empty, ImmutableArray.Empty, QsComments.Empty); + QsCallable.New header.Kind (header.SourceFile, Null) (header.QualifiedName, + header.Attributes, + header.Modifiers, + header.ArgumentTuple, + header.Signature, + ImmutableArray.Empty, + ImmutableArray.Empty, + QsComments.Empty) let signature = SyntaxTreeToQsharp.DeclarationSignature (callable, new Func<_,_>(TypeName)) let annotation = CharacteristicsAnnotation (header.Signature.Information.Characteristics, sprintf "%s%s" newLine) sprintf "%s%s" signature annotation @@ -352,11 +369,12 @@ let public PrintSignature (header : CallableDeclarationHeader) = [] let public VariableInfo (symbolTable : NamespaceManager) (locals : LocalDeclarations) (currentNS, source) (qsSym : QsSymbol) markdown = match qsSym |> globalCallableResolution symbolTable (currentNS, source) with - | Some decl, _ -> - let name = sprintf "%s %s" (printCallableKind true decl.Kind) (PrintSignature decl) |> withNewLine + | Some decl, _ -> + let kind = showModifiers (printCallableKind decl.Kind) decl.Modifiers |> toUpperFirst + let nameAndSignature = PrintSignature decl |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value let doc = PrintSummary decl.Documentation markdown - sprintf "%s%s%s" name ns doc + sprintf "%s %s%s%s" kind nameAndSignature ns doc | None, Some sym -> let localVars = locals.AsVariableLookup() if localVars.ContainsKey sym then @@ -383,25 +401,27 @@ let public DeclarationInfo symbolTable (locals : LocalDeclarations) (currentNS, sprintf "Declaration of %s variable %s%s" kind name info | false, _ -> match qsSym |> globalTypeResolution symbolTable (currentNS, source) with // needs to be before querying callables - | Some decl, _ -> + | Some decl, _ -> + let kind = showModifiers "user-defined type" decl.Modifiers let name = decl.QualifiedName.Name.Value |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let info = sprintf "Underlying type: %s" (decl.Type |> TypeName) let doc = PrintSummary decl.Documentation markdown - sprintf "Declaration of user defined type %s%s%s%s" name ns info doc + sprintf "Declaration of %s %s%s%s%s" kind name ns info doc | None, _ -> match qsSym |> globalCallableResolution symbolTable (currentNS, source) with | Some decl, _ -> - let functorSupport characteristics = - let charEx = SyntaxTreeToQsharp.CharacteristicsExpression characteristics - if String.IsNullOrWhiteSpace charEx then "(None)" else charEx - let name = sprintf "%s %s" (printCallableKind false decl.Kind) decl.QualifiedName.Name.Value |> withNewLine + let kind = showModifiers (printCallableKind decl.Kind) decl.Modifiers + let name = decl.QualifiedName.Name.Value |> withNewLine let ns = sprintf "Namespace: %s" decl.QualifiedName.Namespace.Value |> withNewLine let input = sprintf "Input type: %s" (decl.Signature.ArgumentType |> TypeName) |> withNewLine let output = sprintf "Output type: %s" (decl.Signature.ReturnType |> TypeName) |> withNewLine + let functorSupport characteristics = + let charEx = SyntaxTreeToQsharp.CharacteristicsExpression characteristics + if String.IsNullOrWhiteSpace charEx then "(None)" else charEx let fs = sprintf "Supported functors: %s" (decl.Signature.Information.Characteristics |> functorSupport) let doc = PrintSummary decl.Documentation markdown - sprintf "Declaration of %s%s%s%s%s%s" name ns input output fs doc + sprintf "Declaration of %s %s%s%s%s%s%s" kind name ns input output fs doc | None, _ -> match symbolTable.Documentation().TryGetValue name with | true, docs -> sprintf "Declaration of a partial namespace %s%s" name.Value (namespaceDocumentation (docs, markdown)) @@ -477,5 +497,3 @@ let public SymbolDeclaration (symbolTable : NamespaceManager) (locals : LocalDec | Some decl, _ -> decl.Location |> QsNullable<_>.Map (fun loc -> decl.SourceFile, loc.Offset, loc.Range) | _ -> LocalVariable locals qsSym |> QsNullable<_>.Map (fun (_, pos, range) -> source, pos, range) | _ -> Null - - diff --git a/src/QsCompiler/SyntaxProcessor/TreeVerification.fs b/src/QsCompiler/SyntaxProcessor/TreeVerification.fs index e12df1aaa8..e3660028d8 100644 --- a/src/QsCompiler/SyntaxProcessor/TreeVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/TreeVerification.fs @@ -153,7 +153,8 @@ let CheckDefinedTypesForCycles (definitions : ImmutableArray [it; ot] | QsTypeKind.TupleType vtypeList -> vtypeList |> Seq.toList - | QsTypeKind.UserDefinedType udt -> updateContainedReferences rootIndex (location, QsQualifiedName.New(udt.Namespace, udt.Name)) + | QsTypeKind.UserDefinedType udt -> + updateContainedReferences rootIndex (location, QsQualifiedName.New(udt.Namespace, udt.Name)) | _ -> [] let walk_udts () = // builds up containedTypes and containedIn diff --git a/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs new file mode 100644 index 0000000000..ac831ac137 --- /dev/null +++ b/src/QsCompiler/TestTargets/Libraries/Library1/AccessModifiers.qs @@ -0,0 +1,15 @@ +/// 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 { + internal newtype InternalType = Unit; + + internal function InternalFunction () : Unit {} +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.C { + internal newtype InternalTypeC = Unit; + + internal function InternalFunctionC () : Unit {} +} diff --git a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs new file mode 100644 index 0000000000..72e8ddd8c9 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.QsCompiler.Testing + +open System.Collections.Generic +open System.IO +open Microsoft.Quantum.QsCompiler.DataTypes +open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.SyntaxExtensions +open Microsoft.Quantum.QsCompiler.SyntaxTree +open Xunit + + +type AccessModifierTests (output) = + inherit CompilerTests + (CompilerTests.Compile "TestCases" ["AccessModifiers.qs"] [File.ReadAllLines("ReferenceTargets.txt").[1]], + output) + + member private this.Expect name (diagnostics : IEnumerable) = + let ns = "Microsoft.Quantum.Testing.AccessModifiers" |> NonNullable<_>.New + let name = name |> NonNullable<_>.New + this.Verify (QsQualifiedName.New (ns, name), diagnostics) + + [] + member this.``Callables`` () = + this.Expect "CallableUseOK" [] + this.Expect "CallableReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + + [] + member this.``Types`` () = + this.Expect "TypeUseOK" [] + this.Expect "TypeReferenceInternalInaccessible" [Error ErrorCode.InaccessibleType] + this.Expect "TypeConstructorReferenceInternalInaccessible" [Error ErrorCode.InaccessibleCallable] + + [] + member this.``Callable signatures`` () = + this.Expect "CallableLeaksInternalTypeIn1" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeIn2" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeIn3" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut1" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut2" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "CallableLeaksInternalTypeOut3" [Error ErrorCode.TypeLessAccessibleThanParentCallable] + this.Expect "InternalCallableInternalTypeOK" [] + + [] + member this.``Underlying types`` () = + this.Expect "PublicTypeLeaksInternalType1" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "PublicTypeLeaksInternalType2" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "PublicTypeLeaksInternalType3" [Error ErrorCode.TypeLessAccessibleThanParentType] + this.Expect "InternalTypeInternalTypeOK" [] diff --git a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs index f3a1806ecc..2ae27951e9 100644 --- a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs +++ b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type FunctorAutoGenTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "FunctorGeneration.qs"], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "FunctorGeneration.qs"] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.FunctorGeneration" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs b/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs index beba2f499a..d8c181fc6e 100644 --- a/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs +++ b/src/QsCompiler/Tests.Compiler/CompletionParsingTests.fs @@ -94,7 +94,7 @@ let private operationTopLevelStatement = Keyword "controlled" ] -let testElifElse scope previous = +let private testElifElse scope previous = let statement = match scope with | Operation | OperationTopLevel -> operationStatement @@ -109,6 +109,12 @@ let testElifElse scope previous = ("else ", []) ] +let private testWithModifiers tests = + List.concat [ + tests + List.map (fun (input, result) -> ("internal " + input, result)) tests + ] |> List.iter (matches NamespaceTopLevel Null) + [] let ``Top-level parser tests`` () = List.iter (matches TopLevel Null) [ @@ -128,6 +134,7 @@ let ``Namespace top-level parser tests`` () = Keyword "function" Keyword "operation" Keyword "newtype" + Keyword "internal" Keyword "open" ] List.iter (matches NamespaceTopLevel Null) [ @@ -143,11 +150,15 @@ let ``Namespace top-level parser tests`` () = ("newt", keywords) ("newtype", keywords) ("open", keywords) + ("pri", keywords) + ("int", keywords) + ("internal", keywords) + ("internal ", [Keyword "function"; Keyword "operation"; Keyword "newtype"]) ] [] let ``Function declaration parser tests`` () = - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("function ", [Declaration]) ("function Foo", [Declaration]) ("function Foo ", []) @@ -200,7 +211,7 @@ let ``Operation declaration parser tests`` () = Keyword "Adj" Keyword "Ctl" ] - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("operation ", [Declaration]) ("operation Foo", [Declaration]) ("operation Foo ", []) @@ -281,7 +292,7 @@ let ``Operation declaration parser tests`` () = [] let ``Type declaration parser tests`` () = - List.iter (matches NamespaceTopLevel Null) [ + testWithModifiers [ ("newtype ", [Declaration]) ("newtype MyType", [Declaration]) ("newtype MyType ", []) diff --git a/src/QsCompiler/Tests.Compiler/ExecutionTests.fs b/src/QsCompiler/Tests.Compiler/ExecutionTests.fs index a4a4db38cd..3e222a6ffc 100644 --- a/src/QsCompiler/Tests.Compiler/ExecutionTests.fs +++ b/src/QsCompiler/Tests.Compiler/ExecutionTests.fs @@ -25,8 +25,8 @@ type ExecutionTests (output:ITestOutputHelper) = let ExecuteOnQuantumSimulator cName = let exitCode, ex = ref -101, ref null let out, err = ref (new StringBuilder()), ref (new StringBuilder()) - let exe = File.ReadAllLines("ExecutionTarget.txt").Single() - let args = sprintf "%s %s.%s" exe "Microsoft.Quantum.Testing.ExecutionTests" cName + let exe = File.ReadAllLines("ReferenceTargets.txt").First() + let args = sprintf "\"%s\" %s.%s" exe "Microsoft.Quantum.Testing.ExecutionTests" cName let ranToEnd = ProcessRunner.Run ("dotnet", args, out, err, exitCode, ex, timeout = 10000) Assert.False(String.IsNullOrWhiteSpace exe) Assert.True(ranToEnd) diff --git a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs index 73b1c26f7a..384b25b378 100644 --- a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type GlobalVerificationTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs";"GlobalVerification.qs";"Types.qs";System.IO.Path.Join("LinkingTests", "Core.qs")], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "GlobalVerification.qs"; "Types.qs"; System.IO.Path.Join("LinkingTests", "Core.qs")] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.GlobalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index e97dd4f3ce..773088983c 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -5,7 +5,9 @@ namespace Microsoft.Quantum.QsCompiler.Testing open System open System.Collections.Generic +open System.Collections.Immutable open System.IO +open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.DataTypes @@ -15,18 +17,44 @@ 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 Microsoft.VisualStudio.LanguageServer.Protocol open Xunit open Xunit.Abstractions type LinkingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests" )) ["Core.qs"; "InvalidEntryPoints.qs"], output) + inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests" )) ["Core.qs"; "InvalidEntryPoints.qs"] [], output) let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message)) - let getTempFile () = new Uri(Path.GetFullPath(Path.GetRandomFileName())) + // The file name needs to end in ".qs" so that it isn't ignored by the References.Headers class during the internal renaming tests. + let getTempFile () = Path.GetRandomFileName () + ".qs" |> Path.GetFullPath |> Uri let getManager uri content = CompilationUnitManager.InitializeFileManager(uri, content, compilationManager.PublishDiagnostics, compilationManager.LogException) + let defaultOffset = { + Offset = DiagnosticTools.AsTuple (Position (0, 0)) + Range = QsCompilerDiagnostic.DefaultRange + } + + let qualifiedName ns name = { + Namespace = NonNullable<_>.New ns + Name = NonNullable<_>.New name + } + + let createReferences : seq> -> References = + Seq.map (fun (source, namespaces) -> + KeyValuePair.Create(NonNullable<_>.New source, References.Headers (NonNullable<_>.New source, namespaces))) + >> ImmutableDictionary.CreateRange + >> References + + /// Counts the number of references to the qualified name in all of the namespaces, including the declaration. + let countReferences namespaces (name : QsQualifiedName) = + let references = IdentifierReferences (name, defaultOffset) + Seq.iter (references.Namespaces.OnNamespace >> ignore) namespaces + let declaration = if obj.ReferenceEquals (references.SharedState.DeclarationLocation, null) then 0 else 1 + references.SharedState.Locations.Count + declaration + do let addOrUpdateSourceFile filePath = getManager (new Uri(filePath)) (File.ReadAllText filePath) |> compilationManager.AddOrUpdateSourceFileAsync |> ignore Path.Combine ("TestCases", "LinkingTests", "Core.qs") |> Path.GetFullPath |> addOrUpdateSourceFile @@ -53,19 +81,23 @@ type LinkingTests (output:ITestOutputHelper) = for callable in built.Callables.Values |> Seq.filter inFile do tests.Verify (callable.FullName, diag) - member private this.BuildContent content = + member private this.BuildContent (source, ?references) = + let fileId = getTempFile () + let file = getManager fileId source - let fileId = getTempFile() - let file = getManager fileId content + match references with + | Some references -> compilationManager.UpdateReferencesAsync references |> ignore + | None -> () + compilationManager.AddOrUpdateSourceFileAsync file |> ignore - compilationManager.AddOrUpdateSourceFileAsync(file) |> ignore - let compilationDataStructures = compilationManager.Build() - compilationManager.TryRemoveSourceFileAsync(fileId, false) |> ignore + let compilation = compilationManager.Build () + compilationManager.TryRemoveSourceFileAsync (fileId, false) |> ignore + compilationManager.UpdateReferencesAsync (References ImmutableDictionary<_, _>.Empty) |> ignore - compilationDataStructures.Diagnostics() |> Seq.exists (fun d -> d.IsError()) |> Assert.False - Assert.NotNull compilationDataStructures.BuiltCompilation + compilation.Diagnostics () |> Seq.exists (fun d -> d.IsError ()) |> Assert.False + Assert.NotNull compilation.BuiltCompilation - compilationDataStructures + compilation member private this.CompileMonomorphization input = @@ -111,6 +143,31 @@ 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 chunks = LinkingTests.ReadAndChunkSourceFile "InternalRenaming.qs" + let sourceCompilation = this.BuildContent chunks.[num - 1] + + let namespaces = + sourceCompilation.BuiltCompilation.Namespaces + |> Seq.filter (fun ns -> ns.Name.Value.StartsWith Signatures.InternalRenamingNs) + let references = createReferences ["InternalRenaming.dll", namespaces] + let referenceCompilation = this.BuildContent ("", references) + + let countAll namespaces names = + names |> Seq.map (countReferences namespaces) |> Seq.sum + + let beforeCount = countAll sourceCompilation.BuiltCompilation.Namespaces (Seq.concat [renamed; notRenamed]) + let afterCountOriginal = countAll referenceCompilation.BuiltCompilation.Namespaces renamed + + let newNames = renamed |> Seq.map (fun name -> CompilationUnit.ReferenceDecorator.Decorate (name, 0)) + let afterCount = countAll referenceCompilation.BuiltCompilation.Namespaces (Seq.concat [newNames; notRenamed]) + + Assert.NotEqual (0, beforeCount) + Assert.Equal (0, afterCountOriginal) + Assert.Equal (beforeCount, afterCount) + [] member this.``Monomorphization`` () = @@ -245,3 +302,76 @@ 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"] + + [] + member this.``Group internal specializations by source file`` () = + let chunks = LinkingTests.ReadAndChunkSourceFile "InternalRenaming.qs" + let sourceCompilation = this.BuildContent chunks.[7] + let namespaces = + sourceCompilation.BuiltCompilation.Namespaces + |> Seq.filter (fun ns -> ns.Name.Value.StartsWith Signatures.InternalRenamingNs) + + let references = createReferences ["InternalRenaming1.dll", namespaces + "InternalRenaming2.dll", namespaces] + let referenceCompilation = this.BuildContent ("", references) + let callables = GlobalCallableResolutions referenceCompilation.BuiltCompilation.Namespaces + + for i in 0 .. references.Declarations.Count - 1 do + let name = + CompilationUnit.ReferenceDecorator.Decorate (qualifiedName Signatures.InternalRenamingNs "Foo", i) + let specializations = callables.[name].Specializations + Assert.Equal (4, specializations.Length) + Assert.True (specializations |> Seq.forall (fun s -> s.SourceFile = callables.[name].SourceFile)) diff --git a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs index 2570fd7295..f4854547f2 100644 --- a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs @@ -14,7 +14,7 @@ open Xunit.Abstractions type LocalVerificationTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "LocalVerification.qs"; "Types.qs"; Path.Combine ("LinkingTests", "Core.qs")], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "LocalVerification.qs"; "Types.qs"; Path.Combine ("LinkingTests", "Core.qs")] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.LocalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/SerializationTests.fs b/src/QsCompiler/Tests.Compiler/SerializationTests.fs index 354d580967..c48e8f233d 100644 --- a/src/QsCompiler/Tests.Compiler/SerializationTests.fs +++ b/src/QsCompiler/Tests.Compiler/SerializationTests.fs @@ -148,6 +148,7 @@ module SerializationTests = Kind = QsCallableKind.TypeConstructor QualifiedName = qualifiedName "Microsoft.Quantum" "Pair" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (2,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 13}) |> DeclarationHeader.Range.Defined @@ -161,6 +162,7 @@ module SerializationTests = Kind = QsCallableKind.Function QualifiedName = qualifiedName "Microsoft.Quantum" "emptyFunction" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (4,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 10}, {Line = 1; Column = 23}) |> DeclarationHeader.Range.Defined @@ -174,6 +176,7 @@ module SerializationTests = Kind = QsCallableKind.Operation QualifiedName = qualifiedName "Microsoft.Quantum" "emptyOperation" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (5,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 11}, {Line = 1; Column = 25}) |> DeclarationHeader.Range.Defined @@ -187,6 +190,7 @@ module SerializationTests = Kind = QsCallableKind.TypeConstructor QualifiedName = qualifiedName "Microsoft.Quantum" "Unused" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (3,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 15}) |> DeclarationHeader.Range.Defined @@ -207,6 +211,7 @@ module SerializationTests = { QualifiedName = qualifiedName "Microsoft.Quantum" "Pair" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (2,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 13}) |> DeclarationHeader.Range.Defined @@ -219,6 +224,7 @@ module SerializationTests = { QualifiedName = qualifiedName "Microsoft.Quantum" "Unused" Attributes = ImmutableArray.Empty + Modifiers = {Access = DefaultAccess} SourceFile = "%%%" |> NonNullable.New Position = (3,4) |> DeclarationHeader.Offset.Defined SymbolRange = ({Line = 1; Column = 9}, {Line = 1; Column = 15}) |> DeclarationHeader.Range.Defined @@ -229,15 +235,15 @@ module SerializationTests = |> testOne [] - let CALLABLE_1 = "{\"Kind\":{\"Case\":\"TypeConstructor\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item1__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item2__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"ReturnType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":true}}},\"Documentation\":[\"type constructor for user defined type\"]}" + let CALLABLE_1 = "{\"Kind\":{\"Case\":\"TypeConstructor\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item1__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"__Item2__\"]},\"Type\":{\"Case\":\"Int\"},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":1}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"ReturnType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":true}}},\"Documentation\":[\"type constructor for user defined type\"]}" [] - let TYPE_1 = "{\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"Type\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"TypeItems\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]}]]},\"Documentation\":[]}" + let TYPE_1 = "{\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":2,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":9},\"Item2\":{\"Line\":1,\"Column\":13}},\"Type\":{\"Case\":\"TupleType\",\"Fields\":[[{\"Case\":\"Int\"},{\"Case\":\"Int\"}]]},\"TypeItems\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]},{\"Case\":\"QsTupleItem\",\"Fields\":[{\"Case\":\"Anonymous\",\"Fields\":[{\"Case\":\"Int\"}]}]}]]},\"Documentation\":[]}" [] - let CALLABLE_2 = "{\"Kind\":{\"Case\":\"Function\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":10},\"Item2\":{\"Line\":1,\"Column\":23}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"p\"]},\"Type\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":25},\"Item2\":{\"Line\":1,\"Column\":26}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" + let CALLABLE_2 = "{\"Kind\":{\"Case\":\"Function\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":10},\"Item2\":{\"Line\":1,\"Column\":23}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[{\"Case\":\"QsTupleItem\",\"Fields\":[{\"VariableName\":{\"Case\":\"ValidName\",\"Fields\":[\"p\"]},\"Type\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"InferredInformation\":{\"IsMutable\":false,\"HasLocalQuantumDependency\":false},\"Position\":{\"Case\":\"Null\"},\"Range\":{\"Item1\":{\"Line\":1,\"Column\":25},\"Item2\":{\"Line\":1,\"Column\":26}}}]}]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UserDefinedType\",\"Fields\":[{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"Pair\",\"Range\":{\"Case\":\"Null\"}}]},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" [] let SPECIALIZATION_1 = "{\"Kind\":{\"Case\":\"QsBody\"},\"TypeArguments\":{\"Case\":\"Null\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}},\"Parent\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyFunction\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":4,\"Item2\":43},\"HeaderRange\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":5}},\"Documentation\":[]}" [] - let CALLABLE_3 = "{\"Kind\":{\"Case\":\"Operation\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":25}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UnitType\"},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" + let CALLABLE_3 = "{\"Kind\":{\"Case\":\"Operation\"},\"QualifiedName\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"Modifiers\":{\"Access\":{\"Case\":\"DefaultAccess\"}},\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":4},\"SymbolRange\":{\"Item1\":{\"Line\":1,\"Column\":11},\"Item2\":{\"Line\":1,\"Column\":25}},\"ArgumentTuple\":{\"Case\":\"QsTuple\",\"Fields\":[[]]},\"Signature\":{\"TypeParameters\":[],\"ArgumentType\":{\"Case\":\"UnitType\"},\"ReturnType\":{\"Case\":\"UnitType\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}}},\"Documentation\":[]}" [] let SPECIALIZATION_3 = "{\"Kind\":{\"Case\":\"QsBody\"},\"TypeArguments\":{\"Case\":\"Null\"},\"Information\":{\"Characteristics\":{\"Case\":\"EmptySet\"},\"InferredInformation\":{\"IsSelfAdjoint\":false,\"IsIntrinsic\":false}},\"Parent\":{\"Namespace\":\"Microsoft.Quantum\",\"Name\":\"emptyOperation\"},\"Attributes\":[],\"SourceFile\":\"%%%\",\"Position\":{\"Item1\":5,\"Item2\":39},\"HeaderRange\":{\"Item1\":{\"Line\":1,\"Column\":1},\"Item2\":{\"Line\":1,\"Column\":5}},\"Documentation\":[]}" diff --git a/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs b/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs new file mode 100644 index 0000000000..4292d5d09b --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/AccessModifiers.qs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/// This namespace contains test cases for access modifiers. +namespace Microsoft.Quantum.Testing.AccessModifiers { + open Microsoft.Quantum.Testing.AccessModifiers.A; + open Microsoft.Quantum.Testing.AccessModifiers.B as B; + open Microsoft.Quantum.Testing.AccessModifiers.C; + + // Redefine inaccessible references (see TestTargets\Libraries\Library1\AccessModifiers.qs) + + internal newtype InternalType = Unit; + + internal function InternalFunction () : Unit {} + + // Callables + + function CallableUseOK () : Unit { + InternalFunction(); + InternalFunctionA(); + B.InternalFunctionB(); + } + + function CallableReferenceInternalInaccessible () : Unit { + InternalFunctionC(); + } + + // Types + + function TypeUseOK () : Unit { + let it = InternalType(); + let its = new InternalType[1]; + let ita = InternalTypeA(); + let itas = new InternalTypeA[1]; + let itb = B.InternalTypeB(); + let itbs = new B.InternalTypeB[1]; + } + + function TypeReferenceInternalInaccessible () : Unit { + let itcs = new InternalTypeC[1]; + } + + function TypeConstructorReferenceInternalInaccessible () : Unit { + let itc = InternalTypeC(); + } + + // Callable signatures + + function CallableLeaksInternalTypeIn1 (x : InternalType) : Unit {} + + function CallableLeaksInternalTypeIn2 (x : (Int, InternalType)) : Unit {} + + function CallableLeaksInternalTypeIn3 (x : (Int, (InternalType, Bool))) : Unit {} + + function CallableLeaksInternalTypeOut1 () : InternalType { + return InternalType(); + } + + function CallableLeaksInternalTypeOut2 () : (Int, InternalType) { + return (0, InternalType()); + } + + function CallableLeaksInternalTypeOut3 () : (Int, (InternalType, Bool)) { + return (0, (InternalType(), false)); + } + + internal function InternalCallableInternalTypeOK (x : InternalType) : InternalType { + return InternalType(); + } + + // Underlying types + + newtype PublicTypeLeaksInternalType1 = InternalType; + + newtype PublicTypeLeaksInternalType2 = (Int, InternalType); + + newtype PublicTypeLeaksInternalType3 = (Int, (InternalType, Bool)); + + internal newtype InternalTypeInternalTypeOK = InternalType; +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.A { + internal function InternalFunctionA () : Unit {} + + internal newtype InternalTypeA = Unit; +} + +/// This namespace contains additional definitions of types and callables meant to be used by the +/// Microsoft.Quantum.Testing.AccessModifiers namespace. +namespace Microsoft.Quantum.Testing.AccessModifiers.B { + internal function InternalFunctionB () : Unit {} + + internal newtype InternalTypeB = Unit; +} 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..a28e639c74 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/InternalRenaming.qs @@ -0,0 +1,133 @@ +// 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(); + } +} + +// ================================= +// Test 8: Group internal specializations by source file + +namespace Microsoft.Quantum.Testing.InternalRenaming { + internal operation Foo () : Unit is Adj + Ctl { + body { + } + + adjoint { + } + + controlled (cs, ...) { + } + + controlled adjoint (cs, ...) { + } + } +} diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs index 764104c323..eb03d7079a 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs @@ -90,13 +90,13 @@ type CompilerTests (compilation : CompilationUnitManager.Compilation, output:ITe if other.Any() then NotImplementedException "unknown diagnostics item to verify" |> raise - static member Compile srcFolder files = + static member Compile srcFolder files references = let compileFiles (files : IEnumerable<_>) = let mgr = new CompilationUnitManager(fun ex -> failwith ex.Message) - files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) + files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) |> CompilationUnitManager.InitializeFileManagers - |> mgr.AddOrUpdateSourceFilesAsync + |> mgr.AddOrUpdateSourceFilesAsync |> ignore - mgr.Build() - files |> Seq.map (fun file -> Path.Combine (srcFolder, file)) |> compileFiles - + mgr.UpdateReferencesAsync(new References(ProjectManager.LoadReferencedAssemblies(references))) |> ignore + mgr.Build() + files |> Seq.map (fun file -> Path.Combine (srcFolder, file)) |> compileFiles diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs index 7abc75e0ac..fafd1a95c8 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 1138d77d0f..4f9d319cbb 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -41,6 +41,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -120,6 +123,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -140,6 +146,7 @@ + @@ -161,14 +168,17 @@ - + - + - "$(MSBuildThisFileDirectory)..\TestTargets\Simulation\Example\bin\$(Configuration)\netcoreapp3.1\Example.dll" + $(MSBuildThisFileDirectory)..\TestTargets\Simulation\Example\bin\$(Configuration)\netcoreapp3.1\Example.dll + $(MSBuildThisFileDirectory)..\TestTargets\Libraries\Library1\bin\$(Configuration)\netcoreapp3.1\Library1.dll - + diff --git a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs index 02d13a18ce..cdd8592016 100644 --- a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs +++ b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs @@ -13,7 +13,7 @@ open Xunit.Abstractions type TypeCheckingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "TypeChecking.qs"; "Types.qs"], output) + inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "TypeChecking.qs"; "Types.qs"] [], output) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.TypeChecking" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs index a4e8d649a1..dad33df6a6 100644 --- a/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs +++ b/src/QsCompiler/Tests.DocGenerator/DocParsingTests.cs @@ -10,7 +10,7 @@ using System.IO; using System.Linq; using Xunit; - +using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; namespace Microsoft.Quantum.QsCompiler.Documentation.Testing { @@ -20,17 +20,6 @@ namespace Microsoft.Quantum.QsCompiler.Documentation.Testing public class DocParsingTests { - private readonly Tuple EmptyRange = - new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero); - private readonly QsNullable ZeroLocation = - QsNullable.NewValue(new QsLocation(new Tuple(0, 0), new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); - private readonly NonNullable CanonName = NonNullable.New("Microsoft.Quantum.Canon"); - - private QsQualifiedName MakeFullName(string name) - { - return new QsQualifiedName(CanonName, NonNullable.New(name)); - } - [Fact] public void ParseDocComments() { @@ -158,6 +147,7 @@ element indexes the subsystem on which the generator acts on. var generatorIndexType = new QsCustomType(MakeFullName("GeneratorIndex"), ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), NonNullable.New("GeneratorRepresentation.qs"), ZeroLocation, baseType, @@ -339,6 +329,7 @@ of the generator represented by $U$. var qsCallable = new QsCallable(QsCallableKind.Operation, MakeFullName("AdiabaticStateEnergyUnitary"), ImmutableArray.Empty, + new Modifiers(AccessModifier.DefaultAccess), NonNullable.New("Techniques.qs"), ZeroLocation, signature, diff --git a/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs new file mode 100644 index 0000000000..8cb04e860b --- /dev/null +++ b/src/QsCompiler/Tests.DocGenerator/DocWritingTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; +using static Microsoft.Quantum.QsCompiler.Documentation.Testing.Utils; + +namespace Microsoft.Quantum.QsCompiler.Documentation.Testing +{ + using ArgDeclType = LocalVariableDeclaration; + using QsType = QsTypeKind; + + public class DocWritingTests + { + /// + /// Tests that internal and private items are skipped when generating documentation for a namespace. + /// + [Fact] + public void ExcludeInaccessible() + { + var elements = + new[] { AccessModifier.DefaultAccess, AccessModifier.Internal } + .SelectMany(access => + { + var source = NonNullable.New("Tests.qs"); + var unit = ResolvedType.New(QsType.UnitType); + + var signature = new ResolvedSignature(Array.Empty().ToImmutableArray(), + unit, + unit, + CallableInformation.NoInformation); + var argumentTuple = QsTuple.NewQsTuple(ImmutableArray.Create>()); + var callable = new QsCallable(kind: QsCallableKind.Operation, + fullName: MakeFullName(access + "Operation"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + signature: signature, + argumentTuple: argumentTuple, + specializations: ImmutableArray.Create(), + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); + + var typeItems = QsTuple.NewQsTuple( + ImmutableArray.Create(QsTuple.NewQsTupleItem(QsTypeItem.NewAnonymous(unit)))); + var type = new QsCustomType(fullName: MakeFullName(access + "Type"), + attributes: ImmutableArray.Empty, + modifiers: new Modifiers(access), + sourceFile: source, + location: ZeroLocation, + type: unit, + typeItems: typeItems, + documentation: ImmutableArray.Create(), + comments: QsComments.Empty); + return new[] + { + QsNamespaceElement.NewQsCallable(callable), + QsNamespaceElement.NewQsCustomType(type) + }; + }); + var emptyLookup = Array.Empty>().ToLookup(x => NonNullable.New("")); + var ns = new QsNamespace(CanonName, elements.ToImmutableArray(), emptyLookup); + var docNs = new DocNamespace(ns); + var stream = new MemoryStream(); + docNs.WriteToStream(stream, null); + + var expected = @"### YamlMime:QSharpNamespace +uid: microsoft.quantum.canon +name: Microsoft.Quantum.Canon +operations: +- uid: microsoft.quantum.canon.defaultaccessoperation + summary: '' +newtypes: +- uid: microsoft.quantum.canon.defaultaccesstype + summary: '' +... +"; + var actual = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal(expected, actual); + } + } +} diff --git a/src/QsCompiler/Tests.DocGenerator/Utils.cs b/src/QsCompiler/Tests.DocGenerator/Utils.cs new file mode 100644 index 0000000000..c9badf778a --- /dev/null +++ b/src/QsCompiler/Tests.DocGenerator/Utils.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using System; + +namespace Microsoft.Quantum.QsCompiler.Documentation.Testing +{ + internal static class Utils + { + internal static readonly Tuple EmptyRange = + new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero); + internal static readonly QsNullable ZeroLocation = + QsNullable.NewValue( + new QsLocation(new Tuple(0, 0), + new Tuple(QsPositionInfo.Zero, QsPositionInfo.Zero))); + internal static readonly NonNullable CanonName = NonNullable.New("Microsoft.Quantum.Canon"); + + internal static QsQualifiedName MakeFullName(string name) + { + return new QsQualifiedName(CanonName, NonNullable.New(name)); + } + } +} diff --git a/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs b/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs index f58fc1e20f..051dd94c48 100644 --- a/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/CodeCompletion/FragmentParsing.fs @@ -22,6 +22,10 @@ open Microsoft.Quantum.QsCompiler.TextProcessing.CodeCompletion.ParsingPrimitive #nowarn "40" +/// Parses a declaration modifier list. +let private modifiers = + expectedKeyword qsInternal + /// Parses a callable signature. let private callableSignature = let name = expectedId Declaration (term symbol) @@ -33,11 +37,11 @@ let private callableSignature = /// Parses a function declaration. let private functionDeclaration = - expectedKeyword fctDeclHeader ?>> callableSignature + optR modifiers @>> expectedKeyword fctDeclHeader ?>> callableSignature /// Parses an operation declaration. let private operationDeclaration = - expectedKeyword opDeclHeader ?>> callableSignature ?>> characteristicsAnnotation + optR modifiers @>> expectedKeyword opDeclHeader ?>> callableSignature ?>> characteristicsAnnotation /// Parses a user-defined type declaration. let private udtDeclaration = @@ -46,7 +50,7 @@ let private udtDeclaration = let namedItem = name ?>> expected colon ?>> qsType return! qsType <|>@ tuple1 (namedItem <|>@ udt) } - expectedKeyword typeDeclHeader ?>> name ?>> expected equal ?>> udt + optR modifiers @>> expectedKeyword typeDeclHeader ?>> name ?>> expected equal ?>> udt /// Parses an open directive. let private openDirective = diff --git a/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs b/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs index 17690806e3..fc6e6b8851 100644 --- a/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs +++ b/src/QsCompiler/TextProcessor/CodeCompletion/ParsingPrimitives.fs @@ -110,6 +110,11 @@ let expectedQualifiedSymbol kind = attempt (term qualifiedSymbol |>> fst .>> previousCharSatisfiesNot Char.IsWhiteSpace .>> optional eot) <|> (term qualifiedSymbol >>% []) +/// Optionally parses `p`, backtracking if it consumes EOT so another parser can try, too. Best if used with `@>>`, +/// e.g., `optR foo @>> bar`. +let optR p = + attempt (p .>> previousCharSatisfies ((<>) '\u0004')) <|> lookAhead p <|>% [] + /// `manyR p shouldBacktrack` is like `many p` but is reentrant on the last item if /// 1. The last item is followed by EOT; and /// 2. `shouldBacktrack` succeeds at the beginning of the last item, or the last item consists of only EOT. diff --git a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs index 28dd79d042..c4241ab761 100644 --- a/src/QsCompiler/TextProcessor/QsFragmentParsing.fs +++ b/src/QsCompiler/TextProcessor/QsFragmentParsing.fs @@ -113,6 +113,11 @@ let private allocationScope = let initializerTuple = buildTupleItem validInitializer buildInitializerTuple ErrorCode.InvalidInitializerExpression ErrorCode.MissingInitializerExpression invalidInitializer isTupleContinuation optTupleBrackets (initializerTuple |> symbolBinding equal ErrorCode.ExpectingAssignment) |>> fst +/// Parses keywords that modify the visibility or behavior of a declaration. +let private modifiers = + let accessModifier = (qsInternal.parse >>% Internal) <|>% DefaultAccess + accessModifier |>> fun access -> {Access = access} + /// Parses a Q# operation or function signature. /// Expects type annotations for each symbol in the argument tuple, and raises Missing- or InvalidTypeAnnotation errors otherwise. /// Raises Missing- or InvalidArumentDeclaration error for entirely missing or invalid arguments, @@ -184,60 +189,23 @@ let private functorGenDirective = // parsing all possible directives for all fun // Q# fragments -// making this recursive so any new fragment only needs to be added here (defining the necessary keywords in the Language module) -let rec private getFragments() = // Note: this needs to be a function! - [ - (qsImmutableBinding , letStatement ) - (qsMutableBinding , mutableStatement ) - (qsValueUpdate , setStatement ) - (qsReturn , returnStatement ) - (qsFail , failStatement ) - (qsIf , ifClause ) - (qsElif , elifClause ) - (qsElse , elseClause ) - (qsFor , forHeader ) - (qsWhile , whileHeader ) - (qsRepeat , repeatHeader ) - (qsUntil , untilSuccess ) - (qsWithin , withinHeader ) - (qsApply , applyHeader ) - (qsUsing , usingHeader ) - (qsBorrowing , borrowingHeader ) - (namespaceDeclHeader , namespaceDeclaration ) - (typeDeclHeader , udtDeclaration ) - (opDeclHeader , operationDeclaration ) - (fctDeclHeader , functionDeclaration ) - (ctrlAdjDeclHeader , controlledAdjointDeclaration) // needs to be before adjointDeclaration and controlledDeclaration! - (adjDeclHeader , adjointDeclaration ) - (ctrlDeclHeader , controlledDeclaration ) - (bodyDeclHeader , bodyDeclaration ) - (importDirectiveHeader, openDirective ) - ] - -and private headerCheck = // DO NOT REMOVE - the check is executed once at the beginning, just as it should be - let implementedHeaders = (getFragments() |> List.map (fun x -> (fst x).id)).ToImmutableHashSet() - let existingHeaders = Keywords.FragmentHeaders.ToImmutableHashSet() - if (implementedHeaders.SymmetricExcept existingHeaders).Count <> 0 then - System.NotImplementedException "mismatch between existing Q# fragments and implemented Q# fragments" |> raise - - // namespace and attribute parsing /// Uses buildFragment to parse a Q# OpenDirective as QsFragment. -and private openDirective = +let private openDirective = let invalid = OpenDirective (invalidSymbol, Null) let nsNameAndAlias = let aliasOption = (importedAs.parse >>. expectedNamespaceName eof |>> Value) <|>% Null expectedNamespaceName importedAs.parse .>>. aliasOption - buildFragment importDirectiveHeader.parse nsNameAndAlias invalid OpenDirective eof + buildFragment importDirectiveHeader.parse nsNameAndAlias invalid (fun _ -> OpenDirective) eof /// Uses buildFragment to parse a Q# NamespaceDeclaration as QsFragment. -and private namespaceDeclaration = +let private namespaceDeclaration = let invalid = NamespaceDeclaration invalidSymbol - buildFragment namespaceDeclHeader.parse (expectedNamespaceName eof) invalid NamespaceDeclaration eof + buildFragment namespaceDeclHeader.parse (expectedNamespaceName eof) invalid (fun _ -> NamespaceDeclaration) eof /// Uses buildFragment to parse a Q# DeclarationAttribute as QsFragment. -and private attributeDeclaration = +let private attributeDeclaration = let invalid = DeclarationAttribute (invalidSymbol, unknownExpr) let attributeId = multiSegmentSymbol ErrorCode.InvalidIdentifierName |>> asQualifiedSymbol let expectedArgs = @@ -248,44 +216,54 @@ and private attributeDeclaration = let invalidAttribute = (invalidSymbol, unknownExpr) let invalidErr, missingErr = ErrorCode.InvalidAttributeIdentifier, ErrorCode.MissingAttributeIdentifier expected (attributeId .>>. expectedArgs) invalidErr missingErr invalidAttribute attributeIntro - buildFragment attributeIntro expectedAttribute invalid DeclarationAttribute attributeIntro + buildFragment attributeIntro expectedAttribute invalid (fun _ -> DeclarationAttribute) attributeIntro // operation and function parsing /// Uses buildFragment to parse a Q# BodyDeclaration as QsFragment. -and private bodyDeclaration = +let private bodyDeclaration = let invalid = BodyDeclaration unknownGenerator - buildFragment bodyDeclHeader.parse functorGenDirective invalid BodyDeclaration eof + buildFragment bodyDeclHeader.parse functorGenDirective invalid (fun _ -> BodyDeclaration) eof /// Uses buildFragment to parse a Q# AdjointDeclaration as QsFragment. -and private adjointDeclaration = +let private adjointDeclaration = let invalid = AdjointDeclaration unknownGenerator - buildFragment adjDeclHeader.parse functorGenDirective invalid AdjointDeclaration eof + buildFragment adjDeclHeader.parse functorGenDirective invalid (fun _ -> AdjointDeclaration) eof /// Uses buildFragment to parse a Q# ControlledDeclaration as QsFragment. -and private controlledDeclaration = +let private controlledDeclaration = let invalid = ControlledDeclaration unknownGenerator - buildFragment ctrlDeclHeader.parse functorGenDirective invalid ControlledDeclaration eof + buildFragment ctrlDeclHeader.parse functorGenDirective invalid (fun _ -> ControlledDeclaration) eof /// Uses buildFragment to parse a Q# ControlledAdjointDeclaration as QsFragment. -and private controlledAdjointDeclaration = +let private controlledAdjointDeclaration = let invalid = ControlledAdjointDeclaration unknownGenerator - buildFragment (attempt ctrlAdjDeclHeader.parse) functorGenDirective invalid ControlledAdjointDeclaration eof + buildFragment (attempt ctrlAdjDeclHeader.parse) functorGenDirective invalid (fun _ -> ControlledAdjointDeclaration) eof /// Uses buildFragment to parse a Q# OperationDeclaration as QsFragment. -and private operationDeclaration = - let invalid = OperationDeclaration (invalidSymbol, CallableSignature.Invalid) - buildFragment opDeclHeader.parse signature invalid OperationDeclaration eof - +let private operationDeclaration = + let invalid = OperationDeclaration ({Access = DefaultAccess}, invalidSymbol, CallableSignature.Invalid) + buildFragment + (modifiers .>> opDeclHeader.parse |> attempt) + signature + invalid + (fun mods (symbol, signature) -> OperationDeclaration (mods, symbol, signature)) + eof + /// Uses buildFragment to parse a Q# FunctionDeclaration as QsFragment. -and private functionDeclaration = - let invalid = FunctionDeclaration (invalidSymbol, CallableSignature.Invalid) - buildFragment fctDeclHeader.parse signature invalid FunctionDeclaration eof +let private functionDeclaration = + let invalid = FunctionDeclaration ({Access = DefaultAccess}, invalidSymbol, CallableSignature.Invalid) + buildFragment + (modifiers .>> fctDeclHeader.parse |> attempt) + signature + invalid + (fun mods (symbol, signature) -> FunctionDeclaration (mods, symbol, signature)) + eof /// Uses buildFragment to parse a Q# TypeDefinition as QsFragment. -and private udtDeclaration = - let invalid = TypeDefinition (invalidSymbol, invalidArgTupleItem) +let private udtDeclaration = + let invalid = TypeDefinition ({Access = DefaultAccess}, invalidSymbol, invalidArgTupleItem) let udtTuple = // not unified with the argument tuple for callable declarations, since the error handling needs to be different let asAnonymousItem t = QsTupleItem ((MissingSymbol, Null) |> QsSymbol.New, t) let namedItem = @@ -297,37 +275,52 @@ and private udtDeclaration = let tupleItem = attempt namedItem <|> (typeParser typeTuple |>> asAnonymousItem) // namedItem needs to be first, and we can't be permissive for tuple types! buildTupleItem tupleItem (fst >> QsTuple) ErrorCode.InvalidUdtItemDeclaration ErrorCode.MissingUdtItemDeclaration invalidArgTupleItem eof let invalidNamedSingle = followedBy namedItem >>. optTupleBrackets namedItem |>> fst - invalidNamedSingle <|> udtTupleItem // require parenthesis for a single named item + invalidNamedSingle <|> udtTupleItem // require parenthesis for a single named item let declBody = expectedIdentifierDeclaration equal .>> equal .>>. udtTuple - buildFragment typeDeclHeader.parse declBody invalid TypeDefinition eof + buildFragment + (modifiers .>> typeDeclHeader.parse |> attempt) + declBody + invalid + (fun mods (symbol, underlyingType) -> TypeDefinition (mods, symbol, underlyingType)) + eof // statement parsing /// Uses buildFragment to parse a Q# return-statement as QsFragment. -and private returnStatement = +let private returnStatement = let invalid = ReturnStatement unknownExpr - buildFragment qsReturn.parse (expectedExpr eof) invalid ReturnStatement eof + buildFragment qsReturn.parse (expectedExpr eof) invalid (fun _ -> ReturnStatement) eof /// Uses buildFragment to parse a Q# fail-statement as QsFragment. -and private failStatement = +let private failStatement = let invalid = FailStatement unknownExpr - buildFragment qsFail.parse (expectedExpr eof) invalid FailStatement eof + buildFragment qsFail.parse (expectedExpr eof) invalid (fun _ -> FailStatement) eof /// Uses buildFragment to parse a Q# immutable binding (i.e. let-statement) as QsFragment. -and private letStatement = +let private letStatement = let invalid = ImmutableBinding (invalidSymbol, unknownExpr) - buildFragment qsImmutableBinding.parse (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) invalid ImmutableBinding eof + buildFragment + qsImmutableBinding.parse + (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) + invalid + (fun _ -> ImmutableBinding) + eof /// Uses buildFragment to parse a Q# mutable binding (i.e. mutable-statement) as QsFragment. -and private mutableStatement = +let private mutableStatement = let invalid = MutableBinding (invalidSymbol, unknownExpr) - buildFragment qsMutableBinding.parse (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) invalid MutableBinding eof + buildFragment + qsMutableBinding.parse + (expectedExpr eof |> symbolBinding equal ErrorCode.ExpectingAssignment) + invalid + (fun _ -> MutableBinding) + eof /// Uses buildFragment to parse a Q# value update (i.e. set-statement) as QsFragment. -and private setStatement = +let private setStatement = let applyAndReassignOp = let updateAndReassign id = let update = pstring qsCopyAndUpdateOp.cont |> term @@ -376,72 +369,115 @@ and private setStatement = let expectedEqual = expected equal ErrorCode.ExpectingAssignment ErrorCode.ExpectingAssignment "" (preturn ()) symbolTuple .>> expectedEqual .>>. expectedExpr eof let invalid = ValueUpdate (unknownExpr, unknownExpr) - buildFragment qsValueUpdate.parse (attempt applyAndReassign <|> symbolUpdate) invalid ValueUpdate eof + buildFragment qsValueUpdate.parse (attempt applyAndReassign <|> symbolUpdate) invalid (fun _ -> ValueUpdate) eof /// Uses buildFragment to parse a Q# if clause as QsFragment. -and private ifClause = +let private ifClause = let invalid = IfClause unknownExpr - buildFragment qsIf.parse (expectedCondition eof) invalid IfClause eof + buildFragment qsIf.parse (expectedCondition eof) invalid (fun _ -> IfClause) eof /// Uses buildFragment to parse a Q# elif clause as QsFragment. -and private elifClause = +let private elifClause = let invalid = ElifClause unknownExpr - buildFragment qsElif.parse (expectedCondition eof) invalid ElifClause eof + buildFragment qsElif.parse (expectedCondition eof) invalid (fun _ -> ElifClause) eof /// Uses buildFragment to parse a Q# else clause as QsFragment. -and private elseClause = - let valid = fun _ -> ElseClause - buildFragment qsElse.parse (preturn "") ElseClause valid eof +let private elseClause = + buildFragment qsElse.parse (preturn "") ElseClause (fun _ _ -> ElseClause) eof /// Uses buildFragment to parse a Q# for-statement intro (for-statement without the body) as QsFragment. -and private forHeader = +let private forHeader = let invalid = ForLoopIntro (invalidSymbol, unknownExpr) let loopVariableBinding = expectedExpr rTuple |> symbolBinding qsRangeIter.parse ErrorCode.ExpectingIteratorItemAssignment let forBody = optTupleBrackets loopVariableBinding |>> fst - buildFragment qsFor.parse forBody invalid ForLoopIntro eof + buildFragment qsFor.parse forBody invalid (fun _ -> ForLoopIntro) eof /// Uses buildFragment to parse a Q# while-statement intro (while-statement without the body) as QsFragment. -and private whileHeader = +let private whileHeader = let invalid = WhileLoopIntro unknownExpr let whileBody = optTupleBrackets (expectedExpr isTupleContinuation) |>> fst - buildFragment qsWhile.parse whileBody invalid WhileLoopIntro eof + buildFragment qsWhile.parse whileBody invalid (fun _ -> WhileLoopIntro) eof /// Uses buildFragment to parse a Q# repeat intro as QsFragment. -and private repeatHeader = - let valid = fun _ -> RepeatIntro - buildFragment qsRepeat.parse (preturn "") RepeatIntro valid eof +let private repeatHeader = + buildFragment qsRepeat.parse (preturn "") RepeatIntro (fun _ _ -> RepeatIntro) eof /// Uses buildFragment to parse a Q# until success clause as QsFragment. -and private untilSuccess = +let private untilSuccess = let invalid = UntilSuccess (unknownExpr, false) let optionalFixup = qsRUSfixup.parse >>% true <|> preturn false - buildFragment qsUntil.parse (expectedCondition qsRUSfixup.parse .>>. optionalFixup) invalid UntilSuccess eof + buildFragment qsUntil.parse (expectedCondition qsRUSfixup.parse .>>. optionalFixup) invalid (fun _ -> UntilSuccess) eof /// Uses buildFragment to parse a Q# within-block intro as QsFragment. -and private withinHeader = - let valid = fun _ -> WithinBlockIntro - buildFragment qsWithin.parse (preturn "") WithinBlockIntro valid eof +let private withinHeader = + buildFragment qsWithin.parse (preturn "") WithinBlockIntro (fun _ _ -> WithinBlockIntro) eof /// Uses buildFragment to parse a Q# apply block intro as QsFragment. -and private applyHeader = - let valid = fun _ -> ApplyBlockIntro - buildFragment qsApply.parse (preturn "") ApplyBlockIntro valid eof +let private applyHeader = + buildFragment qsApply.parse (preturn "") ApplyBlockIntro (fun _ _ -> ApplyBlockIntro) eof /// Uses buildFragment to parse a Q# using block intro as QsFragment. -and private usingHeader = +let private usingHeader = let invalid = UsingBlockIntro (invalidSymbol, invalidInitializer) - buildFragment qsUsing.parse allocationScope invalid UsingBlockIntro eof + buildFragment qsUsing.parse allocationScope invalid (fun _ -> UsingBlockIntro) eof /// Uses buildFragment to parse a Q# borrowing block intro as QsFragment. -and private borrowingHeader = +let private borrowingHeader = let invalid = BorrowingBlockIntro (invalidSymbol, invalidInitializer) - buildFragment qsBorrowing.parse allocationScope invalid BorrowingBlockIntro eof + buildFragment qsBorrowing.parse allocationScope invalid (fun _ -> BorrowingBlockIntro) eof + +/// Always builds an invalid fragment after parsing the given fragment header. +let private buildInvalidFragment header = + buildFragment header (fail "invalid syntax") InvalidFragment (fun _ _ -> InvalidFragment) attributeIntro + +/// Fragment header keywords and their corresponding fragment parsers. +let private fragments = + [ + (qsImmutableBinding, letStatement) + (qsMutableBinding, mutableStatement) + (qsValueUpdate, setStatement) + (qsReturn, returnStatement) + (qsFail, failStatement) + (qsIf, ifClause) + (qsElif, elifClause) + (qsElse, elseClause) + (qsFor, forHeader) + (qsWhile, whileHeader) + (qsRepeat, repeatHeader) + (qsUntil, untilSuccess) + (qsWithin, withinHeader) + (qsApply, applyHeader) + (qsUsing, usingHeader) + (qsBorrowing, borrowingHeader) + (namespaceDeclHeader, namespaceDeclaration) + (typeDeclHeader, udtDeclaration) + (opDeclHeader, operationDeclaration) + (fctDeclHeader, functionDeclaration) + (ctrlAdjDeclHeader, controlledAdjointDeclaration) // needs to be before adjointDeclaration and controlledDeclaration! + (adjDeclHeader, adjointDeclaration) + (ctrlDeclHeader, controlledDeclaration) + (bodyDeclHeader, bodyDeclaration) + (importDirectiveHeader, openDirective) + + // This fragment header does not have its own fragment kind. Instead, it is only parsed as part of another + // fragment kind. If this header occurs by itself, without the other header it's a part of, an invalid fragment + // should be created. Since it's at the end of the list, we know that all of the other fragment kinds have been + // tried first. + (qsInternal, buildInvalidFragment qsInternal.parse) + ] + +do + // Make sure all of the fragment header keywords are listed above. + let implementedHeaders = (List.map (fun (keyword, _) -> keyword.id) fragments).ToImmutableHashSet() + let existingHeaders = Keywords.FragmentHeaders.ToImmutableHashSet() + if (implementedHeaders.SymmetricExcept existingHeaders).Count <> 0 then + System.NotImplementedException "mismatch between existing Q# fragments and implemented Q# fragments" |> raise /// Uses buildFragment to parse a Q# expression statement as QsFragment. @@ -455,8 +491,7 @@ let private expressionStatement = errRange |> QsCompilerDiagnostic.Error (ErrorCode.NonCallExprAsStatement, []) |> preturn >>= pushDiagnostic let anyExpr = getPosition .>>. expr >>= errOnNonCallLike >>% InvalidFragment // keeping this as unknown fragment so no further type checking is done attempt callLikeExpr |>> ExpressionStatement <|> anyExpr - buildFragment (lookAhead valid) valid invalid id eof // let's limit this to call like expressions - + buildFragment (lookAhead valid) valid invalid (fun _ -> id) eof // let's limit this to call like expressions // externally called routines @@ -464,12 +499,7 @@ let private expressionStatement = /// Raises a suitable error and returns UnknownStatement as QsFragment if the parsing fails. let internal codeFragment = let validFragment = - choice (getFragments() |> List.map snd) + choice (fragments |> List.map snd) <|> attributeDeclaration - <|> expressionStatement// the expressionStatement needs to be last - let invalidFragment = - let valid = fun _ -> InvalidFragment - buildFragment (preturn ()) (fail "invalid syntax") InvalidFragment valid attributeIntro - attempt validFragment <|> invalidFragment - - + <|> expressionStatement // the expressionStatement needs to be last + attempt validFragment <|> buildInvalidFragment (preturn ()) diff --git a/src/QsCompiler/TextProcessor/QsKeywords.fs b/src/QsCompiler/TextProcessor/QsKeywords.fs index ed0dc00bde..b873770526 100644 --- a/src/QsCompiler/TextProcessor/QsKeywords.fs +++ b/src/QsCompiler/TextProcessor/QsKeywords.fs @@ -200,6 +200,9 @@ let typeDeclHeader = addFragmentHeader Declarations.Type /// keyword for a Q# declaration (QsFragmentHeader) let namespaceDeclHeader = addFragmentHeader Declarations.Namespace +/// keyword for a Q# declaration modifier (QsFragmentHeader) +let qsInternal = addFragmentHeader Declarations.Internal + // directives /// keyword for a Q# directive (QsFragmentHeader) diff --git a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs index 04bf21a1d4..67fcb67e11 100644 --- a/src/QsCompiler/TextProcessor/SyntaxBuilder.fs +++ b/src/QsCompiler/TextProcessor/SyntaxBuilder.fs @@ -424,14 +424,17 @@ let private filterAndAdapt (diagnostics : QsCompilerDiagnostic list) endPos = |> List.map (fun diagnostic -> diagnostic.WithRange(rangeWithinFragment diagnostic.Range)) /// Constructs a QsCodeFragment. -/// If the given header succeeds, attempts the given body parser to obtain the argument to construct the QsFragment using the given -/// fragmentKind constructor, or defaults to the given invalid fragment. -/// Generates a suitable error if the body parser fails. -/// If the body parser succeeds, advances until the next fragment header or until the given continuation succeeds, -/// generating an ExcessContinuation error for any non-whitespace code. -/// Determines the Range and Text for the fragment and attaches all current diagnostics saved in the user state to the QsFragment. -/// Upon fragment construction, clears all diagnostics currently stored in the UserState. -let internal buildFragment header body (invalid : QsFragmentKind) (fragmentKind : 'a -> QsFragmentKind) continuation = +/// +/// If the given header parser succeeds, attempts the given body parser. +/// +/// If the body parser succeeds, gives the results from both the header and body to the given fragmentKind constructor, +/// and advances until the next fragment header or until the given continuation succeeds, generating an +/// ExcessContinuation error for any non-whitespace code. Otherwise, if the body parser fails, defaults to the given +/// invalid fragment and generates a diagnostic. +/// +/// Determines the Range and Text for the fragment and attaches all current diagnostics saved in the user state to the +/// QsFragment. Upon fragment construction, clears all diagnostics currently stored in the UserState. +let internal buildFragment header body (invalid : QsFragmentKind) fragmentKind continuation = let build (kind, (startPos, (text, endPos))) = getUserState .>> clearDiagnostics |>> fun diagnostics -> @@ -449,18 +452,13 @@ let internal buildFragment header body (invalid : QsFragmentKind) (fragmentKind (getPosition .>>. fragmentEnd) |> runOnSubstream state let continuation = (continuation >>% ()) <|> (qsFragmentHeader >>% ()) - let validBody state = + let validBody state headerResult = let processExcessCode = buildError (skipInvalidUntil continuation) ErrorCode.ExcessContinuation >>% () (body .>>? followedBy (continuation <|> eof)) <|> (body .>> processExcessCode) - |>> fragmentKind .>>. delimiters state + |>> fragmentKind headerResult .>>. delimiters state let invalidBody state = getPosition .>> (advanceTo continuation) .>>. delimiters state >>= buildDiagnostic getCharStreamState >>= fun state -> - header >>. (attempt (validBody state) <|> invalidBody state) >>= build - - - - - - + header >>= fun headerResult -> + (attempt (validBody state headerResult) <|> invalidBody state) >>= build diff --git a/src/QsCompiler/Transformations/ContentLifting.cs b/src/QsCompiler/Transformations/ContentLifting.cs index 3368999cd6..c44f3cde4a 100644 --- a/src/QsCompiler/Transformations/ContentLifting.cs +++ b/src/QsCompiler/Transformations/ContentLifting.cs @@ -201,6 +201,7 @@ QsSpecialization MakeSpec(QsSpecializationKind kind, ResolvedSignature signature QsCallableKind.Operation, newName, ImmutableArray.Empty, + new Modifiers(AccessModifier.Internal), CurrentCallable.Callable.SourceFile, QsNullable.Null, signature, diff --git a/src/QsCompiler/Transformations/SearchAndReplace.cs b/src/QsCompiler/Transformations/SearchAndReplace.cs index e12ed5dd81..d66803179a 100644 --- a/src/QsCompiler/Transformations/SearchAndReplace.cs +++ b/src/QsCompiler/Transformations/SearchAndReplace.cs @@ -55,8 +55,9 @@ public Location(NonNullable source, Tuple declOffset, QsLocati public bool Equals(Location other) => this.SourceFile.Value == other?.SourceFile.Value - && this.DeclarationOffset == other?.DeclarationOffset - && this.RelativeStatementLocation == other?.RelativeStatementLocation + && this.DeclarationOffset.Equals(other?.DeclarationOffset) + && this.RelativeStatementLocation.Offset.Equals(other?.RelativeStatementLocation.Offset) + && this.RelativeStatementLocation.Range.Equals(other?.RelativeStatementLocation.Range) && this.SymbolRange.Item1.Equals(other?.SymbolRange?.Item1) && this.SymbolRange.Item2.Equals(other?.SymbolRange?.Item2); @@ -84,7 +85,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 +102,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 +169,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 +179,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); @@ -363,6 +364,59 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) // routines for replacing symbols/identifiers + /// + /// Provides simple name decoration (or name mangling) by prefixing names with a label and number. + /// + public class NameDecorator + { + private const string original = "original"; + + private readonly string label; + + private readonly Regex pattern; + + /// + /// Creates a new name decorator using the label. + /// + /// The label to use as the prefix for decorated names. + public NameDecorator(string label) + { + this.label = label; + pattern = new Regex($"^__{Regex.Escape(label)}[0-9]*__(?<{original}>.*)__$"); + } + + /// + /// Decorates the name with the label of this name decorator and the given number. + /// + /// The name to decorate. + /// The number to use along with the label to decorate the name. + /// The decorated name. + public string Decorate(string name, int number) => $"__{label}{number}__{name}__"; + + /// + /// Decorates the name of the qualified name with the label of this name decorator and the given number. + /// + /// The qualified name to decorate. + /// The number to use along with the label to decorate the qualified name. + /// The decorated qualified name. + public QsQualifiedName Decorate(QsQualifiedName name, int number) => + new QsQualifiedName(name.Namespace, NonNullable.New(Decorate(name.Name.Value, number))); + + /// + /// Reverses decoration previously done to the name using the same label as this name decorator. + /// + /// The decorated name to undecorate. + /// + /// The original name before decoration, if the decorated name uses the same label as this name decorator; + /// otherwise, null. + /// + public string Undecorate(string name) + { + var match = pattern.Match(name).Groups[original]; + return match.Success ? match.Value : null; + } + } + /// /// Upon transformation, assigns each defined variable a unique name, independent on the scope, and replaces all references to it accordingly. /// The original variable name can be recovered by using the static method StripUniqueName. @@ -371,6 +425,8 @@ public override QsStatementKind OnValueUpdate(QsValueUpdate stm) public class UniqueVariableNames : SyntaxTreeTransformation { + private static readonly NameDecorator decorator = new NameDecorator("qsVar"); + public class TransformationState { private int VariableNr = 0; @@ -387,18 +443,14 @@ internal QsExpressionKind AdaptIdentifier(Identifier sym, QsNullable internal NonNullable GenerateUniqueName(NonNullable varName) { - var unique = NonNullable.New($"__{Prefix}{this.VariableNr++}__{varName.Value}__"); + var unique = NonNullable.New(decorator.Decorate(varName.Value, VariableNr++)); this.UniqueNames[varName] = unique; return unique; } } - private const string Prefix = "qsVar"; - private const string OrigVarName = "origVarName"; - private static readonly Regex WrappedVarName = new Regex($"^__{Prefix}[0-9]*__(?<{OrigVarName}>.*)__$"); - - public UniqueVariableNames() + public UniqueVariableNames() : base(new TransformationState()) { this.StatementKinds = new StatementKindTransformation(this); @@ -410,13 +462,12 @@ public UniqueVariableNames() // static methods for convenience internal static QsQualifiedName PrependGuid(QsQualifiedName original) => - new QsQualifiedName(original.Namespace, NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + original.Name.Value)); + new QsQualifiedName( + original.Namespace, + NonNullable.New("_" + Guid.NewGuid().ToString("N") + "_" + original.Name.Value)); - public static NonNullable StripUniqueName(NonNullable uniqueName) - { - var matched = WrappedVarName.Match(uniqueName.Value).Groups[OrigVarName]; - return matched.Success ? NonNullable.New(matched.Value) : uniqueName; - } + public static NonNullable StripUniqueName(NonNullable uniqueName) => + NonNullable.New(decorator.Undecorate(uniqueName.Value) ?? uniqueName.Value); // helper classes @@ -446,6 +497,116 @@ public override QsExpressionKind OnIdentifier(Identifier sym, QsNullable + /// A transformation that renames all references to each given qualified name. + /// + public class RenameReferences : SyntaxTreeTransformation + { + 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 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); + } + + + // private helper classes + + private class TypeTransformation : Core.TypeTransformation + { + private readonly TransformationState State; + + public TypeTransformation(RenameReferences parent) : base(parent) => + this.State = parent.State; + + public override QsTypeKind OnUserDefinedType(UserDefinedType udt) => + QsTypeKind.NewUserDefinedType(State.RenameUdt(udt)); + + public override QsTypeKind OnTypeParameter(QsTypeParameter tp) => + QsTypeKind.NewTypeParameter(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) => + this.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) => + this.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 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 diff --git a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template index 10a844f2a7..4dbc1671fd 100644 --- a/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template +++ b/src/VSCodeExtension/syntaxes/qsharp.tmLanguage.json.v.template @@ -60,7 +60,7 @@ { "comment": "C# reserved words which can't be used in Q#, E-L", "name": "invalid.illegal.el.qsharp", - "match": "\\b(enum|event|explicit|extern|finally|fixed|float|foreach|goto|implicit|int|interface|internal|lock|long)\\b" + "match": "\\b(enum|event|explicit|extern|finally|fixed|float|foreach|goto|implicit|int|interface|lock|long)\\b" }, { "comment": "C# reserved words which can't be used in Q#, N-S", @@ -78,7 +78,7 @@ "patterns": [ { "name": "keyword.other.qsharp", - "match": "\\b(namespace|open|as|newtype|operation|function|body|(a|A)djoint|(c|C)ontrolled|self|auto|distribute|invert|intrinsic)\\b" + "match": "\\b(namespace|open|as|internal|newtype|operation|function|body|(a|A)djoint|(c|C)ontrolled|self|auto|distribute|invert|intrinsic)\\b" } ] },