Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 111 additions & 52 deletions src/System.Management.Automation/engine/parser/PSType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,42 @@ internal static void DefineCustomAttributes(EnumBuilder member, ReadOnlyCollecti
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IISResetMe, your last commit had 4 failures in PowerShell-CI-linux
Error occurred in test script '/home/vsts/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /home/vsts/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/home/vsts/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /home/vsts/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/home/vsts/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /home/vsts/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/home/vsts/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /home/vsts/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IISResetMe, your last commit had 4 failures in PowerShell-CI-macos
Error occurred in test script '/Users/runner/runners/2.169.1/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /Users/runner/runners/2.169.1/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/Users/runner/runners/2.169.1/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /Users/runner/runners/2.169.1/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/Users/runner/runners/2.169.1/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /Users/runner/runners/2.169.1/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Error occurred in test script '/Users/runner/runners/2.169.1/work/1/s/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, /Users/runner/runners/2.169.1/work/1/a/bins/publish/Modules/Pester/4.10.1/Pester.psm1: line 1111

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IISResetMe, your last commit had 4 failures in PowerShell-CI-windows
Error occurred in test script 'D:\a\1\s\test\powershell\Language\Classes\scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, D:\a\1\s\src\powershell-win-core\bin\Release\net5.0\win7-x64\publish\Modules\Pester\4.10.1\Pester.psm1: line 1111

Error occurred in test script 'D:\a\1\s\test\powershell\Language\Classes\scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, D:\a\1\s\src\powershell-win-core\bin\Release\net5.0\win7-x64\publish\Modules\Pester\4.10.1\Pester.psm1: line 1111

Error occurred in test script 'D:\a\1\s\test\powershell\Language\Classes\scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, D:\a\1\s\src\powershell-win-core\bin\Release\net5.0\win7-x64\publish\Modules\Pester\4.10.1\Pester.psm1: line 1111

Error occurred in test script 'D:\a\1\s\test\powershell\Language\Classes\scripting.Classes.inheritance.tests.ps1'

Object reference not set to an instance of an object.
at <ScriptBlock>, D:\a\1\s\src\powershell-win-core\bin\Release\net5.0\win7-x64\publish\Modules\Pester\4.10.1\Pester.psm1: line 1111

}

private class InterfaceExpression
{
private TypeConstraintAst ast;

internal bool IsGeneric => ast?.TypeName.IsGeneric ?? false;

internal InterfaceExpression(TypeConstraintAst ast)
{
this.ast = ast;
}

internal Type ResolveConcreteInterfaceType(TypeBuilder parameter)
=> IsGeneric ? ResolveConcreteInterfaceTypeArguments(ast.TypeName, parameter) : ast.TypeName.GetReflectionType();

private Type ResolveConcreteInterfaceTypeArguments(ITypeName typeName, TypeBuilder parameter)
{
var typeArgs = new List<Type>();
if (typeName.IsGeneric && typeName is GenericTypeName genericName)
{
foreach (var typeArg in genericName.GenericArguments)
{
typeArgs.Add(ResolveConcreteInterfaceTypeArguments(typeArg, parameter));
}

return genericName.TypeName.GetReflectionType().MakeGenericType(typeArgs.ToArray());
}

if (parameter.FullName == typeName.FullName)
{
return parameter;
}

return typeName.GetReflectionType();
}
}

private class DefineTypeHelper
{
private readonly Parser _parser;
Expand All @@ -276,7 +312,7 @@ private class DefineTypeHelper
internal readonly TypeBuilder _staticHelpersTypeBuilder;
private readonly Dictionary<string, PropertyMemberAst> _definedProperties;
private readonly Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>> _definedMethods;
private HashSet<Tuple<string, Type>> _interfaceProperties;
private Dictionary<string, Type> _interfaceProperties;
internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions;
private bool _baseClassHasDefaultCtor;

Expand All @@ -292,10 +328,15 @@ public DefineTypeHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst t
_parser = parser;
_typeDefinitionAst = typeDefinitionAst;

List<Type> interfaces;
List<InterfaceExpression> interfaces;
var baseClass = this.GetBaseTypes(parser, typeDefinitionAst, out interfaces);

_typeBuilder = module.DefineType(typeName, Reflection.TypeAttributes.Class | Reflection.TypeAttributes.Public, baseClass, interfaces.ToArray());
_typeBuilder = module.DefineType(typeName, Reflection.TypeAttributes.Class | Reflection.TypeAttributes.Public, baseClass, null);
foreach (var interfaceExpression in interfaces)
{
_typeBuilder.AddInterfaceImplementation(interfaceExpression.ResolveConcreteInterfaceType(_typeBuilder));
}

_staticHelpersTypeBuilder = module.DefineType(string.Format(CultureInfo.InvariantCulture, "{0}_<staticHelpers>", typeName), Reflection.TypeAttributes.Class);
DefineCustomAttributes(_typeBuilder, typeDefinitionAst.Attributes, _parser, AttributeTargets.Class);
_typeDefinitionAst.Type = _typeBuilder;
Expand All @@ -314,12 +355,31 @@ public DefineTypeHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst t
/// <param name="parser"></param>
/// <param name="typeDefinitionAst"></param>
/// <param name="interfaces">Return declared interfaces.</param>
/// <returns></returns>
private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, out List<Type> interfaces)
/// <returns>The base type</returns>
private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, out List<InterfaceExpression> interfaces)
{
// Define base types and report errors.
Type baseClass = null;
interfaces = new List<Type>();
interfaces = new List<InterfaceExpression>();

bool TryGetInterface(TypeConstraintAst ast, out InterfaceExpression interfaceExpression)
{
interfaceExpression = new InterfaceExpression(ast);
if (ast.TypeName.IsGeneric && ast.TypeName is GenericTypeName genericTypeName)
{
if (genericTypeName.TypeName.GetReflectionType().IsInterface)
{
return true;
}
}
else if (ast.TypeName.GetReflectionType()?.IsInterface ?? false)
{
return true;
}

interfaceExpression = null;
return false;
}

// Default base class is System.Object and it has a default ctor.
_baseClassHasDefaultCtor = true;
Expand All @@ -339,40 +399,43 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou
}
else
{
baseClass = firstBaseTypeAst.TypeName.GetReflectionType();
if (baseClass == null)
if (TryGetInterface(firstBaseTypeAst, out InterfaceExpression interfaceExpression))
{
parser.ReportError(firstBaseTypeAst.Extent,
nameof(ParserStrings.TypeNotFound),
ParserStrings.TypeNotFound,
firstBaseTypeAst.TypeName.FullName);
// fall to the default base type
// First Ast can represent interface as well as BaseClass.
interfaces.Add(interfaceExpression);
baseClass = null;
}
else
{
if (baseClass.IsSealed)
baseClass = firstBaseTypeAst.TypeName.GetReflectionType();
if (baseClass == null)
{
parser.ReportError(firstBaseTypeAst.Extent,
nameof(ParserStrings.SealedBaseClass),
ParserStrings.SealedBaseClass,
baseClass.Name);
// ignore base type if it's sealed.
baseClass = null;
}
else if (baseClass.IsGenericType && !baseClass.IsConstructedGenericType)
{
parser.ReportError(firstBaseTypeAst.Extent,
nameof(ParserStrings.SubtypeUnclosedGeneric),
ParserStrings.SubtypeUnclosedGeneric,
baseClass.Name);
// ignore base type, we cannot inherit from unclosed generic.
baseClass = null;
nameof(ParserStrings.TypeNotFound),
ParserStrings.TypeNotFound,
firstBaseTypeAst.TypeName.FullName);
// fall to the default base type
}
else if (baseClass.IsInterface)
else
{
// First Ast can represent interface as well as BaseClass.
interfaces.Add(baseClass);
baseClass = null;
if (baseClass.IsSealed)
{
parser.ReportError(firstBaseTypeAst.Extent,
nameof(ParserStrings.SealedBaseClass),
ParserStrings.SealedBaseClass,
baseClass.Name);
// ignore base type if it's sealed.
baseClass = null;
}
else if (baseClass.IsGenericType && !baseClass.IsConstructedGenericType)
{
parser.ReportError(firstBaseTypeAst.Extent,
nameof(ParserStrings.SubtypeUnclosedGeneric),
ParserStrings.SubtypeUnclosedGeneric,
baseClass.Name);
// ignore base type, we cannot inherit from unclosed generic.
baseClass = null;
}
}
}
}
Expand Down Expand Up @@ -416,26 +479,16 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou
else
{
Type interfaceType = baseTypeAsts[i].TypeName.GetReflectionType();
if (interfaceType == null)
if (!TryGetInterface(baseTypeAsts[i], out InterfaceExpression interfaceExpression))
{
parser.ReportError(baseTypeAsts[i].Extent,
nameof(ParserStrings.TypeNotFound),
ParserStrings.TypeNotFound,
baseTypeAsts[i].TypeName.FullName);
nameof(ParserStrings.InterfaceNameExpected),
ParserStrings.InterfaceNameExpected,
interfaceType.Name);
}
else
{
if (interfaceType.IsInterface)
{
interfaces.Add(interfaceType);
}
else
{
parser.ReportError(baseTypeAsts[i].Extent,
nameof(ParserStrings.InterfaceNameExpected),
ParserStrings.InterfaceNameExpected,
interfaceType.Name);
}
interfaces.Add(interfaceExpression);
}
}
}
Expand All @@ -448,31 +501,37 @@ private bool ShouldImplementProperty(string name, Type type)
{
if (_interfaceProperties == null)
{
_interfaceProperties = new HashSet<Tuple<string, Type>>();
_interfaceProperties = new Dictionary<string, Type>();
var allInterfaces = new HashSet<Type>();

// TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor.
// During compilation the interface hierarchy is flattened, so we only need to resolve one level of ancestral interfaces.
foreach (var interfaceType in _typeBuilder.GetInterfaces())
{
foreach (var parentInterface in interfaceType.GetInterfaces())
var typeDefinition = interfaceType;
if (interfaceType.IsGenericType && interfaceType.GenericTypeArguments.Contains(_typeBuilder))
{
typeDefinition = interfaceType.GetGenericTypeDefinition();
}

foreach (var parentInterface in typeDefinition.GetInterfaces())
{
allInterfaces.Add(parentInterface);
}

allInterfaces.Add(interfaceType);
allInterfaces.Add(typeDefinition);
}

foreach (var interfaceType in allInterfaces)
{
foreach (var property in interfaceType.GetProperties())
{
_interfaceProperties.Add(Tuple.Create(property.Name, property.PropertyType));
_interfaceProperties.Add(property.Name, property.PropertyType);
}
}
}

return _interfaceProperties.Contains(Tuple.Create(name, type));
return _interfaceProperties.TryGetValue(name, out Type returnType) && (returnType == type || returnType.IsGenericParameter);
}

public void DefineMembers()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ Describe 'Classes inheritance syntax' -Tags "CI" {
{ [A]::b = "bla" } | Should -Throw -ErrorId 'ExceptionWhenSetting'
}

It 'can implement generic interfaces referencing itself as a type parameter' {
$C1 = Invoke-Expression 'class ComparableClass : IComparable[ComparableClass] { [int]$Value; [int]CompareTo([ComparableClass]$obj){ return $this.Value.CompareTo($obj.Value) } } [ComparableClass]'
$C1.ImplementedInterfaces[0].TypeParameter | Should -Be $C1
}

Context "Inheritance from abstract .NET classes" {
BeforeAll {
class TestHost : System.Management.Automation.Host.PSHost
Expand Down