Skip to content

Commit

Permalink
Set up logic for automatically generating interfaces for types, since…
Browse files Browse the repository at this point in the history
… I've been having trouble with my existing library I used.
  • Loading branch information
MeltyPlayer committed Nov 11, 2024
1 parent 1b521be commit eaee2ca
Show file tree
Hide file tree
Showing 8 changed files with 1,084 additions and 30 deletions.
130 changes: 130 additions & 0 deletions Schema Tests/autoInterface/ConstraintTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using NUnit.Framework;

using schema.binary;


namespace schema.autoInterface;

internal class ConstraintTests {
[Test]
[TestCase("class")]
[TestCase("class?")]
[TestCase("notnull")]
[TestCase("struct")]
[TestCase("unmanaged")]
[TestCase("System.IO.Stream")]
[TestCase("System.Collections.Generic.IEnumerable<T>")]
public void TestEachConstraintType(string constraint) {
InterfaceGeneratorTestUtil.AssertGenerated(
$$"""
using schema.autoInterface;

namespace foo.bar {
[GenerateInterface]
public partial class EachConstraint<T> where T : {{constraint}} {
public T Foo<S>(T t, S s) where S : {{constraint}} { }
}
}
""",
$$"""
namespace foo.bar {
public partial class EachConstraint<T> : IEachConstraint<T>;

#nullable enable
public interface IEachConstraint<T> where T : {{constraint}} {
public T Foo<S>(T t, S s) where S : {{constraint}};
}
}

""");
}

[Test]
public void TestCircularConstraints() {
InterfaceGeneratorTestUtil.AssertGenerated(
"""
using schema.autoInterface;

namespace build.autoInterface {
[GenerateInterface]
public partial class Circular<[KeepMutableType] TMutable, TInterface, TImpl>
where TMutable : Circular<TMutable, TInterface, TImpl>, TInterface
where TInterface : ICircular<TMutable, TInterface, TImpl> {
public TMutable Foo(TInterface other);

public TMutable Foo(in TImpl other);
}
}
""",
"""
namespace build.autoInterface {
public partial class Circular<TMutable, TInterface, TImpl> : ICircular<TMutable, TInterface, TImpl>;

#nullable enable
public interface ICircular<TMutable, TInterface, TImpl> where TMutable : Circular<TMutable, TInterface, TImpl>, TInterface where TInterface : build.autoInterface.ICircular<TMutable, TInterface, TImpl> {
public TMutable Foo(TInterface other);
public TMutable Foo(in TImpl other);
}
}

""");
}

[Test]
public void TestGenericSubConstraint() {
InterfaceGeneratorTestUtil.AssertGenerated(
"""
using schema.autoInterface;

namespace foo.bar {
[GenerateInterface]
public partial class SubConstraint<T1, T2> where T2 : T1 {
public T1 Foo<S>(S s) where S : T1 { }

public T2 Bar { get; set; }
}
}
""",
"""
namespace foo.bar {
public partial class SubConstraint<T1, T2> : ISubConstraint<T1, T2>;

#nullable enable
public interface ISubConstraint<T1, T2> where T2 : T1 {
public T1 Foo<S>(S s) where S : T1;
public T2 Bar { get; set; }
}
}

""");
}

[Test]
public void TestMultipleGenericConstraints() {
InterfaceGeneratorTestUtil.AssertGenerated(
"""
using schema.autoInterface;

namespace foo.bar {
[GenerateInterface]
public partial class SimpleAttributes<T1, T2> where T1 : notnull, struct where T2 : unmanaged {
public T1 Foo<T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4) where T3 : class where T4 : class? { }

public T2 Bar { get; set; }
}
}
""",
"""
namespace foo.bar {
public partial class SimpleAttributes<T1, T2> : ISimpleAttributes<T1, T2>;

#nullable enable
public interface ISimpleAttributes<T1, T2> where T1 : notnull, struct where T2 : unmanaged {
public T1 Foo<T3, T4>(T1 t1, T2 t2, T3 t3, T4 t4) where T3 : class where T4 : class?;
public T2 Bar { get; set; }
}
}

""");
}
}
106 changes: 106 additions & 0 deletions Schema Tests/autoInterface/DefaultValueTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using NUnit.Framework;

using schema.binary;


namespace schema.autoInterface;

internal class DefaultValueTests {
[Test]
[TestCase("null")]
[TestCase("false")]
[TestCase("true")]
public void TestSupportsDefaultBools(string boolValue) {
InterfaceGeneratorTestUtil.AssertGenerated(
$$"""
using schema.autoInterface;

namespace foo.bar {
[GenerateInterface]
public partial class Wrapper {
[Const]
public void Foo(bool? value = {{boolValue}});
}
}
""",
$$"""
namespace foo.bar {
public partial class Wrapper : IWrapper;

#nullable enable
public interface IWrapper {
public void Foo(bool? value = {{boolValue}});
}
}

""");
}

[Test]
[TestCase("null")]
[TestCase("0")]
[TestCase("-123")]
[TestCase("123")]
public void TestSupportsDefaultInts(string intValue) {
InterfaceGeneratorTestUtil.AssertGenerated(
$$"""
using schema.autoInterface;

namespace foo.bar {
[GenerateInterface]
public partial class Wrapper {
[Const]
public void Foo(int? value = {{intValue}});
}
}
""",
$$"""
namespace foo.bar {
public partial class Wrapper : IWrapper;

#nullable enable
public interface IWrapper {
public void Foo(int? value = {{intValue}});
}
}

""");
}

[Test]
[TestCase("null", "null")]
[TestCase("SomeType.FOO", "(other.SomeType) 123")]
public void
TestSupportsDefaultEnums(string enumValue, string readonlyValue) {
InterfaceGeneratorTestUtil.AssertGenerated(
$$"""
using schema.autoInterface;
using foo.bar.other;

namespace foo.bar.other {
public enum SomeType {
FOO = 123,
}
}

namespace foo.bar {
[GenerateInterface]
public partial class Wrapper {
[Const]
public void Foo(SomeType? value = {{enumValue}});
}
}
""",
$$"""
namespace foo.bar {
public partial class Wrapper : IWrapper;

#nullable enable
public interface IWrapper {
public void Foo(other.SomeType? value = {{readonlyValue}});
}
}

""");
}
}
62 changes: 62 additions & 0 deletions Schema Tests/autoInterface/InterfaceGeneratorTestUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.IO;
using System.Linq;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using NUnit.Framework;

using schema.binary;
using schema.readOnly;
using schema.util.asserts;

#pragma warning disable CS8604


namespace schema.autoInterface;

internal static class InterfaceGeneratorTestUtil {
public static void AssertGenerated(string src, params string[] expected) {
var syntaxTree = CSharpSyntaxTree.ParseText(src);
var compilation = BinarySchemaTestUtil.Compilation.Clone()
.AddSyntaxTrees(syntaxTree);

var semanticModel = compilation.GetSemanticModel(syntaxTree);

var actual = syntaxTree
.GetRoot()
.DescendantTokens()
.Where(t => t is {
Text: "GenerateInterface",
Parent.Parent: AttributeSyntax
})
.Select(t => t.Parent?.Parent as AttributeSyntax)
.Select(attributeSyntax => {
var attributeListSyntax
= Asserts.AsA<AttributeListSyntax>(
attributeSyntax.Parent);
var declarationSyntax
= Asserts.AsA<TypeDeclarationSyntax>(
attributeListSyntax.Parent);
var symbol
= semanticModel
.GetDeclaredSymbol(declarationSyntax);
var namedTypeSymbol
= symbol as INamedTypeSymbol;
return (namedTypeSymbol, declarationSyntax);
})
.Select(symbolAndSyntax
=> new AutoInterfaceTypeGenerator()
.GenerateSourceForNamedType(
symbolAndSyntax.namedTypeSymbol,
semanticModel,
symbolAndSyntax.declarationSyntax)
.ReplaceLineEndings());

CollectionAssert.AreEqual(expected, actual);
}
}
Loading

0 comments on commit eaee2ca

Please sign in to comment.