From 0e4b9d74f39fb2e20562080c76fa270e6ae11d44 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Thu, 4 Apr 2024 18:22:35 -0400 Subject: [PATCH] [release/v7.4.2] Fix PowerShell class to support deriving from an abstract class with abstract properties (#21331) (#21422) --- .../engine/parser/PSType.cs | 21 ++++++--- .../scripting.Classes.inheritance.tests.ps1 | 44 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/PSType.cs b/src/System.Management.Automation/engine/parser/PSType.cs index d01a22c305..2fe7190f38 100644 --- a/src/System.Management.Automation/engine/parser/PSType.cs +++ b/src/System.Management.Automation/engine/parser/PSType.cs @@ -276,7 +276,7 @@ namespace System.Management.Automation.Language internal readonly TypeBuilder _staticHelpersTypeBuilder; private readonly Dictionary _definedProperties; private readonly Dictionary>> _definedMethods; - private HashSet> _interfaceProperties; + private HashSet> _abstractProperties; internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions; private bool _baseClassHasDefaultCtor; @@ -446,9 +446,9 @@ namespace System.Management.Automation.Language private bool ShouldImplementProperty(string name, Type type) { - if (_interfaceProperties == null) + if (_abstractProperties == null) { - _interfaceProperties = new HashSet>(); + _abstractProperties = new HashSet>(); var allInterfaces = new HashSet(); // TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor. @@ -467,12 +467,23 @@ namespace System.Management.Automation.Language { foreach (var property in interfaceType.GetProperties()) { - _interfaceProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + } + } + + if (_typeBuilder.BaseType.IsAbstract) + { + foreach (var property in _typeBuilder.BaseType.GetProperties()) + { + if (property.GetAccessors().Any(m => m.IsAbstract)) + { + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + } } } } - return _interfaceProperties.Contains(Tuple.Create(name, type)); + return _abstractProperties.Contains(Tuple.Create(name, type)); } public void DefineMembers() diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index cf304d5918..af01307602 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -628,3 +628,47 @@ class Derived : Base $sb.Invoke() | Should -Be 200 } } + +Describe 'Base type has abstract properties' -Tags "CI" { + It 'can derive from `FileSystemInfo`' { + ## FileSystemInfo has 3 abstract members that a derived type needs to implement + ## - public abstract bool Exists { get; } + ## - public abstract string Name { get; } + ## - public abstract void Delete (); + + class myFileSystemInfo : System.IO.FileSystemInfo + { + [string] $Name + [bool] $Exists + + myFileSystemInfo([string]$path) + { + # ctor + $this.Name = $path + $this.Exists = $true + } + + [void] Delete() + { + } + } + + $myFile = [myFileSystemInfo]::new('Hello') + $myFile.Name | Should -Be 'Hello' + $myFile.Exists | Should -BeTrue + } + + It 'deriving from `FileSystemInfo` will fail when the abstract property `Exists` is not implemented' { + $script = [scriptblock]::Create('class WillFail : System.IO.FileSystemInfo { [string] $Name }') + $failure = $null + try { + & $script + } catch { + $failure = $_ + } + + $failure | Should -Not -BeNullOrEmpty + $failure.FullyQualifiedErrorId | Should -BeExactly "TypeCreationError" + $failure.Exception.Message | Should -BeLike "*'get_Exists'*" + } +}