Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit 40494dd

Browse files
authored
Refactor language server code for extracting symbols from Q# fragments (#1220)
* Start adding SymbolOccurrence module * Replace SymbolInformation implementation * Add C# interop for SymbolOccurrence * Rename SymbolInformation.cs to match class name * Mark SymbolInformation as obsolete * Add SymbolOccurrence.fsi * Add doc comments * Fix nullable errors with .NET Core 3.1 * Use as instead of is * Take in fragment instead of file in SymbolOccurrence * Update todo comments * Return NS in open directive as UsedVariable * Use TryGet for SymbolOccurrence C# API
1 parent 5130928 commit 40494dd

File tree

8 files changed

+401
-310
lines changed

8 files changed

+401
-310
lines changed

src/QsCompiler/CompilationManager/EditorSupport/CodeActions.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,16 @@ internal static WorkspaceEdit GetWorkspaceEdit(this FileContentManager file, par
5050
private static IEnumerable<string> IdNamespaceSuggestions(
5151
this FileContentManager file, Position pos, CompilationUnit compilation, out string? idName)
5252
{
53-
var variables = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedVariables;
54-
idName = variables != null && variables.Any() ? variables.Single().Symbol.AsDeclarationName(null) : null;
55-
return idName != null && compilation != null
56-
? compilation.GlobalSymbols.NamespacesContainingCallable(idName)
57-
: ImmutableArray<string>.Empty;
53+
var fragment = file.TryGetFragmentAt(pos, out _, true);
54+
var occurrence = fragment is null ? null : SymbolInfo.SymbolOccurrence(fragment, pos, true);
55+
56+
idName = !(occurrence is null) && occurrence.TryGetUsedVariable(out var variable)
57+
? variable.Symbol.AsDeclarationName(null)
58+
: null;
59+
60+
return idName is null
61+
? ImmutableArray<string>.Empty
62+
: compilation.GlobalSymbols.NamespacesContainingCallable(idName);
5863
}
5964

6065
/// <summary>
@@ -69,13 +74,16 @@ private static IEnumerable<string> IdNamespaceSuggestions(
6974
private static IEnumerable<string> TypeNamespaceSuggestions(
7075
this FileContentManager file, Position pos, CompilationUnit compilation, out string? typeName)
7176
{
72-
var types = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedTypes;
73-
typeName = types != null && types.Any() &&
74-
types.Single().Type is QsTypeKind<QsType, QsSymbol, QsSymbol, Characteristics>.UserDefinedType udt
75-
? udt.Item.Symbol.AsDeclarationName(null) : null;
76-
return typeName != null && compilation != null
77-
? compilation.GlobalSymbols.NamespacesContainingType(typeName)
78-
: ImmutableArray<string>.Empty;
77+
var fragment = file.TryGetFragmentAt(pos, out _, true);
78+
var occurrence = fragment is null ? null : SymbolInfo.SymbolOccurrence(fragment, pos, true);
79+
80+
typeName = !(occurrence is null) && occurrence.TryGetUsedType(out var type) && type.Type is QsTypeKind.UserDefinedType udt
81+
? udt.Item.Symbol.AsDeclarationName(null)
82+
: null;
83+
84+
return typeName is null
85+
? ImmutableArray<string>.Empty
86+
: compilation.GlobalSymbols.NamespacesContainingType(typeName);
7987
}
8088

8189
/// <summary>
@@ -93,8 +101,13 @@ from callable in compilation.GlobalSymbols.AccessibleCallables()
93101
where otherName != name && otherName.Equals(name, StringComparison.OrdinalIgnoreCase)
94102
select otherName;
95103

96-
var symbolKind = file.TryGetQsSymbolInfo(pos, true, out _)?.UsedVariables?.SingleOrDefault()?.Symbol;
97-
return symbolKind.AsDeclarationName(null)?.Apply(AlternateNames) ?? Enumerable.Empty<string>();
104+
var fragment = file.TryGetFragmentAt(pos, out _, true);
105+
var occurrence = fragment is null ? null : SymbolInfo.SymbolOccurrence(fragment, pos, true);
106+
var symbolKind = !(occurrence is null) && occurrence.TryGetUsedVariable(out var variable)
107+
? variable.Symbol
108+
: null;
109+
110+
return symbolKind?.AsDeclarationName(null)?.Apply(AlternateNames) ?? Enumerable.Empty<string>();
98111
}
99112

100113
/// <summary>
@@ -112,8 +125,12 @@ from type in compilation.GlobalSymbols.AccessibleTypes()
112125
where otherName != name && otherName.Equals(name, StringComparison.OrdinalIgnoreCase)
113126
select otherName;
114127

115-
var typeKind = file.TryGetQsSymbolInfo(pos, true, out _)?.UsedTypes?.SingleOrDefault()?.Type;
116-
var udt = typeKind as QsTypeKind.UserDefinedType;
128+
var fragment = file.TryGetFragmentAt(pos, out _, true);
129+
var occurrence = fragment is null ? null : SymbolInfo.SymbolOccurrence(fragment, pos, true);
130+
var udt = !(occurrence is null) && occurrence.TryGetUsedType(out var type)
131+
? type.Type as QsTypeKind.UserDefinedType
132+
: null;
133+
117134
return udt?.Item.Symbol.AsDeclarationName(null)?.Apply(AlternateNames) ?? Enumerable.Empty<string>();
118135
}
119136

src/QsCompiler/CompilationManager/EditorSupport/EditorCommands.cs

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
77
using System.Linq;
8-
using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures;
98
using Microsoft.Quantum.QsCompiler.DataTypes;
109
using Microsoft.Quantum.QsCompiler.SyntaxProcessing;
1110
using Microsoft.Quantum.QsCompiler.SyntaxTokens;
@@ -48,26 +47,29 @@ internal static class EditorCommands
4847
/// </summary>
4948
public static Location? DefinitionLocation(this FileContentManager file, CompilationUnit compilation, Position? position)
5049
{
51-
var symbolInfo = file?.TryGetQsSymbolInfo(position, true, out CodeFragment _); // includes the end position
52-
if (file is null || symbolInfo is null || compilation is null || position is null)
50+
var fragment = file.TryGetFragmentAt(position, out _, true);
51+
if (position is null || fragment is null)
52+
{
53+
return null;
54+
}
55+
56+
var occurrence = SymbolInfo.SymbolOccurrence(fragment, position, true);
57+
if (occurrence is null)
5358
{
5459
return null;
5560
}
5661

5762
var locals = compilation.TryGetLocalDeclarations(file, position, out var cName, includeDeclaredAtPosition: true);
58-
if (cName == null)
63+
if (cName is null)
5964
{
6065
return null;
6166
}
6267

63-
var found =
64-
symbolInfo.UsedVariables.Any()
65-
? compilation.GlobalSymbols.VariableDeclaration(locals, cName.Namespace, file.FileName, symbolInfo.UsedVariables.Single())
66-
: symbolInfo.UsedTypes.Any()
67-
? compilation.GlobalSymbols.TypeDeclaration(cName.Namespace, file.FileName, symbolInfo.UsedTypes.Single())
68-
: symbolInfo.DeclaredSymbols.Any()
69-
? compilation.GlobalSymbols.SymbolDeclaration(locals, cName.Namespace, file.FileName, symbolInfo.DeclaredSymbols.Single())
70-
: QsNullable<Tuple<string, Position, Range>>.Null;
68+
var found = occurrence.Match(
69+
declaration: s => compilation.GlobalSymbols.SymbolDeclaration(locals, cName.Namespace, file.FileName, s),
70+
usedType: t => compilation.GlobalSymbols.TypeDeclaration(cName.Namespace, file.FileName, t),
71+
usedVariable: s => compilation.GlobalSymbols.VariableDeclaration(locals, cName.Namespace, file.FileName, s),
72+
usedLiteral: e => QsNullable<Tuple<string, Position, Range>>.Null);
7173

7274
return found.IsValue
7375
? SymbolInfo.AsLocation(found.Item.Item1, found.Item.Item2, found.Item.Item3)
@@ -221,44 +223,38 @@ DocumentHighlight AsHighlight(Lsp.Range range) =>
221223
public static Hover? HoverInformation(
222224
this FileContentManager file,
223225
CompilationUnit compilation,
224-
Position? position,
226+
Position position,
225227
MarkupKind format = MarkupKind.PlainText)
226228
{
227-
Hover? GetHover(string? info) => info == null ? null : new Hover
228-
{
229-
Contents = new MarkupContent { Kind = format, Value = info },
230-
Range = new Lsp.Range { Start = position.ToLsp(), End = position.ToLsp() },
231-
};
232-
229+
// TODO: Add hover for functor generators and functor applications.
230+
// TODO: Add hover for new array expressions.
231+
// TODO: Add nested types (requires SymbolInfo.SymbolOccurrence to return the closest match).
233232
var markdown = format == MarkupKind.Markdown;
234-
var symbolInfo = file?.TryGetQsSymbolInfo(position, false, out var _);
235-
if (file is null || symbolInfo == null || compilation == null || position is null)
236-
{
237-
return null;
238-
}
239-
240-
if (symbolInfo.UsedLiterals.Any())
241-
{
242-
return GetHover(symbolInfo.UsedLiterals.Single().LiteralInfo(markdown));
243-
}
233+
var fragment = file.TryGetFragmentAt(position, out _);
234+
var occurrence = fragment is null ? null : SymbolInfo.SymbolOccurrence(fragment, position, false);
235+
var info = occurrence?.Match(
236+
declaration: s => WithContext((locals, nsName) =>
237+
compilation.GlobalSymbols.DeclarationInfo(locals, nsName, file.FileName, s, markdown)),
238+
usedType: t => WithContext((locals, nsName) =>
239+
compilation.GlobalSymbols.TypeInfo(nsName, file.FileName, t, markdown)),
240+
usedVariable: s => WithContext((locals, nsName) =>
241+
compilation.GlobalSymbols.VariableInfo(locals, nsName, file.FileName, s, markdown)),
242+
usedLiteral: e => e.LiteralInfo(markdown));
243+
244+
return info is null
245+
? null
246+
: new Hover
247+
{
248+
Contents = new MarkupContent { Kind = format, Value = info },
249+
Range = new Lsp.Range { Start = position.ToLsp(), End = position.ToLsp() },
250+
};
244251

245-
var locals = compilation.TryGetLocalDeclarations(file, position, out var cName, includeDeclaredAtPosition: true);
246-
var nsName = cName?.Namespace ?? file.TryGetNamespaceAt(position);
247-
if (nsName == null)
252+
string? WithContext(Func<LocalDeclarations, string, string?> f)
248253
{
249-
return null;
254+
var locals = compilation.TryGetLocalDeclarations(file, position, out var cName, includeDeclaredAtPosition: true);
255+
var nsName = cName?.Namespace ?? file.TryGetNamespaceAt(position);
256+
return nsName is null ? null : f(locals, nsName);
250257
}
251-
252-
// TODO: add hover for functor generators and functor applications
253-
// TODO: add hover for new array expr ?
254-
// TODO: add nested types - requires dropping the .Single and actually resolving to the closest match!
255-
return GetHover(symbolInfo.UsedVariables.Any()
256-
? compilation.GlobalSymbols.VariableInfo(locals, nsName, file.FileName, symbolInfo.UsedVariables.Single(), markdown)
257-
: symbolInfo.UsedTypes.Any()
258-
? compilation.GlobalSymbols.TypeInfo(nsName, file.FileName, symbolInfo.UsedTypes.Single(), markdown)
259-
: symbolInfo.DeclaredSymbols.Any()
260-
? compilation.GlobalSymbols.DeclarationInfo(locals, nsName, file.FileName, symbolInfo.DeclaredSymbols.Single(), markdown)
261-
: null);
262258
}
263259

264260
/// <summary>

src/QsCompiler/CompilationManager/EditorSupport/SymbolInformation.cs renamed to src/QsCompiler/CompilationManager/EditorSupport/SymbolInfo.cs

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@
77
using System.Diagnostics.CodeAnalysis;
88
using System.Linq;
99
using Microsoft.Quantum.QsCompiler.CompilationBuilder.DataStructures;
10+
using Microsoft.Quantum.QsCompiler.DataTypes;
1011
using Microsoft.Quantum.QsCompiler.SyntaxProcessing;
1112
using Microsoft.Quantum.QsCompiler.SyntaxTokens;
1213
using Microsoft.Quantum.QsCompiler.SyntaxTree;
1314
using Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace;
1415
using Microsoft.VisualStudio.LanguageServer.Protocol;
1516
using Position = Microsoft.Quantum.QsCompiler.DataTypes.Position;
16-
using QsSymbolInfo = Microsoft.Quantum.QsCompiler.SyntaxProcessing.SyntaxExtensions.SymbolInformation;
1717
using Range = Microsoft.Quantum.QsCompiler.DataTypes.Range;
1818

1919
namespace Microsoft.Quantum.QsCompiler.CompilationBuilder
2020
{
21+
using QsTypeKind = QsTypeKind<QsType, QsSymbol, QsSymbol, Characteristics>;
22+
2123
/// <summary>
2224
/// This static class contains utils for getting the necessary information for editor commands.
2325
/// </summary>
@@ -72,54 +74,36 @@ public static IEnumerable<SymbolInformation> CallableDeclarationsSymbolInfo(this
7274
});
7375

7476
/// <summary>
75-
/// If an overlapping code fragment exists, returns all symbol declarations, variable, Q# types, and Q# literals
76-
/// that *overlap* with <paramref name="position"/> as a <see cref="QsSymbolInfo"/>.
77+
/// Returns the symbol occurrence that overlaps with <paramref name="position"/> in <paramref name="fragment"/>.
7778
/// </summary>
78-
/// <param name="fragment">
79-
/// The code fragment that overlaps with <paramref name="position"/> in <paramref name="file"/>,
80-
/// or null if no such fragment exists.
79+
/// <param name="fragment">The fragment to look in.</param>
80+
/// <param name="position">The position to look at.</param>
81+
/// <param name="includeEnd">
82+
/// True if an overlapping symbol's end position can be equal to <paramref name="position"/>.
8183
/// </param>
82-
/// <remarks>
83-
/// Returns null if no such fragment exists, or <paramref name="file"/> and/or <paramref name="position"/>
84-
/// is null, or <paramref name="position"/> is invalid.
85-
/// </remarks>
86-
internal static QsSymbolInfo? TryGetQsSymbolInfo(
87-
this FileContentManager file,
88-
Position? position,
89-
bool includeEnd,
90-
out CodeFragment? fragment)
84+
/// <returns>The overlapping occurrence.</returns>
85+
internal static SymbolOccurrence? SymbolOccurrence(CodeFragment fragment, Position position, bool includeEnd)
9186
{
92-
// getting the relevant token (if any)
93-
fragment = file?.TryGetFragmentAt(position, out _, includeEnd);
94-
if (fragment?.Kind == null)
95-
{
96-
return null;
97-
}
87+
return fragment.Kind is null
88+
? null
89+
: SymbolOccurrenceModule.InFragment(fragment.Kind).SingleOrDefault(OccurrenceOverlaps);
9890

99-
var fragmentStart = fragment.Range.Start;
91+
bool OccurrenceOverlaps(SymbolOccurrence occurrence) => occurrence.Match(
92+
declaration: s => RangeOverlaps(s.Range),
93+
usedType: t => RangeOverlaps(t.Range),
94+
usedVariable: s => RangeOverlaps(s.Range),
95+
usedLiteral: e => RangeOverlaps(e.Range));
10096

101-
// getting the symbol information (if any), and return the overlapping items only
102-
bool OverlapsWithPosition(Range symRange)
97+
bool RangeOverlaps(QsNullable<Range> range)
10398
{
104-
var absolute = fragmentStart + symRange;
99+
if (range.IsNull)
100+
{
101+
return false;
102+
}
103+
104+
var absolute = fragment.Range.Start + range.Item;
105105
return includeEnd ? absolute.ContainsEnd(position) : absolute.Contains(position);
106106
}
107-
108-
var symbolInfo = fragment.Kind.SymbolInformation();
109-
var overlappingDecl = symbolInfo.DeclaredSymbols.Where(sym => sym.Range.IsValue && OverlapsWithPosition(sym.Range.Item));
110-
QsCompilerError.Verify(overlappingDecl.Count() <= 1, "more than one declaration overlaps with the same position");
111-
var overlappingVariables = symbolInfo.UsedVariables.Where(sym => sym.Range.IsValue && OverlapsWithPosition(sym.Range.Item));
112-
QsCompilerError.Verify(overlappingVariables.Count() <= 1, "more than one variable overlaps with the same position");
113-
var overlappingTypes = symbolInfo.UsedTypes.Where(sym => sym.Range.IsValue && OverlapsWithPosition(sym.Range.Item));
114-
QsCompilerError.Verify(overlappingTypes.Count() <= 1, "more than one type overlaps with the same position");
115-
var overlappingLiterals = symbolInfo.UsedLiterals.Where(sym => sym.Range.IsValue && OverlapsWithPosition(sym.Range.Item));
116-
QsCompilerError.Verify(overlappingTypes.Count() <= 1, "more than one literal overlaps with the same position");
117-
118-
return new QsSymbolInfo(
119-
declaredSymbols: overlappingDecl.ToImmutableHashSet(),
120-
usedVariables: overlappingVariables.ToImmutableHashSet(),
121-
usedTypes: overlappingTypes.ToImmutableHashSet(),
122-
usedLiterals: overlappingLiterals.ToImmutableHashSet());
123107
}
124108

125109
/// <summary>
@@ -191,21 +175,20 @@ internal static bool TryGetReferences(
191175
IImmutableSet<string>? limitToSourceFiles = null)
192176
{
193177
(referenceLocations, declarationLocation) = (null, null);
194-
if (file == null || compilation == null)
195-
{
196-
return false;
197-
}
198178

199-
var symbolInfo = file.TryGetQsSymbolInfo(position, true, out var fragment); // includes the end position
200-
if (symbolInfo == null || fragment?.Kind is QsFragmentKind.NamespaceDeclaration)
179+
var fragment = file.TryGetFragmentAt(position, out _, true);
180+
if (fragment is null || fragment.Kind is QsFragmentKind.NamespaceDeclaration)
201181
{
202182
return false;
203183
}
204184

205-
var sym = symbolInfo.UsedTypes.Any()
206-
&& symbolInfo.UsedTypes.Single().Type is QsTypeKind<QsType, QsSymbol, QsSymbol, Characteristics>.UserDefinedType udt ? udt.Item
207-
: symbolInfo.UsedVariables.Any() ? symbolInfo.UsedVariables.Single()
208-
: symbolInfo.DeclaredSymbols.Any() ? symbolInfo.DeclaredSymbols.Single() : null;
185+
var occurrence = SymbolOccurrence(fragment, position, true);
186+
var sym = occurrence?.Match(
187+
declaration: s => s,
188+
usedType: t => (t.Type as QsTypeKind.UserDefinedType)?.Item,
189+
usedVariable: s => s,
190+
usedLiteral: e => null);
191+
209192
if (sym == null)
210193
{
211194
return false;

src/QsCompiler/CompilationManager/ProjectManager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,9 @@ static IEnumerable<SumType<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>
12081208
/// </remarks>
12091209
public Hover? HoverInformation(TextDocumentPositionParams? param, MarkupKind format = MarkupKind.PlainText) =>
12101210
this.Manager(param?.TextDocument?.Uri)?.FileQuery(
1211-
param?.TextDocument, (file, c) => file.HoverInformation(c, param?.Position?.ToQSharp(), format), suppressExceptionLogging: true);
1211+
param?.TextDocument,
1212+
(f, c) => param?.Position?.ToQSharp() is { } p ? f.HoverInformation(c, p, format) : null,
1213+
suppressExceptionLogging: true);
12121214

12131215
/// <summary>
12141216
/// Returns an array with all usages of the identifier at the given position (if any) as an array of <see cref="DocumentHighlight"/>.

0 commit comments

Comments
 (0)