Skip to content

Commit

Permalink
Add Namedtype suggestions for Type arguments in IntelliSense (#2625)
Browse files Browse the repository at this point in the history
Adds Namedtype suggestions for Type arguments in functions that accept
type (ParseJSON, IsType, AsType).

Suggestion handler for TypeLiteralNode is currently in PAclient and will
be moved to this repo in a follow up PR.
  • Loading branch information
adithyaselv authored Sep 6, 2024
1 parent d6aa0dd commit 3c7c076
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ public virtual bool SupportCoercionForArg(int argIndex)

public virtual bool ArgIsType(int argIndex)
{
Contracts.Assert(!HasTypeArgs);
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ public override bool ArgIsType(int argIndex)
return argIndex == 1;
}

private static readonly ISet<DType> SupportedJSONTypes = new HashSet<DType> { DType.Boolean, DType.Number, DType.Decimal, DType.Date, DType.DateTime, DType.DateTimeNoTimeZone, DType.Time, DType.String, DType.Guid, DType.Hyperlink, DType.UntypedObject };
public override bool HasSuggestionsForParam(int argIndex)
{
return argIndex == 1;
}

internal static readonly ISet<DType> SupportedJSONTypes = new HashSet<DType> { DType.Boolean, DType.Number, DType.Decimal, DType.Date, DType.DateTime, DType.DateTimeNoTimeZone, DType.Time, DType.String, DType.Guid, DType.Hyperlink, DType.UntypedObject };

public UntypedOrJSONConversionFunction(string name, TexlStrings.StringGetter description, DType returnType, int arityMax, params DType[] paramTypes)
: base(name, description, FunctionCategories.Text, returnType, 0, 2, arityMax, paramTypes)
Expand Down Expand Up @@ -68,28 +73,10 @@ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[

base.CheckSemantics(binding, args, argTypes, errors);

CheckTypeArgHasSupportedTypes(args[1], argTypes[1], errors);
}

private void CheckTypeArgHasSupportedTypes(TexlNode typeArg, DType type, IErrorContainer errors)
{
// Dataverse types may contain fields with ExpandInfo that may have self / mutually recursive reference
// we allow these in type check phase by ignoring validation of types in such fields.
if (type.HasExpandInfo)
{
return;
}

if ((type.IsRecordNonObjNull || type.IsTableNonObjNull) && type.TypeTree != null)
{
type.TypeTree.ToList().ForEach(t => CheckTypeArgHasSupportedTypes(typeArg, t.Value, errors));
return;
}

if (!SupportedJSONTypes.Contains(type))
// check if the given type argument is not supported
if (!DType.IsSupportedType(argTypes[1], SupportedJSONTypes, out var unsupportedType))
{
errors.EnsureError(DocumentErrorSeverity.Severe, typeArg, TexlStrings.ErrUnsupportedTypeInTypeArgument, type.Kind);
return;
errors.EnsureError(DocumentErrorSeverity.Severe, args[1], TexlStrings.ErrUnsupportedTypeInTypeArgument, unsupportedType.Kind);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.PowerFx.Core.Binding;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Texl.Builtins;
using Microsoft.PowerFx.Core.Texl.Intellisense.SuggestionHandlers.CleanupHandlers;
using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Types.Enums;
using Microsoft.PowerFx.Core.Utils;
Expand Down Expand Up @@ -40,8 +42,13 @@ internal static class ArgumentSuggestions
{ typeof(ValueFunction), LanguageCodeSuggestion }
}, isThreadSafe: true);

public static IEnumerable<KeyValuePair<string, DType>> GetArgumentSuggestions(TryGetEnumSymbol tryGetEnumSymbol, bool suggestUnqualifiedEnums, TexlFunction function, DType scopeType, int argumentIndex, out bool requiresSuggestionEscaping)
public static IEnumerable<KeyValuePair<string, DType>> GetArgumentSuggestions(IntellisenseData.IntellisenseData intellisenseData, TryGetEnumSymbol tryGetEnumSymbol, bool suggestUnqualifiedEnums, TexlFunction function, DType scopeType, int argumentIndex, out bool requiresSuggestionEscaping)
{
if (function.HasTypeArgs)
{
return NamedTypeSuggestions(intellisenseData, function, argumentIndex, out requiresSuggestionEscaping);
}

if (CustomFunctionSuggestionProviders.Value.TryGetValue(function.GetType(), out var suggestor))
{
return suggestor(tryGetEnumSymbol, suggestUnqualifiedEnums, scopeType, argumentIndex, out requiresSuggestionEscaping);
Expand Down Expand Up @@ -214,5 +221,30 @@ private static IEnumerable<KeyValuePair<string, DType>> IfSuggestions(TryGetEnum
.GetNames(DPath.Root)
.Select(name => new KeyValuePair<string, DType>(TexlLexer.EscapeName(name.Name.Value), name.Type));
}

// This method returns the suggestions for type arguments of the JSON , Untyped object conversion functions
public static IEnumerable<KeyValuePair<string, DType>> NamedTypeSuggestions(IntellisenseData.IntellisenseData data, TexlFunction function, int argumentIndex, out bool requiresSuggestionEscaping)
{
Contracts.AssertValue(data);
Contracts.AssertValue(data.Binding);
Contracts.AssertValue(data.Binding.NameResolver);
Contracts.AssertValue(data.Binding.NameResolver.NamedTypes);
Contracts.Assert(function.HasTypeArgs);

requiresSuggestionEscaping = false;

if (argumentIndex == 1)
{
Contracts.Assert(function.ArgIsType(argumentIndex));

// If cursor is currently at type argument, add handler to cleanup SuggestionKinds other than Type.
data.CleanupHandlers.Add(new TypeArgCleanUpHandler());

var types = data.Binding.NameResolver.NamedTypes.Where(t => DType.IsSupportedType(t.Value._type, UntypedOrJSONConversionFunction.SupportedJSONTypes, out var _));
return types.Select(t => new KeyValuePair<string, DType>(TexlLexer.EscapeName(t.Key), t.Value._type));
}

return EnumerableUtils.Yield<KeyValuePair<string, DType>>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ internal virtual IEnumerable<KeyValuePair<string, DType>> GetArgumentSuggestions
Contracts.AssertValue(function);
Contracts.AssertValue(scopeType);

return ArgumentSuggestions.GetArgumentSuggestions(TryGetEnumSymbol, SuggestUnqualifiedEnums, function, scopeType, argumentIndex, out requiresSuggestionEscaping);
return ArgumentSuggestions.GetArgumentSuggestions(this, TryGetEnumSymbol, SuggestUnqualifiedEnums, function, scopeType, argumentIndex, out requiresSuggestionEscaping);
}

/// <summary>
Expand All @@ -314,7 +314,15 @@ internal virtual IEnumerable<KeyValuePair<string, DType>> GetArgumentSuggestions
/// <returns>
/// The suggestion kind for the hypothetical suggestion.
/// </returns>
internal virtual SuggestionKind GetFunctionSuggestionKind(TexlFunction function, int argumentIndex) => SuggestionKind.Global;
internal virtual SuggestionKind GetFunctionSuggestionKind(TexlFunction function, int argumentIndex)
{
if (function.ArgIsType(argumentIndex))
{
return SuggestionKind.Type;
}

return SuggestionKind.Global;
}

/// <summary>
/// This method is called after all default suggestions for value possibilities have been run and may be
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Collections.Generic;
using Microsoft.PowerFx.Core.Utils;
using Microsoft.PowerFx.Intellisense;
using Microsoft.PowerFx.Intellisense.IntellisenseData;

namespace Microsoft.PowerFx.Core.Texl.Intellisense.SuggestionHandlers.CleanupHandlers
{
// Power Fx Intellisense uses clean-up handlers to filter any unnecessary intellisense data that is added as a part of default logic.
// For Type arguments, we do not want to suggest suggestions other than SuggestionKind.Type as they are not relevant.
// This handler is added to Intellisense.CleanupHandlers when only type suggestions are expected in the output.
internal sealed class TypeArgCleanUpHandler : ISpecialCaseHandler
{
public bool Run(IIntellisenseContext context, IntellisenseData intellisenseData, List<IntellisenseSuggestion> suggestions)
{
Contracts.AssertValue(context);
Contracts.AssertValue(suggestions);
Contracts.AssertValue(intellisenseData.CurFunc);
Contracts.Assert(intellisenseData.CurFunc.ArgIsType(intellisenseData.ArgIndex));

var removedAny = false;

var iterateSuggestions = suggestions.ToArray();

foreach (var suggestion in iterateSuggestions)
{
if (suggestion.Kind != SuggestionKind.Type)
{
suggestions.Remove(suggestion);
removedAny = true;
}
}

return removedAny;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ public enum SuggestionKind
ServiceFunctionOption,
Service,
ScopeVariable,
Type,
}
}
34 changes: 34 additions & 0 deletions src/libraries/Microsoft.PowerFx.Core/Types/DType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3937,5 +3937,39 @@ public static bool TryUnionWithCoerce(DType leftType, DType rightType, Features

return !fError;
}

internal static bool IsSupportedType(DType type, ISet<DType> supportedTypes, out DType unsupportedType)
{
unsupportedType = null;

// Dataverse types may contain fields with ExpandInfo that may have self / mutually recursive reference
// we allow these in type check phase by ignoring validation of types in such fields.
if (type.HasExpandInfo)
{
return true;
}

if ((type.IsRecordNonObjNull || type.IsTableNonObjNull) && type.TypeTree != null)
{
foreach (var ctype in type.TypeTree)
{
if (!IsSupportedType(ctype.Value, supportedTypes, out unsupportedType))
{
return false;
}
}

unsupportedType = null;
return true;
}

if (!supportedTypes.Contains(type))
{
unsupportedType = type;
return false;
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -596,5 +596,29 @@ public void LazyTypesStackOverflowTest(string expression)
// Just check that the execution didn't stack overflow.
Assert.True(suggestions.Any());
}

[Theory]
[InlineData("ParseJSON(\"42\", Nu|", "Number")]
[InlineData("AsType(ParseJSON(\"42\"), Da|", "Date", "DateTime", "DateTimeTZInd")]
[InlineData("IsType(ParseJSON(\"42\"),|", "'My Type With Space'", "'Some \" DQuote'", "Boolean", "Date", "DateTime", "DateTimeTZInd", "Decimal", "GUID", "Hyperlink", "MyNewType", "Number", "Text", "Time", "UntypedObject")]
[InlineData("ParseJSON(\"42\", Voi|")]
[InlineData("ParseJSON(\"42\", MyN|", "MyNewType")]
[InlineData("ParseJSON(\"42\", Tim|", "DateTime", "DateTimeTZInd", "Time")]
[InlineData("ParseJSON(\"42\", My|", "'My Type With Space'", "MyNewType")]
[InlineData("ParseJSON(\"42\", So|", "'Some \" DQuote'")]
public void TypeArgumentsTest(string expression, params string[] expected)
{
var symbolTable = SymbolTable.WithPrimitiveTypes();

symbolTable.AddType(new DName("MyNewType"), FormulaType.String);
symbolTable.AddType(new DName("My Type With Space"), FormulaType.String);
symbolTable.AddType(new DName("Some \" DQuote"), FormulaType.String);
symbolTable.AddFunctions(new TexlFunctionSet(BuiltinFunctionsCore.TestOnly_AllBuiltinFunctions.Where(f => f.HasTypeArgs).ToArray()));
symbolTable.AddFunctions(new TexlFunctionSet(new[] { BuiltinFunctionsCore.ParseJSON }));

var config = new PowerFxConfig();
var actualSuggestions = SuggestStrings(expression, config, null, symbolTable);
Assert.Equal(expected, actualSuggestions);
}
}
}

0 comments on commit 3c7c076

Please sign in to comment.