in src/Bicep.LangServer/Completions/BicepCompletionProvider.cs [1006:1132]
private static IEnumerable<CompletionItem> GetAccessibleSymbolCompletions(SemanticModel model, BicepCompletionContext context)
{
// maps insert text to the completion item
var completions = new Dictionary<string, CompletionItem>();
var declaredNames = new HashSet<string>();
var accessibleDecoratorFunctionsCache = new Dictionary<NamespaceType, IEnumerable<FunctionSymbol>>();
var enclosingDeclarationSymbol = context.EnclosingDeclaration == null
? null
: model.GetSymbolInfo(context.EnclosingDeclaration);
// local function
void AddSymbolCompletions(IDictionary<string, CompletionItem> result, IEnumerable<Symbol> symbols)
{
foreach (var symbol in symbols)
{
if (!result.ContainsKey(symbol.Name) && ShouldSymbolBeIncludedInCompletion(symbol, model, context, enclosingDeclarationSymbol))
{
// the symbol satisfies the following conditions:
// - we have not added a symbol with the same name (avoids duplicate completions)
// - the symbol is different than the enclosing declaration (avoids suggesting cycles)
// - the symbol name is different than the name of the enclosing declaration (avoids suggesting a duplicate identifier)
var priority = GetContextualCompletionPriority(symbol, model, context, enclosingDeclarationSymbol);
result.Add(symbol.Name, CreateSymbolCompletion(symbol, context.ReplacementRange, priority: priority, model: model));
}
}
}
// local function
IEnumerable<FunctionSymbol> GetAccessibleDecoratorFunctionsWithCache(NamespaceType namespaceType)
{
if (accessibleDecoratorFunctionsCache.TryGetValue(namespaceType, out var result))
{
return result;
}
result = GetAccessibleDecoratorFunctions(namespaceType,
context.EnclosingDecorable is null ? null : model.GetDeclaredType(context.EnclosingDecorable),
enclosingDeclarationSymbol);
accessibleDecoratorFunctionsCache.Add(namespaceType, result);
return result;
}
var nsTypeDict = GetNamespaceTypeBySymbol(model);
if (!context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName))
{
// add namespaces first
AddSymbolCompletions(completions, nsTypeDict.Keys);
Func<DeclaredSymbol, bool> symbolFilter = _ => true;
// add accessible symbols from innermost scope and then move to outer scopes
// reverse loop iteration
foreach (var scope in context.ActiveScopes.Reverse())
{
// add referenceable declarations with valid identifiers at current scope
AddSymbolCompletions(completions, scope.Declarations
.Where(symbolFilter)
.Where(decl => decl.NameSource.IsValid && decl.CanBeReferenced()));
if (scope.ScopeResolution == ScopeResolution.GlobalsOnly)
{
// don't inherit outer scope variables
break;
}
if (scope.ScopeResolution == ScopeResolution.InheritFunctionsOnly)
{
symbolFilter = symbol => symbol is DeclaredFunctionSymbol;
}
}
}
else
{
// Only add the namespaces that contain accessible decorator function symbols.
AddSymbolCompletions(completions, nsTypeDict.Keys.Where(
ns => GetAccessibleDecoratorFunctionsWithCache(nsTypeDict[ns]).Any()));
// Record the names of referenceable declarations which will be used to check name clashes later.
declaredNames.UnionWith(model.Root.Declarations.Where(decl => decl.NameSource.IsValid && decl.CanBeReferenced()).Select(decl => decl.Name));
}
// get names of functions that always require to be fully qualified due to clashes between namespaces
var alwaysFullyQualifiedNames = nsTypeDict.Values
.SelectMany(ns => context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName)
? GetAccessibleDecoratorFunctionsWithCache(ns)
: ns.MethodResolver.GetKnownFunctions().Values)
.GroupBy(func => func.Name, (name, functionSymbols) => (name, count: functionSymbols.Count()), LanguageConstants.IdentifierComparer)
.Where(tuple => tuple.count > 1)
.Select(tuple => tuple.name)
.ToHashSet(LanguageConstants.IdentifierComparer);
foreach (var (symbol, namespaceType) in nsTypeDict)
{
var functionSymbols = context.Kind.HasFlag(BicepCompletionContextKind.DecoratorName)
? GetAccessibleDecoratorFunctionsWithCache(namespaceType)
: namespaceType.MethodResolver.GetKnownFunctions().Values;
foreach (var function in functionSymbols)
{
if (function.FunctionFlags.HasFlag(FunctionFlags.ParamDefaultsOnly) && !(enclosingDeclarationSymbol is ParameterSymbol))
{
// this function is only allowed in param defaults but the enclosing declaration is not bound to a parameter symbol
// therefore we should not suggesting this function as a viable completion
continue;
}
if (completions.ContainsKey(function.Name) || alwaysFullyQualifiedNames.Contains(function.Name) || declaredNames.Contains(function.Name))
{
// either there is a declaration with the same name as the function or the function is ambiguous between the imported namespaces
// either way the function must be fully qualified in the completion
var fullyQualifiedFunctionName = $"{symbol.Name}.{function.Name}";
completions.Add(fullyQualifiedFunctionName, CreateSymbolCompletion(function, context.ReplacementRange, model, insertText: fullyQualifiedFunctionName));
}
else
{
// function does not have to be fully qualified
completions.Add(function.Name, CreateSymbolCompletion(function, context.ReplacementRange, model));
}
}
}
return completions.Values;
}