diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 2ddb472f2d..1bf0919cf9 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -3963,7 +3963,7 @@ namespace System.Management.Automation private static void NativeCompletionMemberName(CompletionContext context, List result, CommandAst commandAst, AstParameterArgumentPair parameterInfo, bool propertiesOnly = true) { - IEnumerable prevType = GetInferenceTypes(context, commandAst); + IEnumerable prevType = TypeInferenceVisitor.GetInferredEnumeratedTypes(GetInferenceTypes(context, commandAst)); if (prevType is not null) { HashSet excludedMembers = null; @@ -3973,7 +3973,7 @@ namespace System.Management.Automation } Func filter = propertiesOnly ? IsPropertyMember : null; - CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter, isStatic: false, excludedMembers); + CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter, isStatic: false, excludedMembers, addMethodParenthesis: false); } result.Add(CompletionResult.Null); @@ -5901,22 +5901,25 @@ namespace System.Management.Automation /// /// Complete members against extension methods 'Where' and 'ForEach' /// - private static void CompleteExtensionMethods(string memberName, List results) + private static void CompleteExtensionMethods(string memberName, List results, bool addMethodParenthesis = true) { var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); - CompleteExtensionMethods(pattern, results); + CompleteExtensionMethods(pattern, results, addMethodParenthesis); } /// /// Complete members against extension methods 'Where' and 'ForEach' based on the given pattern. /// - private static void CompleteExtensionMethods(WildcardPattern pattern, List results) + private static void CompleteExtensionMethods(WildcardPattern pattern, List results, bool addMethodParenthesis) { - results.AddRange(from member in s_extensionMethods - where pattern.IsMatch(member.Item1) - select - new CompletionResult(member.Item1 + "(", member.Item1, - CompletionResultType.Method, member.Item2)); + foreach (var member in s_extensionMethods) + { + if (pattern.IsMatch(member.Item1)) + { + string completionText = addMethodParenthesis ? $"{member.Item1}(" : member.Item1; + results.Add(new CompletionResult(completionText, member.Item1, CompletionResultType.Method, member.Item2)); + } + } } /// @@ -6035,7 +6038,15 @@ namespace System.Management.Automation } } - internal static void CompleteMemberByInferredType(TypeInferenceContext context, IEnumerable inferredTypes, List results, string memberName, Func filter, bool isStatic, HashSet excludedMembers = null) + internal static void CompleteMemberByInferredType( + TypeInferenceContext context, + IEnumerable inferredTypes, + List results, + string memberName, + Func filter, + bool isStatic, + HashSet excludedMembers = null, + bool addMethodParenthesis = true) { bool extensionMethodsAdded = false; HashSet typeNameUsed = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -6051,7 +6062,7 @@ namespace System.Management.Automation var members = context.GetMembersByInferredType(psTypeName, isStatic, filter); foreach (var member in members) { - AddInferredMember(member, memberNamePattern, results, excludedMembers); + AddInferredMember(member, memberNamePattern, results, excludedMembers, addMethodParenthesis); } // Check if we need to complete against the extension methods 'Where' and 'ForEach' @@ -6059,7 +6070,7 @@ namespace System.Management.Automation { // Complete extension methods 'Where' and 'ForEach' for Enumerable types extensionMethodsAdded = true; - CompleteExtensionMethods(memberNamePattern, results); + CompleteExtensionMethods(memberNamePattern, results, addMethodParenthesis); } } @@ -6077,7 +6088,7 @@ namespace System.Management.Automation } } - private static void AddInferredMember(object member, WildcardPattern memberNamePattern, List results, HashSet excludedMembers) + private static void AddInferredMember(object member, WildcardPattern memberNamePattern, List results, HashSet excludedMembers, bool addMethodParenthesis) { string memberName = null; bool isMethod = false; @@ -6149,7 +6160,7 @@ namespace System.Management.Automation var completionResultType = isMethod ? CompletionResultType.Method : CompletionResultType.Property; string completionText; - if (isMethod) + if (isMethod && addMethodParenthesis) { completionText = $"{memberName}("; } diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 25bd7f999a..a1e761c1cc 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -1288,7 +1288,7 @@ namespace System.Management.Automation if (i > 0) { - inferredTypes.AddRange(InferTypes(parentPipeline.PipelineElements[i - 1])); + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(parentPipeline.PipelineElements[i - 1]))); } } } @@ -1931,7 +1931,7 @@ namespace System.Management.Automation } else if (parent is not null) { - AddInferredTypesForDollarUnderbar(parent, inferredTypes); + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(parent))); } return; @@ -2075,42 +2075,6 @@ namespace System.Management.Automation } } - private void AddInferredTypesForDollarUnderbar(Ast parentExpression, List results) - { - foreach (var result in InferTypes(parentExpression)) - { - if (result.Type != null) - { - // Assume (because we're looking at $_ and we're inside a script block that is an - // argument to some command) that the type we're getting is actually unrolled. - // This might not be right in all cases, but with our simple analysis, it's - // right more often than it's wrong. - if (result.Type.IsArray) - { - results.Add(new PSTypeName(result.Type.GetElementType())); - continue; - } - - if (result.Type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(result.Type)) - { - // We can't deduce much from IEnumerable, but we can if it's generic. - var enumerableInterfaces = result.Type.GetInterfaces(); - foreach (var t in enumerableInterfaces) - { - if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - results.Add(new PSTypeName(t.GetGenericArguments()[0])); - } - } - - continue; - } - } - - results.Add(result); - } - } - /// /// Gets the most specific array type possible from a group of inferred types. /// @@ -2363,7 +2327,7 @@ namespace System.Management.Automation /// The potentially enumerable types to infer enumerated type from. /// /// The enumerated item types. - private static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) + internal static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) { foreach (PSTypeName maybeEnumerableType in enumerableTypes) { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 2134d64530..4ff31245a3 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -504,7 +504,7 @@ switch ($x) It 'ForEach-Object member completion results should include methods' { $res = TabExpansion2 -inputScript '1..10 | ForEach-Object -MemberName ' - $res.CompletionMatches.CompletionText | Should -Contain "GetType(" + $res.CompletionMatches.CompletionText | Should -Contain "GetType" } It 'Should not complete void instance members' { @@ -571,6 +571,13 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Directory' } + It 'Should enumerate types when completing member names for Select-Object' { + $TestString = '"Hello","World" | select-object ' + $res = TabExpansion2 -inputScript $TestString + $res | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Length' + } + Context "Format cmdlet's View paramter completion" { BeforeAll { $viewDefinition = @' diff --git a/test/powershell/engine/Api/TypeInference.Tests.ps1 b/test/powershell/engine/Api/TypeInference.Tests.ps1 index cd99abaf46..1c41ef72e8 100644 --- a/test/powershell/engine/Api/TypeInference.Tests.ps1 +++ b/test/powershell/engine/Api/TypeInference.Tests.ps1 @@ -1342,6 +1342,12 @@ Describe "Type inference Tests" -tags "CI" { $res.Count | Should -Be 1 $res.Name | Should -Be 'System.Security.AccessControl.AuthorizationRule' } + + It 'Enumerates the inferred type after *-Object commands' { + $res = [AstTypeInference]::InferTypeOf( { (([System.Management.Automation.Language.Ast]$null).FindAll() | Select-Object -First 1) }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.Management.Automation.Language.Ast' + } } Describe "AstTypeInference tests" -Tags CI {