Skip to content

Commit

Permalink
Range-Span API cleanup (#435)
Browse files Browse the repository at this point in the history
* Propagation methods on tree

* Moved and changed TextEdit

* Fixed

* Update TextEdit.cs
  • Loading branch information
LPeter1997 authored Aug 21, 2024
1 parent 9e30dcb commit 2e43b3e
Show file tree
Hide file tree
Showing 21 changed files with 171 additions and 116 deletions.
3 changes: 2 additions & 1 deletion src/Draco.Compiler.Tests/Semantics/CodeCompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ private static ImmutableArray<CompletionItem> GetCompletions(SyntaxTree tree, Se
service.AddProvider(new KeywordCompletionProvider());
service.AddProvider(new ExpressionCompletionProvider());
service.AddProvider(new MemberCompletionProvider());
return service.GetCompletions(tree, model, cursor);
var cursorIndex = tree.SourceText.SyntaxPositionToIndex(cursor);
return service.GetCompletions(tree, model, cursorIndex);
}

[Fact]
Expand Down
12 changes: 6 additions & 6 deletions src/Draco.Compiler/Api/CodeCompletion/CompletionItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ namespace Draco.Compiler.Api.CodeCompletion;
/// <param name="Kind">The <see cref="CompletionKind"/> of this completion.</param>
public sealed record class CompletionItem(ImmutableArray<TextEdit> Edits, string DisplayText, ImmutableArray<ISymbol> Symbols, CompletionKind Kind)
{
public static CompletionItem Create(string text, SyntaxRange range, CompletionKind kind) =>
new([new TextEdit(range, text)], text, [], kind);
public static CompletionItem Create(SourceText source, string text, SourceSpan span, CompletionKind kind) =>
new([new TextEdit(source, span, text)], text, [], kind);

public static CompletionItem Create(string text, SyntaxRange range, ISymbol symbol, CompletionKind kind) =>
new([new TextEdit(range, text)], text, [symbol], kind);
public static CompletionItem Create(SourceText source, string text, SourceSpan span, ISymbol symbol, CompletionKind kind) =>
new([new TextEdit(source, span, text)], text, [symbol], kind);

public static CompletionItem Create(string text, SyntaxRange range, ImmutableArray<ISymbol> symbols, CompletionKind kind) =>
new([new TextEdit(range, text)], text, symbols, kind);
public static CompletionItem Create(SourceText source, string text, SourceSpan span, ImmutableArray<ISymbol> symbols, CompletionKind kind) =>
new([new TextEdit(source, span, text)], text, symbols, kind);
}
5 changes: 3 additions & 2 deletions src/Draco.Compiler/Api/CodeCompletion/CompletionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ public abstract class CompletionProvider
/// </summary>
/// <param name="tree">The <see cref="SyntaxTree"/> for which this service will create suggestions.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> for this <paramref name="tree"/>.</param>
/// <param name="cursor">Position of cursor in the <paramref name="tree"/>.</param>
/// <param name="cursorIndex">Position of cursor in the <paramref name="tree"/> as an index.</param>
/// <param name="contexts">Flag enum of current contexts.</param>
/// <returns>All the <see cref="CompletionItem"/>s this <see cref="CompletionProvider"/> created.</returns>
public abstract ImmutableArray<CompletionItem> GetCompletionItems(SyntaxTree tree, SemanticModel semanticModel, SyntaxPosition cursor, CompletionContext contexts);
public abstract ImmutableArray<CompletionItem> GetCompletionItems(
SyntaxTree tree, SemanticModel semanticModel, int cursorIndex, CompletionContext contexts);
}
15 changes: 8 additions & 7 deletions src/Draco.Compiler/Api/CodeCompletion/CompletionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,31 @@ public sealed class CompletionService
/// </summary>
/// <param name="tree">The <see cref="SyntaxTree"/> for which this service will create suggestions.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> for this <paramref name="tree"/>.</param>
/// <param name="cursor">Position of cursor in the <paramref name="tree"/>.</param>
/// <param name="cursorIndex">Index of the cursor in the <paramref name="tree"/>.</param>
/// <returns><see cref="CompletionItem"/>s from all <see cref="CompletionProvider"/>s.</returns>
public ImmutableArray<CompletionItem> GetCompletions(SyntaxTree tree, SemanticModel semanticModel, SyntaxPosition cursor)
public ImmutableArray<CompletionItem> GetCompletions(SyntaxTree tree, SemanticModel semanticModel, int cursorIndex)
{
var result = ImmutableArray.CreateBuilder<CompletionItem>();
var currentContext = this.GetCurrentContexts(tree, cursor);
var currentContext = this.GetCurrentContexts(tree, cursorIndex);
foreach (var provider in this.providers)
{
if (provider.IsApplicableIn(currentContext))
{
result.AddRange(provider.GetCompletionItems(tree, semanticModel, cursor, currentContext));
result.AddRange(provider.GetCompletionItems(tree, semanticModel, cursorIndex, currentContext));
}
}
return result.ToImmutable();
}

/// <summary>
/// Gets current context based on location of <paramref name="cursor"/> in the <paramref name="syntaxTree"/>.
/// Gets current context based on location of <paramref name="cursorIndex"/> in the <paramref name="syntaxTree"/>.
/// </summary>
/// <param name="syntaxTree">The <see cref="SyntaxTree"/> in which to find contexts.</param>
/// <param name="cursor">The location in the <paramref name="syntaxTree"/>.</param>
/// <param name="cursorIndex">The location in the <paramref name="syntaxTree"/>.</param>
/// <returns>Flag enum of the currently valid <see cref="CompletionContext"/>s.</returns>
private CompletionContext GetCurrentContexts(SyntaxTree syntaxTree, SyntaxPosition cursor)
private CompletionContext GetCurrentContexts(SyntaxTree syntaxTree, int cursorIndex)
{
var cursor = syntaxTree.IndexToSyntaxPosition(cursorIndex);
var node = syntaxTree.Root.TraverseSubtreesAtCursorPosition(cursor).Last();
return node switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,43 @@ public override bool IsApplicableIn(CompletionContext context)
return context.HasFlag(CompletionContext.Expression) || context.HasFlag(CompletionContext.Type) || context.HasFlag(CompletionContext.Import);
}

public override ImmutableArray<CompletionItem> GetCompletionItems(SyntaxTree tree, SemanticModel semanticModel, SyntaxPosition cursor, CompletionContext contexts)
public override ImmutableArray<CompletionItem> GetCompletionItems(
SyntaxTree tree, SemanticModel semanticModel, int cursorIndex, CompletionContext contexts)
{
var cursor = tree.IndexToSyntaxPosition(cursorIndex);
var syntax = tree.Root.TraverseSubtreesAtCursorPosition(cursor).LastOrDefault();
if (syntax is null) return [];
var symbols = semanticModel.GetAllDefinedSymbols(syntax);
var range = (syntax as SyntaxToken)?.Range ?? new(cursor, 0);
var span = (syntax as SyntaxToken)?.Span ?? new(cursorIndex, 0);
var completions = symbols
// NOTE: Grouping by GetType is very error-prone, maybe we need a symbol "kind"
.GroupBy(x => (x.GetType(), x.Name))
.Select(x => GetCompletionItem([.. x], contexts, range));
.Select(x => GetCompletionItem(tree.SourceText, [.. x], contexts, span));
return completions.OfType<CompletionItem>().ToImmutableArray();
}

private static CompletionItem? GetCompletionItem(ImmutableArray<ISymbol> symbols, CompletionContext currentContexts, SyntaxRange range) => symbols.First() switch
{
TypeSymbol or TypeAliasSymbol when currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Type) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Class),
private static CompletionItem? GetCompletionItem(
SourceText source, ImmutableArray<ISymbol> symbols, CompletionContext currentContexts, SourceSpan span) => symbols.First() switch
{
TypeSymbol or TypeAliasSymbol when currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Type) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Class),

IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Variable),
IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Variable),

PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Property),
PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Property),

// We need the type context here for qualified type references
ModuleSymbol when currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Import) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Module),
// We need the type context here for qualified type references
ModuleSymbol when currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Import) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Module),

FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Function),
FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Function),

_ => null,
};
_ => null,
};
}
48 changes: 25 additions & 23 deletions src/Draco.Compiler/Api/CodeCompletion/KeywordCompletionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ namespace Draco.Compiler.Api.CodeCompletion;
/// </summary>
public sealed class KeywordCompletionProvider : CompletionProvider
{
private static ImmutableArray<CompletionItem> GetDeclarationKeywords(SyntaxRange range) =>
private static ImmutableArray<CompletionItem> GetDeclarationKeywords(SourceText source, SourceSpan span) =>
[
CompletionItem.Create("import", range, CompletionKind.Keyword),
CompletionItem.Create("var", range, CompletionKind.Keyword),
CompletionItem.Create("val", range, CompletionKind.Keyword),
CompletionItem.Create("func", range, CompletionKind.Keyword),
CompletionItem.Create("internal", range, CompletionKind.Keyword),
CompletionItem.Create("public", range, CompletionKind.Keyword),
CompletionItem.Create("module", range, CompletionKind.Keyword),
CompletionItem.Create(source, "import", span, CompletionKind.Keyword),
CompletionItem.Create(source, "var", span, CompletionKind.Keyword),
CompletionItem.Create(source, "val", span, CompletionKind.Keyword),
CompletionItem.Create(source, "func", span, CompletionKind.Keyword),
CompletionItem.Create(source, "internal", span, CompletionKind.Keyword),
CompletionItem.Create(source, "public", span, CompletionKind.Keyword),
CompletionItem.Create(source, "module", span, CompletionKind.Keyword),
];

private static ImmutableArray<CompletionItem> GetExpressionKeywords(SyntaxRange range) =>
private static ImmutableArray<CompletionItem> GetExpressionKeywords(SourceText source, SourceSpan span) =>
[
CompletionItem.Create("if", range, CompletionKind.Keyword),
CompletionItem.Create("while", range, CompletionKind.Keyword),
CompletionItem.Create("for", range, CompletionKind.Keyword),
CompletionItem.Create("return", range, CompletionKind.Keyword),
CompletionItem.Create("goto", range, CompletionKind.Keyword),
CompletionItem.Create("and", range, CompletionKind.Keyword),
CompletionItem.Create("or", range, CompletionKind.Keyword),
CompletionItem.Create("not", range, CompletionKind.Keyword),
CompletionItem.Create("mod", range, CompletionKind.Keyword),
CompletionItem.Create("rem", range, CompletionKind.Keyword),
CompletionItem.Create(source, "if", span, CompletionKind.Keyword),
CompletionItem.Create(source, "while", span, CompletionKind.Keyword),
CompletionItem.Create(source, "for", span, CompletionKind.Keyword),
CompletionItem.Create(source, "return", span, CompletionKind.Keyword),
CompletionItem.Create(source, "goto", span, CompletionKind.Keyword),
CompletionItem.Create(source, "and", span, CompletionKind.Keyword),
CompletionItem.Create(source, "or", span, CompletionKind.Keyword),
CompletionItem.Create(source, "not", span, CompletionKind.Keyword),
CompletionItem.Create(source, "mod", span, CompletionKind.Keyword),
CompletionItem.Create(source, "rem", span, CompletionKind.Keyword),
];

public override bool IsApplicableIn(CompletionContext context)
Expand All @@ -41,14 +41,16 @@ public override bool IsApplicableIn(CompletionContext context)
return context.HasFlag(CompletionContext.Declaration) || context.HasFlag(CompletionContext.Expression);
}

public override ImmutableArray<CompletionItem> GetCompletionItems(SyntaxTree tree, SemanticModel semanticModel, SyntaxPosition cursor, CompletionContext contexts)
public override ImmutableArray<CompletionItem> GetCompletionItems(
SyntaxTree tree, SemanticModel semanticModel, int cursorIndex, CompletionContext contexts)
{
var cursor = tree.IndexToSyntaxPosition(cursorIndex);
var syntax = tree.Root.TraverseSubtreesAtCursorPosition(cursor).LastOrDefault();
if (syntax is null) return [];
var range = (syntax as SyntaxToken)?.Range ?? new(cursor, 0);
var span = (syntax as SyntaxToken)?.Span ?? new(cursorIndex, 0);
var result = ImmutableArray.CreateBuilder<CompletionItem>();
if (contexts.HasFlag(CompletionContext.Expression)) result.AddRange(GetExpressionKeywords(range));
if (contexts.HasFlag(CompletionContext.Declaration)) result.AddRange(GetDeclarationKeywords(range));
if (contexts.HasFlag(CompletionContext.Expression)) result.AddRange(GetExpressionKeywords(tree.SourceText, span));
if (contexts.HasFlag(CompletionContext.Declaration)) result.AddRange(GetDeclarationKeywords(tree.SourceText, span));
return result.ToImmutable();
}
}
44 changes: 24 additions & 20 deletions src/Draco.Compiler/Api/CodeCompletion/MemberCompletionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using Draco.Compiler.Api.Semantics;
using Draco.Compiler.Api.Syntax;
using Draco.Compiler.Internal.Symbols.Source;

namespace Draco.Compiler.Api.CodeCompletion;

Expand All @@ -17,19 +18,21 @@ public override bool IsApplicableIn(CompletionContext context)
return context.HasFlag(CompletionContext.Expression) || context.HasFlag(CompletionContext.Type) || context.HasFlag(CompletionContext.Import);
}

public override ImmutableArray<CompletionItem> GetCompletionItems(SyntaxTree tree, SemanticModel semanticModel, SyntaxPosition cursor, CompletionContext contexts)
public override ImmutableArray<CompletionItem> GetCompletionItems(
SyntaxTree tree, SemanticModel semanticModel, int cursorIndex, CompletionContext contexts)
{
var cursor = tree.IndexToSyntaxPosition(cursorIndex);
var nodesAtCursor = tree.Root.TraverseSubtreesAtCursorPosition(cursor);
if (nodesAtCursor.LastOrDefault() is not SyntaxToken token) return [];
var expr = token.Parent;
var range = token.Kind == TokenKind.Dot ? new SyntaxRange(token.Range.End, 0) : token.Range;
var span = token.Kind == TokenKind.Dot ? new SourceSpan(token.Span.End, 0) : token.Span;
// If we can't get the accessed propery, we just return empty array
if (!TryGetMemberAccess(tree, cursor, semanticModel, out var symbols)) return [];
var completions = symbols
// NOTE: Not very robust, just like in the other place
// Also, duplication
.GroupBy(x => (x.GetType(), x.Name))
.Select(x => GetCompletionItem([.. x], contexts, range));
.Select(x => GetCompletionItem(tree.SourceText, [.. x], contexts, span));
return completions.OfType<CompletionItem>().ToImmutableArray();
}

Expand Down Expand Up @@ -74,26 +77,27 @@ public static bool TryDeconstructMemberAccess(SyntaxNode? node, [MaybeNullWhen(f
}
}

private static CompletionItem? GetCompletionItem(ImmutableArray<ISymbol> symbols, CompletionContext currentContexts, SyntaxRange range) => symbols.First() switch
{
TypeSymbol when currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Class),
private static CompletionItem? GetCompletionItem(
SourceText source, ImmutableArray<ISymbol> symbols, CompletionContext currentContexts, SourceSpan span) => symbols.First() switch
{
TypeSymbol when currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Class),

ModuleSymbol when currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Import) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Module),
ModuleSymbol when currentContexts.HasFlag(CompletionContext.Type)
|| currentContexts.HasFlag(CompletionContext.Expression)
|| currentContexts.HasFlag(CompletionContext.Import) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Module),

IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Variable),
IVariableSymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Variable),

PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Property),
PropertySymbol when currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Property),

FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(symbols.First().Name, range, symbols, CompletionKind.Function),
FunctionSymbol fun when !fun.IsSpecialName && currentContexts.HasFlag(CompletionContext.Expression) =>
CompletionItem.Create(source, symbols.First().Name, span, symbols, CompletionKind.Function),

_ => null,
};
_ => null,
};
}
1 change: 1 addition & 0 deletions src/Draco.Compiler/Api/CodeFixes/CodeFix.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using Draco.Compiler.Api.Syntax;

namespace Draco.Compiler.Api.CodeFixes;

Expand Down
Loading

0 comments on commit 2e43b3e

Please sign in to comment.