Skip to content

Commit

Permalink
Adding support for Typeinference based on runtime variable values (#2744
Browse files Browse the repository at this point in the history
)

* Refactored the type inference code in preparation to make it a public api.
* Added type inference tests

Fixes #2567
  • Loading branch information
Staffan Gustafsson authored and lzybkr committed Jun 7, 2017
1 parent a164609 commit a52adcd
Show file tree
Hide file tree
Showing 10 changed files with 2,731 additions and 1,478 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -65,3 +65,7 @@ gen

# TestsResults
TestsResults*.xml

# Resharper settings
PowerShell.sln.DotSettings.user

Expand Up @@ -641,7 +641,7 @@ private static List<CompletionResult> InvokeLegacyTabExpansion(PowerShell powers
char quote;
var lastword = LastWordFinder.FindLastWord(legacyInput, out replacementIndex, out quote);
replacementLength = legacyInput.Length - replacementIndex;
var helper = new CompletionExecutionHelper(powershell);
var helper = new PowerShellExecutionHelper(powershell);

powershell.AddCommand("TabExpansion").AddArgument(legacyInput).AddArgument(lastword);

Expand Down Expand Up @@ -749,7 +749,7 @@ internal CommandAndName(PSObject command, PSSnapinQualifiedName commandName)
/// <param name="quote"></param>
/// <param name="completingAtStartOfLine"></param>
/// <returns></returns>
internal static List<CompletionResult> PSv2GenerateMatchSetOfCmdlets(CompletionExecutionHelper helper, string lastWord, string quote, bool completingAtStartOfLine)
internal static List<CompletionResult> PSv2GenerateMatchSetOfCmdlets(PowerShellExecutionHelper helper, string lastWord, string quote, bool completingAtStartOfLine)
{
var results = new List<CompletionResult>();
bool isSnapinSpecified;
Expand Down Expand Up @@ -864,7 +864,7 @@ private static void PrependSnapInNameForSameCmdletNames(CommandAndName[] cmdlets

#region "Handle File Names"

internal static List<CompletionResult> PSv2GenerateMatchSetOfFiles(CompletionExecutionHelper helper, string lastWord, bool completingAtStartOfLine, string quote)
internal static List<CompletionResult> PSv2GenerateMatchSetOfFiles(PowerShellExecutionHelper helper, string lastWord, bool completingAtStartOfLine, string quote)
{
var results = new List<CompletionResult>();

Expand Down Expand Up @@ -926,7 +926,7 @@ internal static List<CompletionResult> PSv2GenerateMatchSetOfFiles(CompletionExe

bool? isContainer = SafeGetProperty<bool?>(combinedMatch.Item, "PSIsContainer");
string childName = SafeGetProperty<string>(combinedMatch.Item, "PSChildName");
string toolTip = CompletionExecutionHelper.SafeToString(combinedMatch.ConvertedPath);
string toolTip = PowerShellExecutionHelper.SafeToString(combinedMatch.ConvertedPath);

if (isContainer != null && childName != null && toolTip != null)
{
Expand Down Expand Up @@ -1034,7 +1034,7 @@ private static T SafeGetProperty<T>(PSObject psObject, string propertyName)
return default(T);
}

private static bool PSv2ShouldFullyQualifyPathsPath(CompletionExecutionHelper helper, string lastWord)
private static bool PSv2ShouldFullyQualifyPathsPath(PowerShellExecutionHelper helper, string lastWord)
{
// These are special cases, as they represent cases where the user expects to
// see the full path.
Expand Down Expand Up @@ -1068,7 +1068,7 @@ internal PathItemAndConvertedPath(string path, PSObject item, string convertedPa
}
}

private static List<PathItemAndConvertedPath> PSv2FindMatches(CompletionExecutionHelper helper, string path, bool shouldFullyQualifyPaths)
private static List<PathItemAndConvertedPath> PSv2FindMatches(PowerShellExecutionHelper helper, string path, bool shouldFullyQualifyPaths)
{
Diagnostics.Assert(!String.IsNullOrEmpty(path), "path should have a value");
var result = new List<PathItemAndConvertedPath>();
Expand Down Expand Up @@ -1113,9 +1113,9 @@ private static List<PathItemAndConvertedPath> PSv2FindMatches(CompletionExecutio
}

result.Add(new PathItemAndConvertedPath(
CompletionExecutionHelper.SafeToString(objectPath),
PowerShellExecutionHelper.SafeToString(objectPath),
item,
CompletionExecutionHelper.SafeToString(convertedPath)));
PowerShellExecutionHelper.SafeToString(convertedPath)));
}
}

Expand Down
Expand Up @@ -24,7 +24,7 @@ internal class CompletionContext
internal Token TokenBeforeCursor { get; set; }
internal IScriptPosition CursorPosition { get; set; }

internal CompletionExecutionHelper Helper { get; set; }
internal PowerShellExecutionHelper Helper { get; set; }
internal Hashtable Options { get; set; }
internal Dictionary<string, ScriptBlock> CustomArgumentCompleters { get; set; }
internal Dictionary<string, ScriptBlock> NativeArgumentCompleters { get; set; }
Expand All @@ -33,7 +33,7 @@ internal class CompletionContext
internal int ReplacementLength { get; set; }
internal ExecutionContext ExecutionContext { get; set; }
internal PseudoBindingInfo PseudoBindingInfo { get; set; }
internal TypeDefinitionAst CurrentTypeDefinitionAst { get; set; }
internal TypeInferenceContext TypeInferenceContext { get; set; }

internal bool GetOption(string option, bool @default)
{
Expand Down Expand Up @@ -96,15 +96,26 @@ private static bool IsCursorOutsideOfExtent(IScriptPosition cursor, IScriptExten
return cursor.Offset < extent.StartOffset || cursor.Offset > extent.EndOffset;
}

internal CompletionContext CreateCompletionContext(ExecutionContext executionContext)

internal CompletionContext CreateCompletionContext(PowerShell powerShell)
{
var typeInferenceContext = new TypeInferenceContext(powerShell);
return InitializeCompletionContext(typeInferenceContext);
}
internal CompletionContext CreateCompletionContext(TypeInferenceContext typeInferenceContext)
{
return InitializeCompletionContext(typeInferenceContext);
}

private CompletionContext InitializeCompletionContext(TypeInferenceContext typeInferenceContext)
{
Token tokenBeforeCursor = null;
IScriptPosition positionForAstSearch = _cursorPosition;
var adjustLineAndColumn = false;
var tokenAtCursor = _tokens.LastOrDefault(token => IsCursorWithinOrJustAfterExtent(_cursorPosition, token.Extent) && IsInterestingToken(token));
var tokenAtCursor = InterstingTokenAtCursorOrDefault(_tokens, _cursorPosition);
if (tokenAtCursor == null)
{
tokenBeforeCursor = _tokens.LastOrDefault(token => IsCursorAfterExtent(_cursorPosition, token.Extent) && IsInterestingToken(token));
tokenBeforeCursor = InterstingTokenBeforeCursorOrDefault(_tokens, _cursorPosition);
if (tokenBeforeCursor != null)
{
positionForAstSearch = tokenBeforeCursor.Extent.EndScriptPosition;
Expand All @@ -114,17 +125,22 @@ internal CompletionContext CreateCompletionContext(ExecutionContext executionCon
else
{
var stringExpandableToken = tokenAtCursor as StringExpandableToken;
if (stringExpandableToken != null && stringExpandableToken.NestedTokens != null)
if (stringExpandableToken?.NestedTokens != null)
{
tokenAtCursor =
stringExpandableToken.NestedTokens.LastOrDefault(
token => IsCursorWithinOrJustAfterExtent(_cursorPosition, token.Extent) && IsInterestingToken(token)) ?? stringExpandableToken;
tokenAtCursor = InterstingTokenAtCursorOrDefault(stringExpandableToken.NestedTokens, _cursorPosition) ?? stringExpandableToken;
}
}

var asts = AstSearcher.FindAll(_ast, ast => IsCursorWithinOrJustAfterExtent(positionForAstSearch, ast.Extent), searchNestedScriptBlocks: true).ToList();

Diagnostics.Assert(tokenAtCursor == null || tokenBeforeCursor == null, "Only one of these tokens can be non-null");

if (typeInferenceContext.CurrentTypeDefinitionAst == null)
{
typeInferenceContext.CurrentTypeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(asts.Last());
}
ExecutionContext executionContext = typeInferenceContext.ExecutionContext;

return new CompletionContext
{
TokenAtCursor = tokenAtCursor,
Expand All @@ -134,12 +150,24 @@ internal CompletionContext CreateCompletionContext(ExecutionContext executionCon
Options = _options,
ExecutionContext = executionContext,
ReplacementIndex = adjustLineAndColumn ? _cursorPosition.Offset : 0,
CurrentTypeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(asts.Last()),
TypeInferenceContext = typeInferenceContext,
Helper = typeInferenceContext.Helper,
CustomArgumentCompleters = executionContext.CustomArgumentCompleters,
NativeArgumentCompleters = executionContext.NativeArgumentCompleters,
};
}

private static Token InterstingTokenAtCursorOrDefault(IEnumerable<Token> tokens, IScriptPosition cursorPosition)
{
return tokens.LastOrDefault(token => IsCursorWithinOrJustAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token));
}

private static Token InterstingTokenBeforeCursorOrDefault(IEnumerable<Token> tokens, IScriptPosition cursorPosition)
{
return tokens.LastOrDefault(token => IsCursorAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token));
}


private static Ast GetLastAstAtCursor(ScriptBlockAst scriptBlockAst, IScriptPosition cursorPosition)
{
var asts = AstSearcher.FindAll(scriptBlockAst, ast => IsCursorRightAfterExtent(cursorPosition, ast.Extent), searchNestedScriptBlocks: true);
Expand Down Expand Up @@ -278,8 +306,7 @@ private static bool IsTokenTheSame(Token x, Token y)

internal List<CompletionResult> GetResults(PowerShell powerShell, out int replacementIndex, out int replacementLength)
{
var completionContext = CreateCompletionContext(powerShell.GetContextFromTLS());
completionContext.Helper = new CompletionExecutionHelper(powerShell);
var completionContext = CreateCompletionContext(powerShell);

PSLanguageMode? previousLanguageMode = null;
try
Expand Down Expand Up @@ -1173,8 +1200,7 @@ private List<CompletionResult> GetResultForString(CompletionContext completionCo
cursorIndexInString = strValue.Length;

var analysis = new CompletionAnalysis(_ast, _tokens, _cursorPosition, _options);
var subContext = analysis.CreateCompletionContext(completionContext.ExecutionContext);
subContext.Helper = completionContext.Helper;
var subContext = analysis.CreateCompletionContext(completionContext.TypeInferenceContext);

int subReplaceIndex, subReplaceLength;
var subResult = analysis.GetResultHelper(subContext, out subReplaceIndex, out subReplaceLength, true);
Expand Down

0 comments on commit a52adcd

Please sign in to comment.