diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs
index 0297f000c6..7369607174 100644
--- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs
+++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs
@@ -62,7 +62,7 @@ namespace System.Management.Automation
///
public static IList InferTypeOf(Ast ast, PowerShell powerShell)
{
- return InferTypeOf(ast, powerShell, TypeInferenceRuntimePermissions.None);
+ return InferTypeOf(ast, powerShell, TypeInferenceRuntimePermissions.None);
}
///
@@ -91,7 +91,7 @@ namespace System.Management.Automation
try
{
context.RuntimePermissions = evalPersmissions;
- return context.InferType(ast, new TypeInferenceVisitor(context)).ToList();
+ return context.InferType(ast, new TypeInferenceVisitor(context)).Distinct(new PSTypeNameComparer()).ToList();
}
finally
{
@@ -100,6 +100,19 @@ namespace System.Management.Automation
}
}
+ class PSTypeNameComparer : IEqualityComparer
+ {
+ public bool Equals(PSTypeName x, PSTypeName y)
+ {
+ return x.Name.Equals(y.Name);
+ }
+
+ public int GetHashCode(PSTypeName obj)
+ {
+ return obj.Name.GetHashCode();
+ }
+ }
+
internal class TypeInferenceContext
{
public static readonly PSTypeName[] EmptyPSTypeNameArray = Utils.EmptyArray();
@@ -122,6 +135,9 @@ namespace System.Management.Automation
Helper = new PowerShellExecutionHelper(powerShell);
}
+ // used to infer types in script properties attached to an object,
+ // to be able to determine the type of $this in the scripts properties
+ public PSTypeName CurrentThisType { get; set; }
public TypeDefinitionAst CurrentTypeDefinitionAst { get; set; }
@@ -868,7 +884,7 @@ namespace System.Management.Automation
// foreach-object - yields the type of it's script block parameters
if (cmdletInfo.ImplementingType == typeof(ForEachObjectCommand))
{
- foreach (var foreachType in InferTypesFromForeachCommand(pseudoBinding))
+ foreach (var foreachType in InferTypesFromForeachCommand(pseudoBinding, commandAst))
{
yield return foreachType;
}
@@ -895,9 +911,32 @@ namespace System.Management.Automation
yield return new PSTypeName(typeof(CimInstance));
}
- private IEnumerable InferTypesFromForeachCommand(PseudoBindingInfo pseudoBinding)
+ private IEnumerable InferTypesFromForeachCommand(PseudoBindingInfo pseudoBinding, CommandAst commandAst)
{
AstParameterArgumentPair argument;
+ if (pseudoBinding.BoundArguments.TryGetValue("MemberName", out argument))
+ {
+ var previousPipelineElement = GetPreviousPipelineCommand(commandAst);
+ if (previousPipelineElement == null)
+ {
+ yield break;
+ }
+ foreach (var t in InferTypes(previousPipelineElement))
+ {
+ var memberName = (((AstPair)argument).Argument as StringConstantExpressionAst)?.Value;
+
+ if (memberName != null)
+ {
+ var members = _context.GetMembersByInferredType(t, false, null);
+ bool maybeWantDefaultCtor = false;
+ foreach (var type in GetTypesOfMembers(t, memberName, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst: false))
+ {
+ yield return type;
+ }
+ }
+ }
+ }
+
if (pseudoBinding.BoundArguments.TryGetValue("Begin", out argument))
{
foreach (var type in GetInferredTypeFromScriptBlockParameter(argument))
@@ -968,173 +1007,212 @@ namespace System.Management.Automation
// If the member name isn't simple, don't even try.
var memberAsStringConst = memberCommandElement as StringConstantExpressionAst;
if (memberAsStringConst == null)
- yield break;
+ return Utils.EmptyArray();
- var exprType = GetExpressionType(expression, isStatic);
+ var exprType = GetExpressionType(expression, isStatic);
if (exprType == null || exprType.Length == 0)
{
- yield break;
+ return Utils.EmptyArray();
}
-
+
+ var res = new List(10);
+ bool isInvokeMemberExpressionAst = memberExpressionAst is InvokeMemberExpressionAst;
var maybeWantDefaultCtor = isStatic
- && memberExpressionAst is InvokeMemberExpressionAst
- && memberAsStringConst.Value.EqualsOrdinalIgnoreCase("new");
+ && isInvokeMemberExpressionAst
+ && memberAsStringConst.Value.EqualsOrdinalIgnoreCase("new");
// We use a list of member names because we might discover aliases properties
// and if we do, we'll add to the list.
var memberNameList = new List { memberAsStringConst.Value };
foreach (var type in exprType)
{
+ if (type.Type == typeof(PSObject))
+ {
+ continue;
+ }
var members = _context.GetMembersByInferredType(type, isStatic, filter: null);
- for (int i = 0; i < memberNameList.Count; i++)
- {
- string memberName = memberNameList[i];
- foreach (var member in members)
- {
- var isInvokeMemberAst = memberExpressionAst is InvokeMemberExpressionAst;
- switch (member)
- {
- case PropertyInfo propertyInfo: // .net property
- {
- if (propertyInfo.Name.EqualsOrdinalIgnoreCase(memberName) && !isInvokeMemberAst)
- {
- yield return new PSTypeName(propertyInfo.PropertyType);
- goto NextMember;
- }
- continue;
- }
- case FieldInfo fieldInfo: // .net field
- {
- if (fieldInfo.Name.EqualsOrdinalIgnoreCase(memberName) && !isInvokeMemberAst)
- {
- yield return new PSTypeName(fieldInfo.FieldType);
- }
- continue;
- }
-
- case DotNetAdapter.MethodCacheEntry methodCacheEntry: // .net method
- {
- if (methodCacheEntry[0].method.Name.EqualsOrdinalIgnoreCase(memberName))
- {
- maybeWantDefaultCtor = false;
- if (isInvokeMemberAst)
- {
- foreach (var method in methodCacheEntry.methodInformationStructures)
- {
- var methodInfo = method.method as MethodInfo;
- if (methodInfo != null && !methodInfo.ReturnType.GetTypeInfo().ContainsGenericParameters)
- {
- yield return new PSTypeName(methodInfo.ReturnType);
- }
- }
- }
- else
- {
- // Accessing a method as a property, we'd return a wrapper over the method.
- yield return new PSTypeName(typeof(PSMethod));
- }
- }
- continue;
- }
- case MemberAst memberAst: // this is for members defined by PowerShell classes
- {
- if (memberAst.Name.EqualsOrdinalIgnoreCase(memberName))
- {
- if (isInvokeMemberAst)
- {
- var functionMemberAst = memberAst as FunctionMemberAst;
- if (functionMemberAst != null && !functionMemberAst.IsReturnTypeVoid())
- {
- yield return new PSTypeName(functionMemberAst.ReturnType.TypeName);
- }
- }
- else
- {
- var propertyMemberAst = memberAst as PropertyMemberAst;
- if (propertyMemberAst != null)
- {
- if (propertyMemberAst.PropertyType != null)
- {
- yield return new PSTypeName(propertyMemberAst.PropertyType.TypeName);
- }
- else
- {
- yield return new PSTypeName(typeof(object));
- }
- }
- else
- {
- // Accessing a method as a property, we'd return a wrapper over the method.
- yield return new PSTypeName(typeof(PSMethod));
- }
- }
- }
- continue;
- }
- case PSMemberInfo memberInfo:
- {
- if (!memberInfo.Name.EqualsOrdinalIgnoreCase(memberName))
- {
- continue;
- }
- switch (member)
- {
- case PSProperty p:
- {
- yield return new PSTypeName(p.Value.GetType());
- goto NextMember;
- }
- case PSNoteProperty noteProperty:
- {
- yield return new PSTypeName(noteProperty.Value.GetType());
- goto NextMember;
- }
- case PSAliasProperty aliasProperty:
- {
- memberNameList.Add(aliasProperty.ReferencedMemberName);
- goto NextMember;
- }
- case PSCodeProperty codeProperty:
- {
- if (codeProperty.GetterCodeReference != null)
- {
- yield return new PSTypeName(codeProperty.GetterCodeReference.ReturnType);
- }
- goto NextMember;
- }
- case PSScriptProperty scriptProperty:
- {
- var scriptBlock = scriptProperty.GetterScript;
- foreach (var t in scriptBlock.OutputType)
- {
- yield return t;
- }
- goto NextMember;
- }
- case PSScriptMethod scriptMethod:
- {
- var scriptBlock = scriptMethod.Script;
- foreach (var t in scriptBlock.OutputType)
- {
- yield return t;
- }
- goto NextMember;
- }
- }
- break;
- }
- }
- }
- NextMember: {}
- }
+ AddTypesOfMembers(type, memberNameList, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, res);
// We didn't find any constructors but they used [T]::new() syntax
if (maybeWantDefaultCtor)
{
- yield return type;
+ res.Add(type);
}
}
+ return res;
+ }
+
+ private List GetTypesOfMembers(PSTypeName thisType, string memberName, IList