From 920865cd545d1a9137b351bee54e23e25c044db3 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Thu, 18 Dec 2025 21:53:31 -0500 Subject: [PATCH] Add signature parameter to Find-Member Also add a `sig` member signature to match against a full method signature. Smudge the signature interfaces a bit to make it easier to handle in one class. --- .../Commands/FindMemberCommand.cs | 5 + src/ClassExplorer/Commands/FindTypeCommand.cs | 10 +- src/ClassExplorer/MemberSearch.cs | 13 +- src/ClassExplorer/MemberSearchOptions.cs | 2 + src/ClassExplorer/ReflectionCache.cs | 67 ++++ src/ClassExplorer/SR.resx | 3 + src/ClassExplorer/ScriptBlockStringOrType.cs | 22 +- src/ClassExplorer/SignatureWriter.cs | 11 + .../Signatures/AllOfSignature.cs | 21 +- .../Signatures/AnyOfSignature.cs | 19 +- .../Signatures/FullMethodSignature.cs | 50 +++ src/ClassExplorer/Signatures/ISignature.cs | 18 + src/ClassExplorer/Signatures/Keywords.cs | 1 + .../Signatures/MemberSignature.cs | 9 +- .../Signatures/SignatureParser.cs | 321 +++++++++++------- src/ClassExplorer/Signatures/TypeSignature.cs | 6 +- .../Signatures/UniversialSignature.cs | 8 +- src/ClassExplorer/StringMatcher.cs | 2 + src/ClassExplorer/TypeExtensions.cs | 180 ++++++---- 19 files changed, 535 insertions(+), 233 deletions(-) create mode 100644 src/ClassExplorer/ReflectionCache.cs create mode 100644 src/ClassExplorer/Signatures/FullMethodSignature.cs create mode 100644 src/ClassExplorer/Signatures/ISignature.cs diff --git a/src/ClassExplorer/Commands/FindMemberCommand.cs b/src/ClassExplorer/Commands/FindMemberCommand.cs index a4aa94e..5f00734 100644 --- a/src/ClassExplorer/Commands/FindMemberCommand.cs +++ b/src/ClassExplorer/Commands/FindMemberCommand.cs @@ -181,6 +181,10 @@ public SwitchParameter Extension set => _options.Extension = value; } + [Parameter] + [Alias("sig")] + public ScriptBlockStringOrType? Signature { get; set; } + private MemberSearch> _search = null!; private protected override void OnNoInput() @@ -212,6 +216,7 @@ protected override void InitializeFilters() _options.ResolutionMap = resolutionMap; _options.AccessView = AccessView; _options.Decoration = Decoration; + _options.Signature = Signature; _search = Search.Members(_options, new PipelineEmitter(this)); } } diff --git a/src/ClassExplorer/Commands/FindTypeCommand.cs b/src/ClassExplorer/Commands/FindTypeCommand.cs index c577917..e6b976a 100644 --- a/src/ClassExplorer/Commands/FindTypeCommand.cs +++ b/src/ClassExplorer/Commands/FindTypeCommand.cs @@ -153,24 +153,24 @@ protected override void InitializeFilters() Dictionary? resolutionMap = InitializeResolutionMap(); var parser = new SignatureParser(resolutionMap); - var signatures = ImmutableArray.CreateBuilder(); - ITypeSignature? signature = null; + var signatures = ImmutableArray.CreateBuilder(); + ISignature? signature = null; if (Signature is not null) { - signature = Signature.Resolve(parser); + signature = Signature.ResolveType(parser); signatures.Add(signature); } if (InheritsType is not null) { - ITypeSignature inheritsTypeSignature = InheritsType.Resolve(parser, excludeSelf: true); + ISignature inheritsTypeSignature = InheritsType.ResolveType(parser, excludeSelf: true); signatures.Add(inheritsTypeSignature); signature ??= inheritsTypeSignature; } if (ImplementsInterface is not null) { - ITypeSignature implementsSignature = ImplementsInterface.Resolve(parser, excludeSelf: true); + ISignature implementsSignature = ImplementsInterface.ResolveType(parser, excludeSelf: true); signatures.Add(implementsSignature); signature ??= implementsSignature; } diff --git a/src/ClassExplorer/MemberSearch.cs b/src/ClassExplorer/MemberSearch.cs index 8e0037d..6a21244 100644 --- a/src/ClassExplorer/MemberSearch.cs +++ b/src/ClassExplorer/MemberSearch.cs @@ -268,21 +268,21 @@ protected override void InitializeOtherFilters(List> filters, if (_options.GenericParameter is not null) { filters.AddFilter( - new GenericParameterTypeSignature(_options.GenericParameter.Resolve(parser)), + new GenericParameterTypeSignature(_options.GenericParameter.ResolveType(parser)), static (member, signature) => signature.IsMatch(member)); } if (_options.ParameterType is not null) { filters.AddFilter( - new ParameterTypeSignature(_options.ParameterType.Resolve(parser)), + new ParameterTypeSignature(_options.ParameterType.ResolveType(parser)), static (member, signature) => signature.IsMatch(member)); } if (_options.ReturnType is not null) { filters.AddFilter( - new ReturnTypeSignature(_options.ReturnType.Resolve(parser)), + new ReturnTypeSignature(_options.ReturnType.ResolveType(parser)), static (member, signature) => signature.IsMatch(member)); } @@ -295,5 +295,12 @@ protected override void InitializeOtherFilters(List> filters, _options.ResolutionMap)), static (member, signature) => signature.IsMatch(member)); } + + if (_options.Signature is not null) + { + filters.AddFilter( + (IMemberSignature)_options.Signature.Resolve(parser, SignatureKind.Member), + static (member, signature) => signature.IsMatch(member)); + } } } diff --git a/src/ClassExplorer/MemberSearchOptions.cs b/src/ClassExplorer/MemberSearchOptions.cs index 20ae24e..a991ed6 100644 --- a/src/ClassExplorer/MemberSearchOptions.cs +++ b/src/ClassExplorer/MemberSearchOptions.cs @@ -33,4 +33,6 @@ internal class MemberSearchOptions : ReflectionSearchOptions public bool RecurseNestedType { get; set; } public bool Extension { get; set; } + + public ScriptBlockStringOrType? Signature { get; set; } } diff --git a/src/ClassExplorer/ReflectionCache.cs b/src/ClassExplorer/ReflectionCache.cs new file mode 100644 index 0000000..19d064c --- /dev/null +++ b/src/ClassExplorer/ReflectionCache.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq.Expressions; +using System.Management.Automation; +using System.Reflection; + +namespace ClassExplorer; + +internal static class ReflectionCache +{ + private static readonly Type? ExecutionContextType; + + private static readonly MethodInfo? GetExecutionContextFromTLSMethod; + + private static readonly Func? GetUsingNamespacesFunc; + + static ReflectionCache() + { + ExecutionContextType = typeof(PSObject).Assembly.GetType("System.Management.Automation.ExecutionContext"); + if (ExecutionContextType is null) return; + + GetExecutionContextFromTLSMethod = typeof(PSObject).Assembly.GetType("System.Management.Automation.Runspaces.LocalPipeline") + ?.GetMethod("GetExecutionContextFromTLS", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (GetExecutionContextFromTLSMethod is null) return; + + GetUsingNamespacesFunc = CreateGetUsingNamespaces(); + } + + public static string[] GetUsingNamespacesFromTLS() + { + return GetUsingNamespacesFunc?.Invoke() ?? ["System"]; + } + + private static Func? CreateGetUsingNamespaces() + { + const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; + + if (GetExecutionContextFromTLSMethod is null) return null; + + PropertyInfo? getEngineSessionState = ExecutionContextType?.GetProperty("EngineSessionState", flags); + if (getEngineSessionState is null) return null; + + PropertyInfo? currentScope = getEngineSessionState.GetReturnType()?.GetProperty("CurrentScope", flags); + if (currentScope is null) return null; + + PropertyInfo? typeRes = currentScope.GetReturnType()?.GetProperty("TypeResolutionState", flags); + if (typeRes is null) return null; + + FieldInfo? namespaces = typeRes.GetReturnType()?.GetField("namespaces", flags); + if (namespaces is null) return null; + if (namespaces.FieldType != typeof(string[])) return null; + + return Expression.Lambda>( + Expression.Field( + Expression.Call( + Expression.Call( + Expression.Call( + Expression.Call(GetExecutionContextFromTLSMethod), + getEngineSessionState.GetGetMethod(nonPublic: true)), + currentScope.GetGetMethod(nonPublic: true)), + typeRes.GetGetMethod(nonPublic: true)), + namespaces), + "GetUsingNamespacesDynamically", + []) + .Compile(); + } +} diff --git a/src/ClassExplorer/SR.resx b/src/ClassExplorer/SR.resx index 7ad4477..70b4b01 100644 --- a/src/ClassExplorer/SR.resx +++ b/src/ClassExplorer/SR.resx @@ -129,6 +129,9 @@ The keyword "decoration" requires exactly one generic argument (Example: [decoration[ParameterAttribute]]). See https://seemingly.dev/about-type-signatures or help about_Type_Signatures. + + The keyword "sig" requires at least one generic argument representing the return type (Example: [sig[Arg1, Arg2, void]]). See https://seemingly.dev/about-type-signatures or help about_Type_Signatures. + Unable to find type [{0}]. diff --git a/src/ClassExplorer/ScriptBlockStringOrType.cs b/src/ClassExplorer/ScriptBlockStringOrType.cs index 0c1739f..6d46ca7 100644 --- a/src/ClassExplorer/ScriptBlockStringOrType.cs +++ b/src/ClassExplorer/ScriptBlockStringOrType.cs @@ -2,14 +2,13 @@ using System.Collections.Immutable; using System.Management.Automation; using System.Management.Automation.Language; -using System.Runtime.CompilerServices; using ClassExplorer.Signatures; namespace ClassExplorer { public sealed class ScriptBlockStringOrType { - private ITypeSignature? _cachedType; + private ISignature? _cachedType; public ScriptBlockStringOrType(string? typeName) => Value = typeName; @@ -21,7 +20,10 @@ public sealed class ScriptBlockStringOrType internal object? Value { get; } - internal ITypeSignature Resolve(SignatureParser parser, bool isForMap = false, bool excludeSelf = false) + internal ISignature ResolveType(SignatureParser parser, bool isForMap = false, bool excludeSelf = false) + => Resolve(parser, SignatureKind.Type, isForMap, excludeSelf); + + internal ISignature Resolve(SignatureParser parser, SignatureKind expected, bool isForMap = false, bool excludeSelf = false) { if (_cachedType is not null) { @@ -30,7 +32,7 @@ internal ITypeSignature Resolve(SignatureParser parser, bool isForMap = false, b if (Value is Type exactType) { - static ITypeSignature SignatureForType(Type type, bool isForMap, bool excludeSelf) + static ISignature SignatureForType(Type type, SignatureKind expected, bool isForMap, bool excludeSelf) { if (isForMap) { @@ -40,7 +42,7 @@ static ITypeSignature SignatureForType(Type type, bool isForMap, bool excludeSel if (excludeSelf) { return new AllOfTypeSignature( - ImmutableArray.Create( + ImmutableArray.Create( new AssignableTypeSignature(type), new NotTypeSignature(new ExactTypeSignature(type)))); } @@ -48,12 +50,12 @@ static ITypeSignature SignatureForType(Type type, bool isForMap, bool excludeSel return new ContainsSignature(new AssignableTypeSignature(type)); } - return _cachedType = SignatureForType(exactType, isForMap, excludeSelf); + return _cachedType = SignatureForType(exactType, expected, isForMap, excludeSelf); } if (Value is ScriptBlock scriptBlock) { - return _cachedType = parser.Parse(scriptBlock); + return _cachedType = parser.Parse(scriptBlock, expected); } if (Value is string typeName) @@ -75,7 +77,7 @@ static ScriptBlockAst GetAstForString(string typeName) } ScriptBlockAst ast = GetAstForString(typeName); - ITypeSignature signature = parser.Parse(ast); + ISignature signature = parser.Parse(ast, expected); if (isForMap) { return _cachedType = signature; @@ -87,7 +89,7 @@ static ScriptBlockAst GetAstForString(string typeName) { return _cachedType = new AllOfTypeSignature( - ImmutableArray.Create( + ImmutableArray.Create( assignable, new NotTypeSignature(new ExactTypeSignature(assignable.Type)))); } @@ -98,7 +100,7 @@ static ScriptBlockAst GetAstForString(string typeName) return _cachedType = new ContainsSignature(signature); } - return Unreachable.Code(); + return Unreachable.Code(); } } } diff --git a/src/ClassExplorer/SignatureWriter.cs b/src/ClassExplorer/SignatureWriter.cs index 66b1b2e..a64d51e 100644 --- a/src/ClassExplorer/SignatureWriter.cs +++ b/src/ClassExplorer/SignatureWriter.cs @@ -1066,6 +1066,17 @@ public SignatureWriter MaybeNewLine(ref bool isFirst) return NewLineNoIndent().NewLine(); } + public SignatureWriter WriteMember(MemberInfo member) + => member switch + { + MethodBase m => Member(m), + EventInfo m => Member(m), + PropertyInfo m => Member(m), + FieldInfo m => Member(m), + Type m => Member(m), + _ => throw new ArgumentOutOfRangeException(nameof(member)), + }; + public SignatureWriter Member(Type type) { type = type.UnwrapConstruction(); diff --git a/src/ClassExplorer/Signatures/AllOfSignature.cs b/src/ClassExplorer/Signatures/AllOfSignature.cs index 5a7f1b4..210ea6f 100644 --- a/src/ClassExplorer/Signatures/AllOfSignature.cs +++ b/src/ClassExplorer/Signatures/AllOfSignature.cs @@ -4,15 +4,15 @@ namespace ClassExplorer.Signatures { - internal sealed class AllOfTypeSignature : TypeSignature + internal sealed class AllOfTypeSignature : UniversialSignature { - internal AllOfTypeSignature(ImmutableArray elements) + internal AllOfTypeSignature(ImmutableArray elements) { Poly.Assert(!elements.IsDefaultOrEmpty); Elements = elements; } - public ImmutableArray Elements { get; } + public ImmutableArray Elements { get; } public override bool IsMatch(ParameterInfo parameter) { @@ -39,23 +39,12 @@ public override bool IsMatch(Type type) return true; } - } - - internal sealed class AllOfMemberSignature : MemberSignature - { - internal AllOfMemberSignature(ImmutableArray elements) - { - Poly.Assert(!elements.IsDefaultOrEmpty); - Elements = elements; - } - - public ImmutableArray Elements { get; } - public override bool IsMatch(MemberInfo member) + public override bool IsMatch(MemberInfo subject) { foreach (IMemberSignature signature in Elements) { - if (!signature.IsMatch(member)) + if (!signature.IsMatch(subject)) { return false; } diff --git a/src/ClassExplorer/Signatures/AnyOfSignature.cs b/src/ClassExplorer/Signatures/AnyOfSignature.cs index c39b257..b2babf8 100644 --- a/src/ClassExplorer/Signatures/AnyOfSignature.cs +++ b/src/ClassExplorer/Signatures/AnyOfSignature.cs @@ -4,15 +4,15 @@ namespace ClassExplorer.Signatures { - internal sealed class AnyOfSignature : TypeSignature + internal sealed class AnyOfSignature : UniversialSignature { - internal AnyOfSignature(ImmutableArray elements) + internal AnyOfSignature(ImmutableArray elements) { Poly.Assert(!elements.IsDefaultOrEmpty); Elements = elements; } - internal ImmutableArray Elements { get; } + internal ImmutableArray Elements { get; } public override bool IsMatch(ParameterInfo parameter) { @@ -39,5 +39,18 @@ public override bool IsMatch(Type type) return false; } + + public override bool IsMatch(MemberInfo subject) + { + foreach (IMemberSignature signature in Elements) + { + if (signature.IsMatch(subject)) + { + return true; + } + } + + return false; + } } } diff --git a/src/ClassExplorer/Signatures/FullMethodSignature.cs b/src/ClassExplorer/Signatures/FullMethodSignature.cs new file mode 100644 index 0000000..12aa7e8 --- /dev/null +++ b/src/ClassExplorer/Signatures/FullMethodSignature.cs @@ -0,0 +1,50 @@ +using System.Collections.Immutable; +using System.Reflection; + +namespace ClassExplorer.Signatures; + +internal sealed class FullMethodSignature(ITypeSignature returnType, ImmutableArray parameters) : MemberSignature +{ + internal ITypeSignature ReturnType { get; } = returnType; + + internal ImmutableArray Parameters { get; } = parameters; + + public override bool IsMatch(MemberInfo subject) + { + if (subject is not MethodBase methodBase) + { + return false; + } + + if (subject is ConstructorInfo ctor) + { + if (!ReturnType.IsMatch(ctor.ReflectedType)) + { + return false; + } + } + else if (subject is MethodInfo method) + { + if (!ReturnType.IsMatch(method.ReturnParameter)) + { + return false; + } + } + + ParameterInfo[] parameters = methodBase.GetParameters(); + if (parameters.Length != Parameters.Length) + { + return false; + } + + for (int i = 0; i < parameters.Length; i++) + { + if (!Parameters[i].IsMatch(parameters[i])) + { + return false; + } + } + + return true; + } +} diff --git a/src/ClassExplorer/Signatures/ISignature.cs b/src/ClassExplorer/Signatures/ISignature.cs new file mode 100644 index 0000000..38260bf --- /dev/null +++ b/src/ClassExplorer/Signatures/ISignature.cs @@ -0,0 +1,18 @@ +using System; +using System.Reflection; + +namespace ClassExplorer.Signatures; + +internal interface ISignature : ITypeSignature, IMemberSignature +{ + SignatureKind SignatureKind { get; } +} + +[Flags] +internal enum SignatureKind +{ + Invalid = 0, + Type = 1 << 0, + Member = 1 << 1, + Any = Type | Member, +} diff --git a/src/ClassExplorer/Signatures/Keywords.cs b/src/ClassExplorer/Signatures/Keywords.cs index 8d8c5d1..f2ec880 100644 --- a/src/ClassExplorer/Signatures/Keywords.cs +++ b/src/ClassExplorer/Signatures/Keywords.cs @@ -39,5 +39,6 @@ internal static class Keywords public const string hasattr = "hasattr"; public const string number = "number"; public const string hasdefault = "hasdefault"; + public const string sig = "sig"; } } diff --git a/src/ClassExplorer/Signatures/MemberSignature.cs b/src/ClassExplorer/Signatures/MemberSignature.cs index 1e3c359..8d5b0b9 100644 --- a/src/ClassExplorer/Signatures/MemberSignature.cs +++ b/src/ClassExplorer/Signatures/MemberSignature.cs @@ -1,9 +1,16 @@ +using System; using System.Reflection; namespace ClassExplorer.Signatures { - internal abstract class MemberSignature : IMemberSignature + internal abstract class MemberSignature : ISignature { + public SignatureKind SignatureKind => SignatureKind.Member; + public abstract bool IsMatch(MemberInfo subject); + + bool ITypeSignature.IsMatch(ParameterInfo parameter) => false; + + bool ITypeSignature.IsMatch(Type subject) => false; } } diff --git a/src/ClassExplorer/Signatures/SignatureParser.cs b/src/ClassExplorer/Signatures/SignatureParser.cs index c37fb46..e6c831b 100644 --- a/src/ClassExplorer/Signatures/SignatureParser.cs +++ b/src/ClassExplorer/Signatures/SignatureParser.cs @@ -19,7 +19,7 @@ internal sealed class SignatureParser private readonly Dictionary _resolutionMap; - private readonly Lazy _namespaces = new(static () => GetUsingNamespaces()); + private readonly Lazy _namespaces = new(static () => ReflectionCache.GetUsingNamespacesFromTLS()); private string[] Namespaces => _namespaces.Value; @@ -28,9 +28,18 @@ internal SignatureParser(Dictionary? resolution _resolutionMap = resolutionMap ?? new(); } - public static ITypeSignature Parse(ScriptBlock signature, Dictionary? resolutionMap) + public static ISignature ParseType(ScriptBlock signature, Dictionary? resolutionMap) + => Parse(signature, SignatureKind.Type, resolutionMap); + + public static ISignature ParseMember(ScriptBlock signature, Dictionary? resolutionMap) + => Parse(signature, SignatureKind.Member, resolutionMap); + + public static ISignature Parse( + ScriptBlock signature, + SignatureKind expected, + Dictionary? resolutionMap) { - return new SignatureParser(resolutionMap).Parse(signature); + return new SignatureParser(resolutionMap).Parse(signature, expected); } private static ExpressionAst GetFirstExpression(ScriptBlock signature) @@ -79,17 +88,30 @@ private static ExpressionAst GetFirstExpression(ScriptBlockAst ast) return commandExpression.Expression; } - internal ITypeSignature Parse(ScriptBlock signature) - { - return Parse(GetFirstExpression(signature)); - } + internal ISignature Parse(ScriptBlock signature, SignatureKind expected) => Parse(GetFirstExpression(signature), expected); - internal ITypeSignature Parse(ScriptBlockAst signature) + internal ISignature Parse(ScriptBlockAst signature, SignatureKind expected) => Parse(GetFirstExpression(signature), expected); + + private ISignature EnsureExpected(ISignature signature, SignatureKind expected, Ast errorPosition) + => EnsureExpected(signature, expected, errorPosition.Extent); + + private ISignature EnsureExpected(ISignature signature, SignatureKind expected, ITypeName errorPosition) + => EnsureExpected(signature, expected, errorPosition.Extent); + + private ISignature EnsureExpected(ISignature signature, SignatureKind expected, IScriptExtent errorPosition) { - return Parse(GetFirstExpression(signature)); + if ((signature.SignatureKind & expected) is 0) + { + return ThrowUnexpectedSignatureKind(expected, signature.SignatureKind, errorPosition); + } + + return signature; } - private ITypeSignature Parse(ExpressionAst expression) + private ISignature ParseType(ExpressionAst expression) + => Parse(expression, SignatureKind.Type); + + private ISignature Parse(ExpressionAst expression, SignatureKind expected = SignatureKind.Any) { if (expression is ConvertExpressionAst convert) { @@ -97,10 +119,10 @@ private ITypeSignature Parse(ExpressionAst expression) string asLower = typeName.Name.ToLowerInvariant(); return asLower switch { - Keywords.@ref => new RefSignature(RefKind.Ref, Parse(convert.Child)), - Keywords.@out => new RefSignature(RefKind.Out, Parse(convert.Child)), - Keywords.@in => new RefSignature(RefKind.In, Parse(convert.Child)), - Keywords.anyref => new RefSignature(RefKind.AnyRef, Parse(convert.Child)), + Keywords.@ref => EnsureExpected(new RefSignature(RefKind.Ref, ParseType(convert.Child)), expected, typeName), + Keywords.@out => EnsureExpected(new RefSignature(RefKind.Out, ParseType(convert.Child)), expected, typeName), + Keywords.@in => EnsureExpected(new RefSignature(RefKind.In, ParseType(convert.Child)), expected, typeName), + Keywords.anyref => EnsureExpected(new RefSignature(RefKind.AnyRef, ParseType(convert.Child)), expected, typeName), _ => ThrowSignatureParseException( convert.Type.TypeName.Extent, SR.ConvertMustBeRef), @@ -109,25 +131,27 @@ private ITypeSignature Parse(ExpressionAst expression) if (expression is TypeExpressionAst typeExpression) { - return Parse(typeExpression.TypeName); + return Parse(typeExpression.TypeName, expected); } return ThrowExpectedTypeExpression(expression.Extent); } [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)] - private static ITypeSignature ThrowExpectedTypeExpression(IScriptExtent extent) + private static ISignature ThrowExpectedTypeExpression(IScriptExtent extent) { throw new SignatureParseException( SR.NotTypeExpression, extent); } - public ITypeSignature Parse(ITypeName typeName) + public ISignature ParseType(ITypeName typeName) => Parse(typeName, SignatureKind.Type); + + public ISignature Parse(ITypeName typeName, SignatureKind expected = SignatureKind.Any) { if (typeName is ArrayTypeName array) { - return new ArraySignature(Parse(array.ElementType)); + return EnsureExpected(new ArraySignature(ParseType(array.ElementType)), expected, typeName); } GetDefinitionAndArgs( @@ -135,24 +159,29 @@ public ITypeSignature Parse(ITypeName typeName) out TypeName definition, out ReadOnlyCollection genericArguments); - ITypeSignature signature = ParseDefinition( + ISignature signature = ParseDefinition( definition, genericArguments, + expected, out bool argsConsumed); if (argsConsumed || genericArguments.Count is 0) { - return signature; + return EnsureExpected(signature, expected, definition); } - return new GenericTypeSignature( - signature, - Parse(genericArguments, definition)); + return EnsureExpected( + new GenericTypeSignature( + signature, + ParseTypes(genericArguments, definition)), + expected, + typeName); } - public ITypeSignature ParseDefinition( + public ISignature ParseDefinition( TypeName typeName, ReadOnlyCollection args, + SignatureKind expected, out bool argsConsumed) { string nameAsLower = typeName.Name.ToLowerInvariant(); @@ -167,77 +196,87 @@ public ITypeSignature ParseDefinition( if (i is > 0) { argsConsumed = false; - var newTypeName = new TypeName( + TypeName newTypeName = new( typeName.Extent, typeName.Name.Substring(0, typeName.Name.Length - i)); if (IsGenericParameter(newTypeName, out kind, out position)) { argsConsumed = true; - return new PointerSignature( - CreateGenericSignature(kind, position, args), - new RangeExpression(i)); + return EnsureExpected( + new PointerSignature( + CreateGenericSignature(kind, position, args), + new RangeExpression(i)), + expected, + typeName); } - return new PointerSignature(Parse(newTypeName), new RangeExpression(i)); + return EnsureExpected( + new PointerSignature(ParseType(newTypeName), new RangeExpression(i)), + expected, + typeName); } if (_resolutionMap.TryGetValue(typeName.Name, out ScriptBlockStringOrType? input)) { argsConsumed = false; - return input.Resolve(this, isForMap: true); + return EnsureExpected(input.Resolve(this, expected, isForMap: true), expected, typeName); } if (IsGenericParameter(typeName, out kind, out position)) { argsConsumed = true; - return CreateGenericSignature(kind, position, args); + return EnsureExpected(CreateGenericSignature(kind, position, args), expected, typeName); } if (IsIndex(nameAsLower.AsSpan(), out RangeExpression? indexRange)) { argsConsumed = false; - return new ParameterIndexSignature(indexRange); + return EnsureExpected(new ParameterIndexSignature(indexRange), expected, typeName); } argsConsumed = false; - return nameAsLower switch - { - Keywords.exact => Consume(new ExactTypeSignature(SingleType(Keywords.exact, typeName, args)), out argsConsumed), - Keywords.assignable => Consume(new AssignableTypeSignature(SingleType(Keywords.assignable, typeName, args)), out argsConsumed), - Keywords.contains => Consume(new ContainsSignature(ParseSingle(args, typeName)), out argsConsumed), - Keywords.@ref => Consume(new RefSignature(RefKind.Ref, ParseSingle(args, typeName)), out argsConsumed), - Keywords.anyref => Consume(new RefSignature(RefKind.AnyRef, ParseSingle(args, typeName)), out argsConsumed), - Keywords.@out => Consume(new RefSignature(RefKind.Out, ParseSingle(args, typeName)), out argsConsumed), - Keywords.@in => Consume(new RefSignature(RefKind.In, ParseSingle(args, typeName)), out argsConsumed), - Keywords.anyof => Consume(new AnyOfSignature(Parse(args, typeName)), out argsConsumed), - Keywords.allof => Consume(new AllOfTypeSignature(Parse(args, typeName)), out argsConsumed), - Keywords.not => Consume(new NotTypeSignature(ParseSingle(args, typeName)), out argsConsumed), - Keywords.@class => new TypeClassification(ClassificationKind.Class), - Keywords.@struct => new TypeClassification(ClassificationKind.Struct), - Keywords.record => new TypeClassification(ClassificationKind.Record), - Keywords.recordclass => new TypeClassification(ClassificationKind.Record | ClassificationKind.Class), - Keywords.recordstruct => new TypeClassification(ClassificationKind.Record | ClassificationKind.Struct), - Keywords.readonlyclass => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Class), - Keywords.readonlystruct => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Struct), - Keywords.readonlyrefstruct => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Ref | ClassificationKind.Struct), - Keywords.refstruct => new TypeClassification(ClassificationKind.Ref | ClassificationKind.Struct), - Keywords.@enum => new TypeClassification(ClassificationKind.Enum), - Keywords.referencetype => new TypeClassification(ClassificationKind.ReferenceType), - Keywords.@interface => new TypeClassification(ClassificationKind.Interface), - Keywords.@abstract => new TypeClassification(ClassificationKind.Abstract), - Keywords.concrete => new TypeClassification(ClassificationKind.Concrete), - Keywords.primitive => new TypeClassification(ClassificationKind.Primitive), - Keywords.any => new AnySignature(), - Keywords.generic => Consume(ParseGeneric(args, typeName), out argsConsumed), - Keywords.hasdefault => new HasDefaultSignature(), - Keywords.number => new NumberTypeSignature(), - Keywords.decoration or Keywords.hasattr => Consume(Decoration(args, typeName), out argsConsumed), - Keywords.pointer => Consume(ParsePointer(args, typeName), out argsConsumed), - _ => Default(typeName, args), - }; - - ITypeSignature Decoration(ReadOnlyCollection args, ITypeName errorPosition) + return EnsureExpected( + nameAsLower switch + { + Keywords.exact => Consume(new ExactTypeSignature(SingleType(Keywords.exact, typeName, args)), out argsConsumed), + Keywords.assignable => Consume(new AssignableTypeSignature(SingleType(Keywords.assignable, typeName, args)), out argsConsumed), + Keywords.contains => Consume(new ContainsSignature(ParseSingle(args, typeName)), out argsConsumed), + Keywords.@ref => Consume(new RefSignature(RefKind.Ref, ParseSingle(args, typeName)), out argsConsumed), + Keywords.anyref => Consume(new RefSignature(RefKind.AnyRef, ParseSingle(args, typeName)), out argsConsumed), + Keywords.@out => Consume(new RefSignature(RefKind.Out, ParseSingle(args, typeName)), out argsConsumed), + Keywords.@in => Consume(new RefSignature(RefKind.In, ParseSingle(args, typeName)), out argsConsumed), + Keywords.anyof => Consume(new AnyOfSignature(Parse(args, typeName)), out argsConsumed), + Keywords.allof => Consume(new AllOfTypeSignature(Parse(args, typeName)), out argsConsumed), + Keywords.not => Consume(new NotTypeSignature(ParseSingle(args, typeName)), out argsConsumed), + Keywords.@class => new TypeClassification(ClassificationKind.Class), + Keywords.@struct => new TypeClassification(ClassificationKind.Struct), + Keywords.record => new TypeClassification(ClassificationKind.Record), + Keywords.recordclass => new TypeClassification(ClassificationKind.Record | ClassificationKind.Class), + Keywords.recordstruct => new TypeClassification(ClassificationKind.Record | ClassificationKind.Struct), + Keywords.readonlyclass => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Class), + Keywords.readonlystruct => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Struct), + Keywords.readonlyrefstruct => new TypeClassification(ClassificationKind.ReadOnly | ClassificationKind.Ref | ClassificationKind.Struct), + Keywords.refstruct => new TypeClassification(ClassificationKind.Ref | ClassificationKind.Struct), + Keywords.@enum => new TypeClassification(ClassificationKind.Enum), + Keywords.referencetype => new TypeClassification(ClassificationKind.ReferenceType), + Keywords.@interface => new TypeClassification(ClassificationKind.Interface), + Keywords.@abstract => new TypeClassification(ClassificationKind.Abstract), + Keywords.concrete => new TypeClassification(ClassificationKind.Concrete), + Keywords.primitive => new TypeClassification(ClassificationKind.Primitive), + Keywords.any => new AnySignature(), + Keywords.generic => Consume(ParseGeneric(args, typeName), out argsConsumed), + Keywords.hasdefault => new HasDefaultSignature(), + Keywords.number => new NumberTypeSignature(), + Keywords.decoration or Keywords.hasattr => Consume(Decoration(args, typeName), out argsConsumed), + Keywords.pointer => Consume(ParsePointer(args, typeName), out argsConsumed), + Keywords.sig => Consume(ParseFullMethodSignature(args, typeName), out argsConsumed), + _ => Default(typeName, args), + }, + expected, + typeName); + + ISignature Decoration(ReadOnlyCollection args, ITypeName errorPosition) { if (args is not { Count: 1 }) { @@ -262,7 +301,7 @@ ITypeSignature Decoration(ReadOnlyCollection args, ITypeName errorPos return new DecorationSignature(name); } - ITypeSignature Default(TypeName typeName, ReadOnlyCollection args) + ISignature Default(TypeName typeName, ReadOnlyCollection args) { ReadOnlySpan name = typeName.Name.AsSpan(); @@ -304,14 +343,33 @@ ITypeSignature Default(TypeName typeName, ReadOnlyCollection args) return new AssignableTypeSignature(ResolveReflectionType(typeName, args.Count)); } - static ITypeSignature Consume(ITypeSignature signature, out bool argsConsumed) + static ISignature Consume(ISignature signature, out bool argsConsumed) { argsConsumed = true; return signature; } } - private ITypeSignature ParseGeneric(ReadOnlyCollection args, ITypeName errorPosition) + private ISignature ParseFullMethodSignature(ReadOnlyCollection args, ITypeName errorPosition) + { + if (args.Count is 0) + { + return ThrowSignatureParseException( + errorPosition.Extent, + SR.SigBadArgs); + } + + ISignature[] parameters = new ISignature[args.Count - 1]; + for (int i = args.Count - 2; i >= 0; i--) + { + parameters[i] = ParseType(args[i]); + } + + ISignature returnType = ParseType(args[args.Count - 1]); + return new FullMethodSignature(returnType, Unsafe.As>(ref parameters)); + } + + private ISignature ParseGeneric(ReadOnlyCollection args, ITypeName errorPosition) { if (args.Count is not 2) { @@ -337,7 +395,7 @@ private ITypeSignature ParseGeneric(ReadOnlyCollection args, ITypeNam return new GenericTypeSignature(definition, argSignatures.MoveToImmutable()); } - private ITypeSignature ParsePointer(ReadOnlyCollection args, ITypeName errorPosition) + private ISignature ParsePointer(ReadOnlyCollection args, ITypeName errorPosition) { if (args.Count is 1) { @@ -391,9 +449,58 @@ private ITypeSignature ParseSingle(ReadOnlyCollection args, ITypeName return Parse(args[0]); } - private ImmutableArray Parse( + private ImmutableArray ParseTypes( + ReadOnlyCollection args, + ITypeName errorPosition, + int assertAtLeast = 1, + int assertAtMax = -1) + { + ISignature[] signatures = ParseImpl( + args, + errorPosition, + SignatureKind.Type, + assertAtLeast, + assertAtMax); + + return Unsafe.As>(ref signatures); + } + + private ImmutableArray ParseMembers( + ReadOnlyCollection args, + ITypeName errorPosition, + int assertAtLeast = 1, + int assertAtMax = -1) + { + ISignature[] signatures = ParseImpl( + args, + errorPosition, + SignatureKind.Member, + assertAtLeast, + assertAtMax); + + return Unsafe.As>(ref signatures); + } + + private ImmutableArray Parse( + ReadOnlyCollection args, + ITypeName errorPosition, + int assertAtLeast = 1, + int assertAtMax = -1) + { + ISignature[] signatures = ParseImpl( + args, + errorPosition, + SignatureKind.Any, + assertAtLeast, + assertAtMax); + + return Unsafe.As>(ref signatures); + } + + private ISignature[] ParseImpl( ReadOnlyCollection args, ITypeName errorPosition, + SignatureKind expected = SignatureKind.Any, int assertAtLeast = 1, int assertAtMax = -1) { @@ -411,13 +518,22 @@ private ImmutableArray Parse( SR.Format(SR.NeedsLessArgs, errorPosition.Name, assertAtMax)); } - var builder = ImmutableArray.CreateBuilder(args.Count); - foreach (ITypeName typeName in args) + ISignature[] signatures = new ISignature[args.Count]; + for (int i = 0; i < args.Count; i++) { - builder.Add(Parse(typeName)); + ITypeName typeName = args[i]; + signatures[i] = EnsureExpected(Parse(typeName), expected, typeName.Extent); } - return builder.MoveToImmutable(); + return signatures; + } + + [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)] + private ISignature ThrowUnexpectedSignatureKind(SignatureKind expected, SignatureKind actual, IScriptExtent errorPosition) + { + throw new SignatureParseException( + SR.Format(SR.UnexpectedSignatureKind, expected, actual), + errorPosition); } private Type SingleType(string name, TypeName subject, ReadOnlyCollection genericArgs) @@ -572,51 +688,6 @@ private Type ResolveReflectionType( goto retry; } - private static string[] GetUsingNamespaces() - { - EngineIntrinsics? engine; - - using (var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - engine = pwsh.AddScript("$ExecutionContext").Invoke().FirstOrDefault(); - } - - if (engine is null) - { - return s_defaultUsing; - } - - const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; - object? ssi = engine.SessionState.GetType() - .GetProperty("Internal", flags) - ?.GetValue(engine.SessionState); - - if (ssi is null) return s_defaultUsing; - - object? currentScope = ssi.GetType() - .GetProperty("CurrentScope", flags) - ?.GetValue(ssi); - - if (currentScope is null) return s_defaultUsing; - - object? typeResolutionState = currentScope.GetType() - .GetProperty("TypeResolutionState", flags) - ?.GetValue(currentScope); - - if (typeResolutionState is null) return s_defaultUsing; - - object? namespaces = typeResolutionState.GetType() - .GetField("namespaces", flags) - ?.GetValue(typeResolutionState); - - if (namespaces is string[] result) - { - return result; - } - - return s_defaultUsing; - } - private void GetDefinitionAndArgs(ITypeName name, out TypeName definition, out ReadOnlyCollection genericArgs) { if (name is TypeName typeName) @@ -639,7 +710,7 @@ private void GetDefinitionAndArgs(ITypeName name, out TypeName definition, out R } [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)] - private static ITypeSignature ThrowSignatureParseException(IScriptExtent extent, string message) + private static ISignature ThrowSignatureParseException(IScriptExtent extent, string message) { throw new SignatureParseException(message, extent); } @@ -709,7 +780,7 @@ private static bool IsGenericParameter(TypeName typeName, out GenericParameterKi return false; } - private ITypeSignature CreateGenericSignature( + private ISignature CreateGenericSignature( GenericParameterKind kind, int position, ReadOnlyCollection args) diff --git a/src/ClassExplorer/Signatures/TypeSignature.cs b/src/ClassExplorer/Signatures/TypeSignature.cs index 4d8992c..f67db47 100644 --- a/src/ClassExplorer/Signatures/TypeSignature.cs +++ b/src/ClassExplorer/Signatures/TypeSignature.cs @@ -3,10 +3,14 @@ namespace ClassExplorer.Signatures { - internal abstract class TypeSignature : ITypeSignature + internal abstract class TypeSignature : ISignature { + public SignatureKind SignatureKind => SignatureKind.Type; + public virtual bool IsMatch(ParameterInfo parameter) => IsMatch(parameter.ParameterType); public abstract bool IsMatch(Type type); + + bool IMemberSignature.IsMatch(MemberInfo subject) => false; } } diff --git a/src/ClassExplorer/Signatures/UniversialSignature.cs b/src/ClassExplorer/Signatures/UniversialSignature.cs index dbca7ac..93b420a 100644 --- a/src/ClassExplorer/Signatures/UniversialSignature.cs +++ b/src/ClassExplorer/Signatures/UniversialSignature.cs @@ -3,13 +3,15 @@ namespace ClassExplorer.Signatures { - internal abstract class UniversialSignature : ITypeSignature, IMemberSignature + internal abstract class UniversialSignature : ISignature { + public SignatureKind SignatureKind => SignatureKind.Any; + public abstract bool IsMatch(MemberInfo subject); - public virtual bool IsMatch(ParameterInfo parameter) => IsMatch(parameter.ParameterType); + public virtual bool IsMatch(ParameterInfo parameter) => IsMatch((MemberInfo)parameter.ParameterType); - bool ITypeSignature.IsMatch(Type subject) => IsMatch(subject); + public virtual bool IsMatch(Type subject) => IsMatch((MemberInfo)subject); bool IMemberSignature.IsMatch(MemberInfo subject) => IsMatch(subject); } diff --git a/src/ClassExplorer/StringMatcher.cs b/src/ClassExplorer/StringMatcher.cs index bd7224f..bf5a4e9 100644 --- a/src/ClassExplorer/StringMatcher.cs +++ b/src/ClassExplorer/StringMatcher.cs @@ -57,6 +57,8 @@ public unsafe bool IsMatch(string? input) { // There's a solid chance this isn't actually faster than just branching // here, I'll test one day. + // + // 2025 edit: yeah dude of course branching would be faster this is silly lol return _matcher(input, _expected, _pattern); } diff --git a/src/ClassExplorer/TypeExtensions.cs b/src/ClassExplorer/TypeExtensions.cs index 7557f8c..0bd4ff8 100644 --- a/src/ClassExplorer/TypeExtensions.cs +++ b/src/ClassExplorer/TypeExtensions.cs @@ -3,11 +3,13 @@ using System.Reflection; using System.Runtime.CompilerServices; -namespace ClassExplorer +namespace ClassExplorer; + +internal static class TypeExtensions { - internal static class TypeExtensions + extension(MemberInfo member) { - public static bool IsVirtualOrAbstract(this MemberInfo member) + public bool IsVirtualOrAbstract() { return member switch { @@ -18,10 +20,7 @@ public static bool IsVirtualOrAbstract(this MemberInfo member) }; } - public static bool IsVirtualOrAbstract(this MethodInfo? method) - => method is { IsFinal: false } and ({ IsVirtual: true } or { IsAbstract: true }); - - public static bool IsVirtual(this MemberInfo member) + public bool IsVirtual() { return member switch { @@ -32,7 +31,7 @@ public static bool IsVirtual(this MemberInfo member) }; } - public static bool IsAbstract(this MemberInfo member) + public bool IsAbstract() { return member switch { @@ -43,7 +42,7 @@ public static bool IsAbstract(this MemberInfo member) }; } - public static bool DoesMatchView(this MemberInfo member, AccessView view) + public bool DoesMatchView(AccessView view) { return member switch { @@ -56,61 +55,61 @@ public static bool DoesMatchView(this MemberInfo member, AccessView view) }; } - public static bool DoesMatchView(this Type type, AccessView view) + public Type? GetReturnType() => member switch { - return type switch - { - _ when type.IsPublic => (view & AccessView.Public) is not 0, - _ when type.IsNestedPublic - => (view & AccessView.Public) is not 0 - && type.ReflectedType?.DoesMatchView(view) is true, - _ when !type.IsNested => (view & AccessView.Internal) is not 0, - _ when view is AccessView.This => true, - _ when type.IsNestedPrivate => (view & AccessView.Private) is not 0, - _ when type.IsNestedAssembly => (view & AccessView.Internal) is not 0, - _ when type.IsNestedFamily => (view & AccessView.Protected) is not 0, - _ when type.IsNestedFamANDAssem - => (view & AccessView.Protected) is not 0 && (view & AccessView.Internal) is not 0, - _ when type.IsNestedFamORAssem - => (view & AccessView.Protected) is not 0 || (view & AccessView.Internal) is not 0, - _ => Unreachable.Code(), - }; - - static bool IsPublic(Type type) - { - if (type.IsPublic) - { - return true; - } + MethodInfo m => m.ReturnType, + ConstructorInfo m => m.ReflectedType, + FieldInfo m => m.FieldType, + PropertyInfo m => m.GetGetMethod(true)?.ReturnType, + _ => null, + }; + } - if (!type.IsNestedPublic) - { - return false; - } + extension(MethodInfo? method) + { + public bool IsVirtualOrAbstract() => method is { IsFinal: false } and ({ IsVirtual: true } or { IsAbstract: true }); - if (type.ReflectedType is null) - { - // Unreachable? - return true; - } + public T? CreateDelegatePoly() where T : Delegate => (T?)method?.CreateDelegate(typeof(T)); + } - return IsPublic(type.ReflectedType); - } - } + extension(MethodInfo method) + { + public Type GetReturnType() => method.ReturnType; + } - public static bool DoesMatchView(this PropertyInfo property, AccessView view) + extension(PropertyInfo property) + { + public bool DoesMatchView(AccessView view) { MethodInfo? method = property.GetFirstMethod(); return method is not null && DoesMatchView(method, view); } - public static bool DoesMatchView(this EventInfo eventInfo, AccessView view) + public MethodInfo? GetFirstMethod() + { + return property.GetGetMethod(nonPublic: true) ?? property.GetSetMethod(nonPublic: true); + } + + public Type? GetReturnType() => property.GetGetMethod(true)?.ReturnType; + } + + extension(EventInfo eventInfo) + { + public bool DoesMatchView(AccessView view) { MethodInfo? method = eventInfo.GetFirstMethod(); return method is not null && DoesMatchView(method, view); } - public static bool DoesMatchView(this MethodBase method, AccessView view) + public MethodInfo? GetFirstMethod() + { + return eventInfo.GetAddMethod(nonPublic: true) ?? eventInfo.GetRemoveMethod(nonPublic: true); + } + } + + extension(MethodBase method) + { + public bool DoesMatchView(AccessView view) { return method switch { @@ -126,8 +125,11 @@ _ when method.IsFamilyOrAssembly _ => Unreachable.Code(), }; } + } - public static bool DoesMatchView(this FieldInfo field, AccessView view) + extension(FieldInfo field) + { + public bool DoesMatchView(AccessView view) { return field switch { @@ -144,17 +146,54 @@ _ when field.IsFamilyOrAssembly }; } - public static MethodInfo? GetFirstMethod(this PropertyInfo property) - { - return property.GetGetMethod(nonPublic: true) ?? property.GetSetMethod(nonPublic: true); - } + public Type GetReturnType() => field.FieldType; + } - public static MethodInfo? GetFirstMethod(this EventInfo eventInfo) + extension(Type type) + { + public bool DoesMatchView(AccessView view) { - return eventInfo.GetAddMethod(nonPublic: true) ?? eventInfo.GetRemoveMethod(nonPublic: true); + return type switch + { + _ when type.IsPublic => (view & AccessView.Public) is not 0, + _ when type.IsNestedPublic + => (view & AccessView.Public) is not 0 + && type.ReflectedType?.DoesMatchView(view) is true, + _ when !type.IsNested => (view & AccessView.Internal) is not 0, + _ when view is AccessView.This => true, + _ when type.IsNestedPrivate => (view & AccessView.Private) is not 0, + _ when type.IsNestedAssembly => (view & AccessView.Internal) is not 0, + _ when type.IsNestedFamily => (view & AccessView.Protected) is not 0, + _ when type.IsNestedFamANDAssem + => (view & AccessView.Protected) is not 0 && (view & AccessView.Internal) is not 0, + _ when type.IsNestedFamORAssem + => (view & AccessView.Protected) is not 0 || (view & AccessView.Internal) is not 0, + _ => Unreachable.Code(), + }; + + static bool IsPublic(Type type) + { + if (type.IsPublic) + { + return true; + } + + if (!type.IsNestedPublic) + { + return false; + } + + if (type.ReflectedType is null) + { + // Unreachable? + return true; + } + + return IsPublic(type.ReflectedType); + } } - public static Type UnwrapConstruction(this Type type) + public Type UnwrapConstruction() { if (type.IsConstructedGenericType) { @@ -163,8 +202,11 @@ public static Type UnwrapConstruction(this Type type) return type; } + } - public static bool IsExactly(this string left, string? right) + extension(string left) + { + public bool IsExactly(string? right) { if (right is null) { @@ -173,23 +215,29 @@ public static bool IsExactly(this string left, string? right) return left.Equals(right, StringComparison.Ordinal); } + } - public static T? GetIndexOrNull(this IList instance, int index) + extension(IList instance) + { + public T? GetIndexOrNull(int index) { - if (index <=instance.Count) + if (index <= instance.Count) { return default; } return instance[index]; } + } - public static T? As(this object instance) where T : class + extension(object instance) + { + public T? As() where T : class { return instance as T; } - public static T? TryUnbox(this object instance) where T : struct + public T? TryUnbox() where T : struct { if (instance.GetType() == typeof(T)) { @@ -198,10 +246,10 @@ public static bool IsExactly(this string left, string? right) return null; } + } - private class RawData - { - public byte Data; - } + private class RawData + { + public byte Data; } }