diff --git a/src/Draco.Compiler.Tests/CodeGeneration/MsilStructureTests.cs b/src/Draco.Compiler.Tests/CodeGeneration/MsilStructureTests.cs new file mode 100644 index 000000000..1f1e613aa --- /dev/null +++ b/src/Draco.Compiler.Tests/CodeGeneration/MsilStructureTests.cs @@ -0,0 +1,103 @@ +using System.Reflection; +using Draco.Compiler.Internal; + +namespace Draco.Compiler.Tests.CodeGeneration; + +public sealed class MsilStructureTests +{ + private readonly struct RelayDisposable(Action dispose) : IDisposable + { + public void Dispose() => dispose(); + } + + private const BindingFlags AllBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + + private string? CurrentNamespace => this.namespaceStack.Count == 0 + ? null + : string.Join(".", this.namespaceStack.Reverse()); + private Type CurrentType => this.typeStack.Peek(); + + private Assembly assembly = null!; + private readonly Stack<Type> typeStack = new(); + private readonly Stack<string> namespaceStack = new(); + + private void Compile(string source, string? rootModulePath = null) + { + this.assembly = TestUtilities.CompileToAssembly( + sourceCode: source, + rootModulePath: rootModulePath); + } + + private RelayDisposable Ns(string name) + { + this.namespaceStack.Push(name); + return new RelayDisposable(() => this.namespaceStack.Pop()); + } + + private RelayDisposable C(string name, Func<Type, bool> predicate) + { + var type = this.typeStack.Count == 0 + // Look up in root assembly + ? this.assembly.GetType(name) + // Look up in current type + : this.CurrentType.GetNestedType(name, AllBindingFlags); + + Assert.NotNull(type); + Assert.Equal(this.CurrentNamespace, type.Namespace); + Assert.True(predicate(type)); + this.typeStack.Push(type); + + return new RelayDisposable(() => this.typeStack.Pop()); + } + + private RelayDisposable StaticC(string name, Func<Type, bool> predicate) => + this.C(name, t => t.IsAbstract && t.IsSealed && predicate(t)); + + private void M(string name, Func<MethodInfo, bool> predicate) + { + var method = this.CurrentType.GetMethod(name, AllBindingFlags); + Assert.NotNull(method); + Assert.True(predicate(method)); + } + + [Fact] + public void HelloWorld() + { + this.Compile(""" + import System.Console; + + func main() { + WriteLine("Hello, World!"); + } + """); + + using (this.StaticC(CompilerConstants.DefaultModuleName, t => !t.IsPublic)) + { + this.M("main", m => !m.IsPublic && m.IsStatic); + } + } + + [Fact] + public void HelloWorldInModule() + { + this.Compile(""" + import System.Console; + + func main() { + WriteLine("Hello, World!"); + } + """, + rootModulePath: "foo/bar"); + + using (this.Ns("foo")) + { + using (this.StaticC("bar", t => !t.IsPublic)) + { + using (this.StaticC(CompilerConstants.DefaultModuleName, t => !t.IsPublic)) + { + this.M("main", m => !m.IsPublic && m.IsStatic); + } + } + } + } +}