Skip to content

Commit 294def7

Browse files
authored
Fix 'PropertyOnlyAdapter' to allow calling base methods (PowerShell#6394)
For a PropertyOnlyAdapter, the property may come from various sources, but methods, including parameterized properties, still come from DotNetAdapter. So, the binder can optimize on method calls for objects that map to a custom PropertyOnlyAdapter.
1 parent a099786 commit 294def7

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

src/System.Management.Automation/engine/CoreAdapter.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ internal abstract class Adapter
4646

4747
#region member
4848

49-
internal virtual bool SiteBinderCanOptimize { get { return false; } }
49+
internal virtual bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) { return false; }
5050

5151
protected static IEnumerable<string> GetDotNetTypeNameHierarchy(Type type)
5252
{
@@ -3519,7 +3519,7 @@ private static bool PropertyIsStatic(PSProperty property)
35193519

35203520
#region member
35213521

3522-
internal override bool SiteBinderCanOptimize { get { return true; } }
3522+
internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) { return true; }
35233523

35243524
private static ConcurrentDictionary<Type, ConsolidatedString> s_typeToTypeNameDictionary =
35253525
new ConcurrentDictionary<Type, ConsolidatedString>();
@@ -4545,7 +4545,24 @@ protected override PSMemberInfoInternalCollection<T> GetMembers<T>(object obj)
45454545
/// </summary>
45464546
internal abstract class PropertyOnlyAdapter : DotNetAdapter
45474547
{
4548-
internal override bool SiteBinderCanOptimize { get { return false; } }
4548+
/// <summary>
4549+
/// For a PropertyOnlyAdapter, the property may come from various sources,
4550+
/// but methods, including parameterized properties, still come from DotNetAdapter.
4551+
/// So, the binder can optimize on method calls for objects that map to a
4552+
/// custom PropertyOnlyAdapter.
4553+
/// </summary>
4554+
internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn)
4555+
{
4556+
switch (typeToOperateOn)
4557+
{
4558+
case MemberTypes.Property:
4559+
return false;
4560+
case MemberTypes.Method:
4561+
return true;
4562+
default:
4563+
throw new InvalidOperationException("Should be unreachable. Update code if other member types need to be handled here.");
4564+
}
4565+
}
45494566

45504567
protected override ConsolidatedString GetInternedTypeNameHierarchy(object obj)
45514568
{

src/System.Management.Automation/engine/runtime/Binding/Binders.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5053,7 +5053,7 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy
50535053

50545054
bool canOptimize;
50555055
Type aliasConversionType;
5056-
memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType);
5056+
memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property);
50575057

50585058
if (!canOptimize)
50595059
{
@@ -5415,15 +5415,16 @@ private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject targe
54155415
return null;
54165416
}
54175417

5418-
PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, aliases, aliasRestrictions);
5419-
5418+
PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType,
5419+
MemberTypes.Property, aliases, aliasRestrictions);
54205420
return result;
54215421
}
54225422

54235423
internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
54245424
out BindingRestrictions restrictions,
54255425
out bool canOptimize,
54265426
out Type aliasConversionType,
5427+
MemberTypes memberTypeToOperateOn,
54275428
HashSet<string> aliases = null,
54285429
List<BindingRestrictions> aliasRestrictions = null)
54295430
{
@@ -5493,7 +5494,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target,
54935494
var adapterSet = PSObject.GetMappedAdapter(value, typeTable);
54945495
if (memberInfo == null)
54955496
{
5496-
canOptimize = adapterSet.OriginalAdapter.SiteBinderCanOptimize;
5497+
canOptimize = adapterSet.OriginalAdapter.CanSiteBinderOptimize(memberTypeToOperateOn);
54975498
// Don't bother looking for the member if we're not going to use it.
54985499
if (canOptimize)
54995500
{
@@ -5969,7 +5970,7 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy
59695970
BindingRestrictions restrictions;
59705971
bool canOptimize;
59715972
Type aliasConversionType;
5972-
memberInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType);
5973+
memberInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property);
59735974

59745975
restrictions = restrictions.Merge(value.PSGetTypeRestriction());
59755976

@@ -6464,7 +6465,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
64646465
BindingRestrictions restrictions;
64656466
bool canOptimize;
64666467
Type aliasConversionType;
6467-
var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType) as PSMethodInfo;
6468+
var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Method) as PSMethodInfo;
64686469
restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
64696470

64706471
// If the process has ever used ConstrainedLanguage, then we need to add the language mode

test/powershell/engine/ETS/Adapter.Tests.ps1

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,49 @@ Describe "DataRow and DataRowView Adapter tests" -tags "CI" {
315315
}
316316
}
317317
}
318+
319+
Describe "Base method call on object mapped to PropertyOnlyAdapter should work" -tags "CI" {
320+
It "Base method call on object of a subclass of 'XmlDocument' -- Add-Type" {
321+
$code =@'
322+
namespace BaseMethodCallTest.OnXmlDocument {
323+
public class Foo : System.Xml.XmlDocument {
324+
public string MyName { get; set; }
325+
public override void LoadXml(string content) {
326+
MyName = content;
327+
}
328+
}
329+
}
330+
'@
331+
try {
332+
$null = [BaseMethodCallTest.OnXmlDocument.Foo]
333+
} catch {
334+
Add-Type -TypeDefinition $code
335+
}
336+
337+
$foo = [BaseMethodCallTest.OnXmlDocument.Foo]::new()
338+
$foo.LoadXml('<test>bar</test>')
339+
$foo.MyName | Should -BeExactly '<test>bar</test>'
340+
$foo.ChildNodes.Count | Should -Be 0
341+
342+
([System.Xml.XmlDocument]$foo).LoadXml('<test>bar</test>')
343+
$foo.test | Should -BeExactly 'bar'
344+
$foo.ChildNodes.Count | Should -Be 1
345+
}
346+
347+
It "Base method call on object of a subclass of 'XmlDocument' -- PowerShell Class" {
348+
class XmlDocChild : System.Xml.XmlDocument {
349+
[string] $MyName
350+
[void] LoadXml([string]$content) {
351+
$this.MyName = $content
352+
# Try to call the base type's .LoadXml() method.
353+
([System.Xml.XmlDocument] $this).LoadXml($content)
354+
}
355+
}
356+
357+
$child = [XmlDocChild]::new()
358+
$child.LoadXml('<test>bar</test>')
359+
$child.MyName | Should -BeExactly '<test>bar</test>'
360+
$child.test | Should -BeExactly 'bar'
361+
$child.ChildNodes.Count | Should -Be 1
362+
}
363+
}

0 commit comments

Comments
 (0)