From 699b3d741a8e5da61d58e427f22acadc8cfa63be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:13:15 +0000 Subject: [PATCH 01/12] Initial plan From fd7d70425a88f0f867300a637dd629ea957ebab4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:20:23 +0000 Subject: [PATCH 02/12] Add unit tests for ParsedType, FuncType, and Unification Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/FuncTypeTest.java | 119 ++++++++++ .../typeclasses/impl/ParsedTypeTest.java | 133 +++++++++++ .../typeclasses/impl/UnificationTest.java | 206 ++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java create mode 100644 src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java create mode 100644 src/test/java/com/garciat/typeclasses/impl/UnificationTest.java diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java new file mode 100644 index 0000000..8b550cf --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -0,0 +1,119 @@ +package com.garciat.typeclasses.impl; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Method; +import java.util.List; +import org.junit.jupiter.api.Test; + +final class FuncTypeTest { + @Test + void parseSimpleStaticMethod() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("simple"); + FuncType result = FuncType.parse(method); + + assertEquals(0, result.paramTypes().size()); + assertEquals(new ParsedType.Const(String.class), result.returnType()); + assertEquals(method, result.java()); + } + + @Test + void parseMethodWithParameters() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); + FuncType result = FuncType.parse(method); + + assertEquals(2, result.paramTypes().size()); + assertEquals(new ParsedType.Const(Integer.class), result.paramTypes().get(0)); + assertEquals(new ParsedType.Const(String.class), result.paramTypes().get(1)); + assertEquals(new ParsedType.Const(Boolean.class), result.returnType()); + } + + @Test + void parseMethodWithGenericReturn() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("genericReturn"); + FuncType result = FuncType.parse(method); + + assertEquals(0, result.paramTypes().size()); + assertEquals( + new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ), + result.returnType() + ); + } + + @Test + void parseMethodWithGenericParams() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("genericParams", List.class); + FuncType result = FuncType.parse(method); + + assertEquals(1, result.paramTypes().size()); + assertEquals( + new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(Integer.class) + ), + result.paramTypes().get(0) + ); + } + + @Test + void parseMethodWithPrimitives() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("withPrimitives", int.class, boolean.class); + FuncType result = FuncType.parse(method); + + assertEquals(2, result.paramTypes().size()); + assertEquals(new ParsedType.Primitive(int.class), result.paramTypes().get(0)); + assertEquals(new ParsedType.Primitive(boolean.class), result.paramTypes().get(1)); + assertEquals(new ParsedType.Primitive(void.class), result.returnType()); + } + + @Test + void parseNonStaticMethodThrows() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("nonStatic"); + assertThrows(IllegalArgumentException.class, () -> FuncType.parse(method)); + } + + @Test + void formatNoParams() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("simple"); + FuncType funcType = FuncType.parse(method); + + String formatted = funcType.format(); + assertTrue(formatted.contains("() -> String"), "Format should show no params and return type"); + } + + @Test + void formatWithParams() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); + FuncType funcType = FuncType.parse(method); + + String formatted = funcType.format(); + assertTrue(formatted.contains("Integer, String"), "Format should show param types"); + assertTrue(formatted.contains("Boolean"), "Format should show return type"); + } + + // Test helper class with various method signatures + private static class TestMethods { + public static String simple() { + return ""; + } + + public static Boolean withParams(Integer i, String s) { + return true; + } + + public static List genericReturn() { + return List.of(); + } + + public static void genericParams(List list) {} + + public static void withPrimitives(int i, boolean b) {} + + public String nonStatic() { + return ""; + } + } +} diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java new file mode 100644 index 0000000..72117a4 --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -0,0 +1,133 @@ +package com.garciat.typeclasses.impl; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +final class ParsedTypeTest { + @Test + void parseClass() { + ParsedType result = ParsedType.parse(String.class); + assertEquals(new ParsedType.Const(String.class), result); + } + + @Test + void parsePrimitiveType() { + ParsedType result = ParsedType.parse(int.class); + assertEquals(new ParsedType.Primitive(int.class), result); + } + + @Test + void parseArrayType() { + ParsedType result = ParsedType.parse(int[].class); + assertEquals( + new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)), + result); + } + + @Test + void parseObjectArrayType() { + ParsedType result = ParsedType.parse(String[].class); + assertEquals( + new ParsedType.ArrayOf(new ParsedType.Const(String.class)), + result); + } + + @Test + void parseParameterizedType() throws Exception { + Type listType = new TypeToken>() {}.type(); + ParsedType result = ParsedType.parse(listType); + + ParsedType expected = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ); + assertEquals(expected, result); + } + + @Test + void parseMultipleTypeParameters() throws Exception { + Type mapType = new TypeToken>() {}.type(); + ParsedType result = ParsedType.parse(mapType); + + ParsedType expected = new ParsedType.App( + new ParsedType.App( + new ParsedType.Const(Map.class), + new ParsedType.Const(String.class) + ), + new ParsedType.Const(Integer.class) + ); + assertEquals(expected, result); + } + + @Test + void parseNestedParameterizedType() throws Exception { + Type nestedType = new TypeToken>>() {}.type(); + ParsedType result = ParsedType.parse(nestedType); + + ParsedType expected = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.App( + new ParsedType.Const(Optional.class), + new ParsedType.Const(String.class) + ) + ); + assertEquals(expected, result); + } + + @Test + void formatConst() { + ParsedType type = new ParsedType.Const(String.class); + assertEquals("String", type.format()); + } + + @Test + void formatApp() { + ParsedType type = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ); + assertEquals("List[E](String)", type.format()); + } + + @Test + void formatArrayOf() { + ParsedType type = new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)); + assertEquals("int[]", type.format()); + } + + @Test + void formatPrimitive() { + ParsedType type = new ParsedType.Primitive(int.class); + assertEquals("int", type.format()); + } + + @Test + void parseAll() throws Exception { + Type[] types = {String.class, Integer.class, int.class}; + List result = ParsedType.parseAll(types); + + List expected = List.of( + new ParsedType.Const(String.class), + new ParsedType.Const(Integer.class), + new ParsedType.Primitive(int.class) + ); + assertEquals(expected, result); + } + + // Helper class to capture generic type information + private abstract static class TypeToken { + Type type() { + Type superclass = getClass().getGenericSuperclass(); + if (superclass instanceof ParameterizedType pt) { + return pt.getActualTypeArguments()[0]; + } + throw new IllegalStateException("TypeToken requires type parameter"); + } + } +} diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java new file mode 100644 index 0000000..4edc269 --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -0,0 +1,206 @@ +package com.garciat.typeclasses.impl; + +import static org.junit.jupiter.api.Assertions.*; + +import com.garciat.typeclasses.types.Maybe; +import java.lang.reflect.TypeVariable; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +final class UnificationTest { + @Test + void unifyEqualConsts() { + ParsedType t1 = new ParsedType.Const(String.class); + ParsedType t2 = new ParsedType.Const(String.class); + + Maybe> result = Unification.unify(t1, t2); + + assertTrue(result instanceof Maybe.Just); + assertEquals(Map.of(), ((Maybe.Just>) result).value()); + } + + @Test + void unifyDifferentConsts() { + ParsedType t1 = new ParsedType.Const(String.class); + ParsedType t2 = new ParsedType.Const(Integer.class); + + Maybe> result = Unification.unify(t1, t2); + + assertTrue(result instanceof Maybe.Nothing); + } + + @Test + void unifyVarWithConst() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType t1 = new ParsedType.Var(tv); + ParsedType t2 = new ParsedType.Const(String.class); + + Maybe> result = Unification.unify(t1, t2); + + assertTrue(result instanceof Maybe.Just); + Map map = ((Maybe.Just>) result).value(); + assertEquals(1, map.size()); + assertEquals(t2, map.get(t1)); + } + + @Test + void unifyVarWithPrimitiveFails() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType t1 = new ParsedType.Var(tv); + ParsedType t2 = new ParsedType.Primitive(int.class); + + Maybe> result = Unification.unify(t1, t2); + + assertTrue(result instanceof Maybe.Nothing); + } + + @Test + void unifyApps() { + ParsedType list1 = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ); + ParsedType list2 = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ); + + Maybe> result = Unification.unify(list1, list2); + + assertTrue(result instanceof Maybe.Just); + assertEquals(Map.of(), ((Maybe.Just>) result).value()); + } + + @Test + void unifyAppsDifferentArgs() { + ParsedType list1 = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(String.class) + ); + ParsedType list2 = new ParsedType.App( + new ParsedType.Const(List.class), + new ParsedType.Const(Integer.class) + ); + + Maybe> result = Unification.unify(list1, list2); + + assertTrue(result instanceof Maybe.Nothing); + } + + @Test + void unifyArrays() { + ParsedType arr1 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); + ParsedType arr2 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); + + Maybe> result = Unification.unify(arr1, arr2); + + assertTrue(result instanceof Maybe.Just); + assertEquals(Map.of(), ((Maybe.Just>) result).value()); + } + + @Test + void unifyPrimitives() { + ParsedType p1 = new ParsedType.Primitive(int.class); + ParsedType p2 = new ParsedType.Primitive(int.class); + + Maybe> result = Unification.unify(p1, p2); + + assertTrue(result instanceof Maybe.Just); + assertEquals(Map.of(), ((Maybe.Just>) result).value()); + } + + @Test + void unifyDifferentPrimitives() { + ParsedType p1 = new ParsedType.Primitive(int.class); + ParsedType p2 = new ParsedType.Primitive(boolean.class); + + Maybe> result = Unification.unify(p1, p2); + + assertTrue(result instanceof Maybe.Nothing); + } + + @Test + void substituteVar() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + ParsedType replacement = new ParsedType.Const(String.class); + Map map = Map.of(var, replacement); + + ParsedType result = Unification.substitute(map, var); + + assertEquals(replacement, result); + } + + @Test + void substituteConst() { + ParsedType type = new ParsedType.Const(String.class); + Map map = Map.of(); + + ParsedType result = Unification.substitute(map, type); + + assertEquals(type, result); + } + + @Test + void substituteApp() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + ParsedType app = new ParsedType.App( + new ParsedType.Const(List.class), + var + ); + ParsedType replacement = new ParsedType.Const(String.class); + Map map = Map.of(var, replacement); + + ParsedType result = Unification.substitute(map, app); + + ParsedType expected = new ParsedType.App( + new ParsedType.Const(List.class), + replacement + ); + assertEquals(expected, result); + } + + @Test + void substituteArrayOf() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + ParsedType arrayType = new ParsedType.ArrayOf(var); + ParsedType replacement = new ParsedType.Const(String.class); + Map map = Map.of(var, replacement); + + ParsedType result = Unification.substitute(map, arrayType); + + ParsedType expected = new ParsedType.ArrayOf(replacement); + assertEquals(expected, result); + } + + @Test + void substituteAll() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + List types = List.of( + var, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), var) + ); + ParsedType replacement = new ParsedType.Const(String.class); + Map map = Map.of(var, replacement); + + List result = Unification.substituteAll(map, types); + + List expected = List.of( + replacement, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), replacement) + ); + assertEquals(expected, result); + } + + // Helper to get a type variable for testing + private TypeVariable getTypeVariable() throws Exception { + class TestClass {} + return TestClass.class.getTypeParameters()[0]; + } +} From 8514093e58a154dcc9491a7b3141fcda3f9a2ba0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:24:16 +0000 Subject: [PATCH 03/12] Add comprehensive tests for TypeClasses.witness() behavior Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../garciat/typeclasses/TypeClassesTest.java | 257 ++++++++++++++++++ .../testclasses/AmbiguousShow.java | 19 ++ .../typeclasses/testclasses/OverlapShow.java | 23 ++ 3 files changed, 299 insertions(+) create mode 100644 src/test/java/com/garciat/typeclasses/TypeClassesTest.java create mode 100644 src/test/java/com/garciat/typeclasses/testclasses/AmbiguousShow.java create mode 100644 src/test/java/com/garciat/typeclasses/testclasses/OverlapShow.java diff --git a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java new file mode 100644 index 0000000..7a7ae0d --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java @@ -0,0 +1,257 @@ +package com.garciat.typeclasses; + +import static com.garciat.typeclasses.TypeClasses.witness; +import static org.junit.jupiter.api.Assertions.*; + +import com.garciat.typeclasses.api.Ctx; +import com.garciat.typeclasses.api.Ty; +import com.garciat.typeclasses.classes.Eq; +import com.garciat.typeclasses.classes.Show; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +final class TypeClassesTest { + + // ============================================ + // Basic witness resolution tests + // ============================================ + + @Test + void witnessSimpleTypeClass() { + Show showString = witness(new Ty<>() {}); + assertNotNull(showString); + assertEquals("\"test\"", showString.show("test")); + } + + @Test + void witnessWithDependency() { + Show> showOptional = witness(new Ty<>() {}); + assertNotNull(showOptional); + assertEquals("Some(\"test\")", showOptional.show(Optional.of("test"))); + assertEquals("None", showOptional.show(Optional.empty())); + } + + @Test + void witnessWithMultipleDependencies() { + Eq> eqList = witness(new Ty<>() {}); + assertNotNull(eqList); + assertTrue(eqList.eq(List.of("a", "b"), List.of("a", "b"))); + assertFalse(eqList.eq(List.of("a", "b"), List.of("a", "c"))); + } + + // ============================================ + // Witness constructor lookup tests + // ============================================ + + @Test + void witnessLookupsInTypeClass() { + // Lookup should find witnesses in the type class interface (Show) + Show show = witness(new Ty<>() {}); + assertNotNull(show); + } + + @Test + void witnessLookupsInTypeArguments() { + // Lookup should find witnesses in type arguments (String has witness in Show) + Show> show = witness(new Ty<>() {}); + assertNotNull(show); + } + + // ============================================ + // Public static @TypeClass.Witness annotation tests + // ============================================ + + @Test + void witnessRequiresPublicStaticMethod() { + // This should work - witness is public static + Show show = witness(new Ty<>() {}); + assertNotNull(show); + } + + @Test + void witnessNotFoundForUnannotatedTypes() { + // NoWitnessType has no @TypeClass.Witness methods + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); + } + + // ============================================ + // Recursive dependency resolution tests + // ============================================ + + @Test + void witnessRecursiveDependencies() { + // List> requires: + // 1. listShow(Show>) + // 2. optionalShow(Show) + // 3. stringShow() + Show>> show = witness(new Ty<>() {}); + assertNotNull(show); + assertEquals("[Some(\"a\"), None, Some(\"b\")]", + show.show(List.of(Optional.of("a"), Optional.empty(), Optional.of("b")))); + } + + @Test + void witnessDeepRecursion() { + // List>> should resolve recursively + Show>>> show = witness(new Ty<>() {}); + assertNotNull(show); + } + + // ============================================ + // Overlapping instances tests + // ============================================ + + @Test + void overlappingInstancesMoreSpecificWins() { + // When we have both general and specific instances, + // the more specific (overlapping) one should win + com.garciat.typeclasses.testclasses.OverlapShow show = witness(new Ty<>() {}); + assertNotNull(show); + // Should use the specific Integer instance + assertEquals("Integer: 42", show.show(42)); + } + + @Test + void overlappableInstanceCanBeOverridden() { + // OVERLAPPABLE instances can be overridden by more specific ones + com.garciat.typeclasses.testclasses.OverlapShow show = witness(new Ty<>() {}); + assertNotNull(show); + assertEquals("String: test", show.show("test")); + } + + // ============================================ + // Ambiguity detection tests + // ============================================ + + @Test + void ambiguousWitnessesThrow() { + // AmbiguousShow has two witness constructors without overlap markers + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); + } + + // ============================================ + // Not found error tests + // ============================================ + + @Test + void witnessNotFoundThrows() { + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); + } + + @Test + void witnessNotFoundNestedThrows() { + // List - the dependency Show cannot be found + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>>() {})); + } + + // ============================================ + // Context/witness summoning tests + // ============================================ + + @Test + void witnessSummoningWithContext() { + // Provide a custom witness via context + CustomType customValue = new CustomType("test"); + Show customShow = c -> "custom:" + c.value; + + Show> listShow = + witness(new Ty<>() {}, new Ctx<>(customShow) {}); + + assertNotNull(listShow); + assertEquals("[custom:a, custom:b]", + listShow.show(List.of(new CustomType("a"), new CustomType("b")))); + } + + @Test + void witnessSummoningBuildsTree() { + // Verify that the witness is actually constructed correctly + // by checking its behavior with nested types + Show>> show = witness(new Ty<>() {}); + + assertEquals("Some([\"a\", \"b\", \"c\"])", show.show(Optional.of(List.of("a", "b", "c")))); + assertEquals("None", show.show(Optional.empty())); + } + + // ============================================ + // WitnessResolutionException tests + // ============================================ + + @Test + void witnessResolutionExceptionHasMessage() { + TypeClasses.WitnessResolutionException ex = assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); + + assertNotNull(ex.getMessage()); + assertTrue(ex.getMessage().contains("NoWitnessType"), + "Error message should mention the type that failed: " + ex.getMessage()); + } + + @Test + void witnessResolutionExceptionForAmbiguous() { + TypeClasses.WitnessResolutionException ex = assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); + + assertNotNull(ex.getMessage()); + assertTrue(ex.getMessage().toLowerCase().contains("ambiguous"), + "Error message should mention ambiguity: " + ex.getMessage()); + } + + @Test + void witnessMapWithDependencies() { + // Map requires Eq and Eq + Eq> eqMap = witness(new Ty<>() {}); + assertNotNull(eqMap); + + Map map1 = Map.of("a", 1, "b", 2); + Map map2 = Map.of("a", 1, "b", 2); + Map map3 = Map.of("a", 1, "b", 3); + + assertTrue(eqMap.eq(map1, map2)); + assertFalse(eqMap.eq(map1, map3)); + } + + @Test + void witnessArrays() { + // Integer arrays should have a Show witness + Show showArray = witness(new Ty<>() {}); + assertNotNull(showArray); + + assertEquals("[1, 2, 3]", showArray.show(new Integer[]{1, 2, 3})); + } + + @Test + void witnessPrimitiveArrays() { + // Primitive int arrays should have a Show witness + Show showIntArray = witness(new Ty<>() {}); + assertNotNull(showIntArray); + + assertEquals("[1, 2, 3]", showIntArray.show(new int[]{1, 2, 3})); + } + + // ============================================ + // Test helper classes + // ============================================ + + static class NoWitnessType { + String value; + } + + static class CustomType { + String value; + CustomType(String value) { + this.value = value; + } + } +} diff --git a/src/test/java/com/garciat/typeclasses/testclasses/AmbiguousShow.java b/src/test/java/com/garciat/typeclasses/testclasses/AmbiguousShow.java new file mode 100644 index 0000000..06a39b3 --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/testclasses/AmbiguousShow.java @@ -0,0 +1,19 @@ +package com.garciat.typeclasses.testclasses; + +import com.garciat.typeclasses.api.TypeClass; + +@TypeClass +public interface AmbiguousShow { + String show(A a); + + // Two witnesses without overlap markers - should cause ambiguity + @TypeClass.Witness + static AmbiguousShow witness1() { + return a -> "witness1"; + } + + @TypeClass.Witness + static AmbiguousShow witness2() { + return a -> "witness2"; + } +} diff --git a/src/test/java/com/garciat/typeclasses/testclasses/OverlapShow.java b/src/test/java/com/garciat/typeclasses/testclasses/OverlapShow.java new file mode 100644 index 0000000..a35248e --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/testclasses/OverlapShow.java @@ -0,0 +1,23 @@ +package com.garciat.typeclasses.testclasses; + +import com.garciat.typeclasses.api.TypeClass; + +@TypeClass +public interface OverlapShow { + String show(A a); + + @TypeClass.Witness(overlap = TypeClass.Witness.Overlap.OVERLAPPABLE) + static OverlapShow genericShow() { + return a -> "Generic: " + a.toString(); + } + + @TypeClass.Witness(overlap = TypeClass.Witness.Overlap.OVERLAPPING) + static OverlapShow integerShow() { + return i -> "Integer: " + i; + } + + @TypeClass.Witness(overlap = TypeClass.Witness.Overlap.OVERLAPPING) + static OverlapShow stringShow() { + return s -> "String: " + s; + } +} From 8891085d02a6a777d55e32dc25f762629e478fd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:25:24 +0000 Subject: [PATCH 04/12] Format test files with google-java-format Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../garciat/typeclasses/TypeClassesTest.java | 35 +++--- .../typeclasses/impl/FuncTypeTest.java | 20 +--- .../typeclasses/impl/ParsedTypeTest.java | 57 ++++----- .../typeclasses/impl/UnificationTest.java | 113 ++++++++---------- 4 files changed, 98 insertions(+), 127 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java index 7a7ae0d..e2dfb04 100644 --- a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java +++ b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java @@ -90,7 +90,8 @@ void witnessRecursiveDependencies() { // 3. stringShow() Show>> show = witness(new Ty<>() {}); assertNotNull(show); - assertEquals("[Some(\"a\"), None, Some(\"b\")]", + assertEquals( + "[Some(\"a\"), None, Some(\"b\")]", show.show(List.of(Optional.of("a"), Optional.empty(), Optional.of("b")))); } @@ -164,12 +165,11 @@ void witnessSummoningWithContext() { CustomType customValue = new CustomType("test"); Show customShow = c -> "custom:" + c.value; - Show> listShow = - witness(new Ty<>() {}, new Ctx<>(customShow) {}); + Show> listShow = witness(new Ty<>() {}, new Ctx<>(customShow) {}); assertNotNull(listShow); - assertEquals("[custom:a, custom:b]", - listShow.show(List.of(new CustomType("a"), new CustomType("b")))); + assertEquals( + "[custom:a, custom:b]", listShow.show(List.of(new CustomType("a"), new CustomType("b")))); } @Test @@ -188,23 +188,27 @@ void witnessSummoningBuildsTree() { @Test void witnessResolutionExceptionHasMessage() { - TypeClasses.WitnessResolutionException ex = assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + TypeClasses.WitnessResolutionException ex = + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); assertNotNull(ex.getMessage()); - assertTrue(ex.getMessage().contains("NoWitnessType"), + assertTrue( + ex.getMessage().contains("NoWitnessType"), "Error message should mention the type that failed: " + ex.getMessage()); } @Test void witnessResolutionExceptionForAmbiguous() { - TypeClasses.WitnessResolutionException ex = assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + TypeClasses.WitnessResolutionException ex = + assertThrows( + TypeClasses.WitnessResolutionException.class, + () -> witness(new Ty>() {})); assertNotNull(ex.getMessage()); - assertTrue(ex.getMessage().toLowerCase().contains("ambiguous"), + assertTrue( + ex.getMessage().toLowerCase().contains("ambiguous"), "Error message should mention ambiguity: " + ex.getMessage()); } @@ -228,7 +232,7 @@ void witnessArrays() { Show showArray = witness(new Ty<>() {}); assertNotNull(showArray); - assertEquals("[1, 2, 3]", showArray.show(new Integer[]{1, 2, 3})); + assertEquals("[1, 2, 3]", showArray.show(new Integer[] {1, 2, 3})); } @Test @@ -237,7 +241,7 @@ void witnessPrimitiveArrays() { Show showIntArray = witness(new Ty<>() {}); assertNotNull(showIntArray); - assertEquals("[1, 2, 3]", showIntArray.show(new int[]{1, 2, 3})); + assertEquals("[1, 2, 3]", showIntArray.show(new int[] {1, 2, 3})); } // ============================================ @@ -250,6 +254,7 @@ static class NoWitnessType { static class CustomType { String value; + CustomType(String value) { this.value = value; } diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java index 8b550cf..14f9842 100644 --- a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -35,12 +35,8 @@ void parseMethodWithGenericReturn() throws Exception { assertEquals(0, result.paramTypes().size()); assertEquals( - new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ), - result.returnType() - ); + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)), + result.returnType()); } @Test @@ -50,12 +46,8 @@ void parseMethodWithGenericParams() throws Exception { assertEquals(1, result.paramTypes().size()); assertEquals( - new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(Integer.class) - ), - result.paramTypes().get(0) - ); + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(Integer.class)), + result.paramTypes().get(0)); } @Test @@ -79,7 +71,7 @@ void parseNonStaticMethodThrows() throws Exception { void formatNoParams() throws Exception { Method method = TestMethods.class.getDeclaredMethod("simple"); FuncType funcType = FuncType.parse(method); - + String formatted = funcType.format(); assertTrue(formatted.contains("() -> String"), "Format should show no params and return type"); } @@ -88,7 +80,7 @@ void formatNoParams() throws Exception { void formatWithParams() throws Exception { Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); FuncType funcType = FuncType.parse(method); - + String formatted = funcType.format(); assertTrue(formatted.contains("Integer, String"), "Format should show param types"); assertTrue(formatted.contains("Boolean"), "Format should show return type"); diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 72117a4..3e502b2 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -25,28 +25,22 @@ void parsePrimitiveType() { @Test void parseArrayType() { ParsedType result = ParsedType.parse(int[].class); - assertEquals( - new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)), - result); + assertEquals(new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)), result); } @Test void parseObjectArrayType() { ParsedType result = ParsedType.parse(String[].class); - assertEquals( - new ParsedType.ArrayOf(new ParsedType.Const(String.class)), - result); + assertEquals(new ParsedType.ArrayOf(new ParsedType.Const(String.class)), result); } @Test void parseParameterizedType() throws Exception { Type listType = new TypeToken>() {}.type(); ParsedType result = ParsedType.parse(listType); - - ParsedType expected = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ); + + ParsedType expected = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); assertEquals(expected, result); } @@ -54,14 +48,11 @@ void parseParameterizedType() throws Exception { void parseMultipleTypeParameters() throws Exception { Type mapType = new TypeToken>() {}.type(); ParsedType result = ParsedType.parse(mapType); - - ParsedType expected = new ParsedType.App( + + ParsedType expected = new ParsedType.App( - new ParsedType.Const(Map.class), - new ParsedType.Const(String.class) - ), - new ParsedType.Const(Integer.class) - ); + new ParsedType.App(new ParsedType.Const(Map.class), new ParsedType.Const(String.class)), + new ParsedType.Const(Integer.class)); assertEquals(expected, result); } @@ -69,14 +60,12 @@ void parseMultipleTypeParameters() throws Exception { void parseNestedParameterizedType() throws Exception { Type nestedType = new TypeToken>>() {}.type(); ParsedType result = ParsedType.parse(nestedType); - - ParsedType expected = new ParsedType.App( - new ParsedType.Const(List.class), + + ParsedType expected = new ParsedType.App( - new ParsedType.Const(Optional.class), - new ParsedType.Const(String.class) - ) - ); + new ParsedType.Const(List.class), + new ParsedType.App( + new ParsedType.Const(Optional.class), new ParsedType.Const(String.class))); assertEquals(expected, result); } @@ -88,10 +77,8 @@ void formatConst() { @Test void formatApp() { - ParsedType type = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ); + ParsedType type = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); assertEquals("List[E](String)", type.format()); } @@ -111,12 +98,12 @@ void formatPrimitive() { void parseAll() throws Exception { Type[] types = {String.class, Integer.class, int.class}; List result = ParsedType.parseAll(types); - - List expected = List.of( - new ParsedType.Const(String.class), - new ParsedType.Const(Integer.class), - new ParsedType.Primitive(int.class) - ); + + List expected = + List.of( + new ParsedType.Const(String.class), + new ParsedType.Const(Integer.class), + new ParsedType.Primitive(int.class)); assertEquals(expected, result); } diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index 4edc269..ee5d8e6 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -13,9 +13,9 @@ final class UnificationTest { void unifyEqualConsts() { ParsedType t1 = new ParsedType.Const(String.class); ParsedType t2 = new ParsedType.Const(String.class); - + Maybe> result = Unification.unify(t1, t2); - + assertTrue(result instanceof Maybe.Just); assertEquals(Map.of(), ((Maybe.Just>) result).value()); } @@ -24,9 +24,9 @@ void unifyEqualConsts() { void unifyDifferentConsts() { ParsedType t1 = new ParsedType.Const(String.class); ParsedType t2 = new ParsedType.Const(Integer.class); - + Maybe> result = Unification.unify(t1, t2); - + assertTrue(result instanceof Maybe.Nothing); } @@ -35,11 +35,12 @@ void unifyVarWithConst() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType t1 = new ParsedType.Var(tv); ParsedType t2 = new ParsedType.Const(String.class); - + Maybe> result = Unification.unify(t1, t2); - + assertTrue(result instanceof Maybe.Just); - Map map = ((Maybe.Just>) result).value(); + Map map = + ((Maybe.Just>) result).value(); assertEquals(1, map.size()); assertEquals(t2, map.get(t1)); } @@ -49,42 +50,34 @@ void unifyVarWithPrimitiveFails() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType t1 = new ParsedType.Var(tv); ParsedType t2 = new ParsedType.Primitive(int.class); - + Maybe> result = Unification.unify(t1, t2); - + assertTrue(result instanceof Maybe.Nothing); } @Test void unifyApps() { - ParsedType list1 = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ); - ParsedType list2 = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ); - + ParsedType list1 = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + ParsedType list2 = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + Maybe> result = Unification.unify(list1, list2); - + assertTrue(result instanceof Maybe.Just); assertEquals(Map.of(), ((Maybe.Just>) result).value()); } @Test void unifyAppsDifferentArgs() { - ParsedType list1 = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(String.class) - ); - ParsedType list2 = new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.Const(Integer.class) - ); - + ParsedType list1 = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + ParsedType list2 = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(Integer.class)); + Maybe> result = Unification.unify(list1, list2); - + assertTrue(result instanceof Maybe.Nothing); } @@ -92,9 +85,9 @@ void unifyAppsDifferentArgs() { void unifyArrays() { ParsedType arr1 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); ParsedType arr2 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); - + Maybe> result = Unification.unify(arr1, arr2); - + assertTrue(result instanceof Maybe.Just); assertEquals(Map.of(), ((Maybe.Just>) result).value()); } @@ -103,9 +96,9 @@ void unifyArrays() { void unifyPrimitives() { ParsedType p1 = new ParsedType.Primitive(int.class); ParsedType p2 = new ParsedType.Primitive(int.class); - + Maybe> result = Unification.unify(p1, p2); - + assertTrue(result instanceof Maybe.Just); assertEquals(Map.of(), ((Maybe.Just>) result).value()); } @@ -114,9 +107,9 @@ void unifyPrimitives() { void unifyDifferentPrimitives() { ParsedType p1 = new ParsedType.Primitive(int.class); ParsedType p2 = new ParsedType.Primitive(boolean.class); - + Maybe> result = Unification.unify(p1, p2); - + assertTrue(result instanceof Maybe.Nothing); } @@ -126,9 +119,9 @@ void substituteVar() throws Exception { ParsedType.Var var = new ParsedType.Var(tv); ParsedType replacement = new ParsedType.Const(String.class); Map map = Map.of(var, replacement); - + ParsedType result = Unification.substitute(map, var); - + assertEquals(replacement, result); } @@ -136,9 +129,9 @@ void substituteVar() throws Exception { void substituteConst() { ParsedType type = new ParsedType.Const(String.class); Map map = Map.of(); - + ParsedType result = Unification.substitute(map, type); - + assertEquals(type, result); } @@ -146,19 +139,13 @@ void substituteConst() { void substituteApp() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); - ParsedType app = new ParsedType.App( - new ParsedType.Const(List.class), - var - ); + ParsedType app = new ParsedType.App(new ParsedType.Const(List.class), var); ParsedType replacement = new ParsedType.Const(String.class); Map map = Map.of(var, replacement); - + ParsedType result = Unification.substitute(map, app); - - ParsedType expected = new ParsedType.App( - new ParsedType.Const(List.class), - replacement - ); + + ParsedType expected = new ParsedType.App(new ParsedType.Const(List.class), replacement); assertEquals(expected, result); } @@ -169,9 +156,9 @@ void substituteArrayOf() throws Exception { ParsedType arrayType = new ParsedType.ArrayOf(var); ParsedType replacement = new ParsedType.Const(String.class); Map map = Map.of(var, replacement); - + ParsedType result = Unification.substitute(map, arrayType); - + ParsedType expected = new ParsedType.ArrayOf(replacement); assertEquals(expected, result); } @@ -180,21 +167,21 @@ void substituteArrayOf() throws Exception { void substituteAll() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); - List types = List.of( - var, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), var) - ); + List types = + List.of( + var, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), var)); ParsedType replacement = new ParsedType.Const(String.class); Map map = Map.of(var, replacement); - + List result = Unification.substituteAll(map, types); - - List expected = List.of( - replacement, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), replacement) - ); + + List expected = + List.of( + replacement, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), replacement)); assertEquals(expected, result); } From 68b29bae3193d403c7aa34ff9c33a6af69303993 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:35:11 +0000 Subject: [PATCH 05/12] Address PR feedback: use Ty from api package and assertEquals for Maybe types Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/ParsedTypeTest.java | 19 +++----------- .../typeclasses/impl/UnificationTest.java | 26 +++++++------------ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 3e502b2..265a452 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; -import java.lang.reflect.ParameterizedType; +import com.garciat.typeclasses.api.Ty; import java.lang.reflect.Type; import java.util.List; import java.util.Map; @@ -36,7 +36,7 @@ void parseObjectArrayType() { @Test void parseParameterizedType() throws Exception { - Type listType = new TypeToken>() {}.type(); + Type listType = new Ty>() {}.type(); ParsedType result = ParsedType.parse(listType); ParsedType expected = @@ -46,7 +46,7 @@ void parseParameterizedType() throws Exception { @Test void parseMultipleTypeParameters() throws Exception { - Type mapType = new TypeToken>() {}.type(); + Type mapType = new Ty>() {}.type(); ParsedType result = ParsedType.parse(mapType); ParsedType expected = @@ -58,7 +58,7 @@ void parseMultipleTypeParameters() throws Exception { @Test void parseNestedParameterizedType() throws Exception { - Type nestedType = new TypeToken>>() {}.type(); + Type nestedType = new Ty>>() {}.type(); ParsedType result = ParsedType.parse(nestedType); ParsedType expected = @@ -106,15 +106,4 @@ void parseAll() throws Exception { new ParsedType.Primitive(int.class)); assertEquals(expected, result); } - - // Helper class to capture generic type information - private abstract static class TypeToken { - Type type() { - Type superclass = getClass().getGenericSuperclass(); - if (superclass instanceof ParameterizedType pt) { - return pt.getActualTypeArguments()[0]; - } - throw new IllegalStateException("TypeToken requires type parameter"); - } - } } diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index ee5d8e6..546cb3e 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -16,8 +16,7 @@ void unifyEqualConsts() { Maybe> result = Unification.unify(t1, t2); - assertTrue(result instanceof Maybe.Just); - assertEquals(Map.of(), ((Maybe.Just>) result).value()); + assertEquals(Maybe.just(Map.of()), result); } @Test @@ -27,7 +26,7 @@ void unifyDifferentConsts() { Maybe> result = Unification.unify(t1, t2); - assertTrue(result instanceof Maybe.Nothing); + assertEquals(Maybe.nothing(), result); } @Test @@ -38,11 +37,7 @@ void unifyVarWithConst() throws Exception { Maybe> result = Unification.unify(t1, t2); - assertTrue(result instanceof Maybe.Just); - Map map = - ((Maybe.Just>) result).value(); - assertEquals(1, map.size()); - assertEquals(t2, map.get(t1)); + assertEquals(Maybe.just(Map.of(t1, t2)), result); } @Test @@ -53,7 +48,7 @@ void unifyVarWithPrimitiveFails() throws Exception { Maybe> result = Unification.unify(t1, t2); - assertTrue(result instanceof Maybe.Nothing); + assertEquals(Maybe.nothing(), result); } @Test @@ -65,8 +60,7 @@ void unifyApps() { Maybe> result = Unification.unify(list1, list2); - assertTrue(result instanceof Maybe.Just); - assertEquals(Map.of(), ((Maybe.Just>) result).value()); + assertEquals(Maybe.just(Map.of()), result); } @Test @@ -78,7 +72,7 @@ void unifyAppsDifferentArgs() { Maybe> result = Unification.unify(list1, list2); - assertTrue(result instanceof Maybe.Nothing); + assertEquals(Maybe.nothing(), result); } @Test @@ -88,8 +82,7 @@ void unifyArrays() { Maybe> result = Unification.unify(arr1, arr2); - assertTrue(result instanceof Maybe.Just); - assertEquals(Map.of(), ((Maybe.Just>) result).value()); + assertEquals(Maybe.just(Map.of()), result); } @Test @@ -99,8 +92,7 @@ void unifyPrimitives() { Maybe> result = Unification.unify(p1, p2); - assertTrue(result instanceof Maybe.Just); - assertEquals(Map.of(), ((Maybe.Just>) result).value()); + assertEquals(Maybe.just(Map.of()), result); } @Test @@ -110,7 +102,7 @@ void unifyDifferentPrimitives() { Maybe> result = Unification.unify(p1, p2); - assertTrue(result instanceof Maybe.Nothing); + assertEquals(Maybe.nothing(), result); } @Test From c2456dc622c9b93d46e65cd20e46d8cf4a514878 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:44:29 +0000 Subject: [PATCH 06/12] Address PR feedback: use custom test type classes, simplify FuncType assertions, add generic method test, remove format tests Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../garciat/typeclasses/TypeClassesTest.java | 81 ++++++++----------- .../typeclasses/impl/FuncTypeTest.java | 77 ++++++++++-------- .../typeclasses/impl/ParsedTypeTest.java | 25 ------ .../typeclasses/testclasses/TestEq.java | 50 ++++++++++++ .../typeclasses/testclasses/TestShow.java | 35 ++++++++ 5 files changed, 160 insertions(+), 108 deletions(-) create mode 100644 src/test/java/com/garciat/typeclasses/testclasses/TestEq.java create mode 100644 src/test/java/com/garciat/typeclasses/testclasses/TestShow.java diff --git a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java index e2dfb04..d443337 100644 --- a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java +++ b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java @@ -5,8 +5,8 @@ import com.garciat.typeclasses.api.Ctx; import com.garciat.typeclasses.api.Ty; -import com.garciat.typeclasses.classes.Eq; -import com.garciat.typeclasses.classes.Show; +import com.garciat.typeclasses.testclasses.TestEq; +import com.garciat.typeclasses.testclasses.TestShow; import java.util.List; import java.util.Map; import java.util.Optional; @@ -20,22 +20,22 @@ final class TypeClassesTest { @Test void witnessSimpleTypeClass() { - Show showString = witness(new Ty<>() {}); + TestShow showString = witness(new Ty<>() {}); assertNotNull(showString); - assertEquals("\"test\"", showString.show("test")); + assertEquals("string:test", showString.show("test")); } @Test void witnessWithDependency() { - Show> showOptional = witness(new Ty<>() {}); + TestShow> showOptional = witness(new Ty<>() {}); assertNotNull(showOptional); - assertEquals("Some(\"test\")", showOptional.show(Optional.of("test"))); - assertEquals("None", showOptional.show(Optional.empty())); + assertEquals("opt(string:test)", showOptional.show(Optional.of("test"))); + assertEquals("empty", showOptional.show(Optional.empty())); } @Test void witnessWithMultipleDependencies() { - Eq> eqList = witness(new Ty<>() {}); + TestEq> eqList = witness(new Ty<>() {}); assertNotNull(eqList); assertTrue(eqList.eq(List.of("a", "b"), List.of("a", "b"))); assertFalse(eqList.eq(List.of("a", "b"), List.of("a", "c"))); @@ -47,15 +47,15 @@ void witnessWithMultipleDependencies() { @Test void witnessLookupsInTypeClass() { - // Lookup should find witnesses in the type class interface (Show) - Show show = witness(new Ty<>() {}); + // Lookup should find witnesses in the type class interface (TestShow) + TestShow show = witness(new Ty<>() {}); assertNotNull(show); } @Test void witnessLookupsInTypeArguments() { - // Lookup should find witnesses in type arguments (String has witness in Show) - Show> show = witness(new Ty<>() {}); + // Lookup should find witnesses in type arguments (String has witness in TestShow) + TestShow> show = witness(new Ty<>() {}); assertNotNull(show); } @@ -66,7 +66,7 @@ void witnessLookupsInTypeArguments() { @Test void witnessRequiresPublicStaticMethod() { // This should work - witness is public static - Show show = witness(new Ty<>() {}); + TestShow show = witness(new Ty<>() {}); assertNotNull(show); } @@ -75,7 +75,7 @@ void witnessNotFoundForUnannotatedTypes() { // NoWitnessType has no @TypeClass.Witness methods assertThrows( TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + () -> witness(new Ty>() {})); } // ============================================ @@ -85,20 +85,20 @@ void witnessNotFoundForUnannotatedTypes() { @Test void witnessRecursiveDependencies() { // List> requires: - // 1. listShow(Show>) - // 2. optionalShow(Show) + // 1. listShow(TestShow>) + // 2. optionalShow(TestShow) // 3. stringShow() - Show>> show = witness(new Ty<>() {}); + TestShow>> show = witness(new Ty<>() {}); assertNotNull(show); assertEquals( - "[Some(\"a\"), None, Some(\"b\")]", + "[opt(string:a),empty,opt(string:b)]", show.show(List.of(Optional.of("a"), Optional.empty(), Optional.of("b")))); } @Test void witnessDeepRecursion() { // List>> should resolve recursively - Show>>> show = witness(new Ty<>() {}); + TestShow>>> show = witness(new Ty<>() {}); assertNotNull(show); } @@ -144,15 +144,15 @@ void ambiguousWitnessesThrow() { void witnessNotFoundThrows() { assertThrows( TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + () -> witness(new Ty>() {})); } @Test void witnessNotFoundNestedThrows() { - // List - the dependency Show cannot be found + // List - the dependency TestShow cannot be found assertThrows( TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>>() {})); + () -> witness(new Ty>>() {})); } // ============================================ @@ -163,23 +163,24 @@ void witnessNotFoundNestedThrows() { void witnessSummoningWithContext() { // Provide a custom witness via context CustomType customValue = new CustomType("test"); - Show customShow = c -> "custom:" + c.value; + TestShow customShow = c -> "custom:" + c.value; - Show> listShow = witness(new Ty<>() {}, new Ctx<>(customShow) {}); + TestShow> listShow = witness(new Ty<>() {}, new Ctx<>(customShow) {}); assertNotNull(listShow); assertEquals( - "[custom:a, custom:b]", listShow.show(List.of(new CustomType("a"), new CustomType("b")))); + "[custom:a,custom:b]", listShow.show(List.of(new CustomType("a"), new CustomType("b")))); } @Test void witnessSummoningBuildsTree() { // Verify that the witness is actually constructed correctly // by checking its behavior with nested types - Show>> show = witness(new Ty<>() {}); + TestShow>> show = witness(new Ty<>() {}); - assertEquals("Some([\"a\", \"b\", \"c\"])", show.show(Optional.of(List.of("a", "b", "c")))); - assertEquals("None", show.show(Optional.empty())); + assertEquals( + "opt([string:a,string:b,string:c])", show.show(Optional.of(List.of("a", "b", "c")))); + assertEquals("empty", show.show(Optional.empty())); } // ============================================ @@ -191,7 +192,7 @@ void witnessResolutionExceptionHasMessage() { TypeClasses.WitnessResolutionException ex = assertThrows( TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + () -> witness(new Ty>() {})); assertNotNull(ex.getMessage()); assertTrue( @@ -214,8 +215,8 @@ void witnessResolutionExceptionForAmbiguous() { @Test void witnessMapWithDependencies() { - // Map requires Eq and Eq - Eq> eqMap = witness(new Ty<>() {}); + // Map requires TestEq and TestEq + TestEq> eqMap = witness(new Ty<>() {}); assertNotNull(eqMap); Map map1 = Map.of("a", 1, "b", 2); @@ -226,24 +227,6 @@ void witnessMapWithDependencies() { assertFalse(eqMap.eq(map1, map3)); } - @Test - void witnessArrays() { - // Integer arrays should have a Show witness - Show showArray = witness(new Ty<>() {}); - assertNotNull(showArray); - - assertEquals("[1, 2, 3]", showArray.show(new Integer[] {1, 2, 3})); - } - - @Test - void witnessPrimitiveArrays() { - // Primitive int arrays should have a Show witness - Show showIntArray = witness(new Ty<>() {}); - assertNotNull(showIntArray); - - assertEquals("[1, 2, 3]", showIntArray.show(new int[] {1, 2, 3})); - } - // ============================================ // Test helper classes // ============================================ diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java index 14f9842..7706760 100644 --- a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -12,9 +12,8 @@ void parseSimpleStaticMethod() throws Exception { Method method = TestMethods.class.getDeclaredMethod("simple"); FuncType result = FuncType.parse(method); - assertEquals(0, result.paramTypes().size()); - assertEquals(new ParsedType.Const(String.class), result.returnType()); - assertEquals(method, result.java()); + FuncType expected = new FuncType(method, List.of(), new ParsedType.Const(String.class)); + assertEquals(expected, result); } @Test @@ -22,10 +21,12 @@ void parseMethodWithParameters() throws Exception { Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); FuncType result = FuncType.parse(method); - assertEquals(2, result.paramTypes().size()); - assertEquals(new ParsedType.Const(Integer.class), result.paramTypes().get(0)); - assertEquals(new ParsedType.Const(String.class), result.paramTypes().get(1)); - assertEquals(new ParsedType.Const(Boolean.class), result.returnType()); + FuncType expected = + new FuncType( + method, + List.of(new ParsedType.Const(Integer.class), new ParsedType.Const(String.class)), + new ParsedType.Const(Boolean.class)); + assertEquals(expected, result); } @Test @@ -33,10 +34,13 @@ void parseMethodWithGenericReturn() throws Exception { Method method = TestMethods.class.getDeclaredMethod("genericReturn"); FuncType result = FuncType.parse(method); - assertEquals(0, result.paramTypes().size()); - assertEquals( - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)), - result.returnType()); + FuncType expected = + new FuncType( + method, + List.of(), + new ParsedType.App( + new ParsedType.Const(List.class), new ParsedType.Const(String.class))); + assertEquals(expected, result); } @Test @@ -44,10 +48,14 @@ void parseMethodWithGenericParams() throws Exception { Method method = TestMethods.class.getDeclaredMethod("genericParams", List.class); FuncType result = FuncType.parse(method); - assertEquals(1, result.paramTypes().size()); - assertEquals( - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(Integer.class)), - result.paramTypes().get(0)); + FuncType expected = + new FuncType( + method, + List.of( + new ParsedType.App( + new ParsedType.Const(List.class), new ParsedType.Const(Integer.class))), + new ParsedType.Primitive(void.class)); + assertEquals(expected, result); } @Test @@ -55,10 +63,12 @@ void parseMethodWithPrimitives() throws Exception { Method method = TestMethods.class.getDeclaredMethod("withPrimitives", int.class, boolean.class); FuncType result = FuncType.parse(method); - assertEquals(2, result.paramTypes().size()); - assertEquals(new ParsedType.Primitive(int.class), result.paramTypes().get(0)); - assertEquals(new ParsedType.Primitive(boolean.class), result.paramTypes().get(1)); - assertEquals(new ParsedType.Primitive(void.class), result.returnType()); + FuncType expected = + new FuncType( + method, + List.of(new ParsedType.Primitive(int.class), new ParsedType.Primitive(boolean.class)), + new ParsedType.Primitive(void.class)); + assertEquals(expected, result); } @Test @@ -68,22 +78,17 @@ void parseNonStaticMethodThrows() throws Exception { } @Test - void formatNoParams() throws Exception { - Method method = TestMethods.class.getDeclaredMethod("simple"); - FuncType funcType = FuncType.parse(method); - - String formatted = funcType.format(); - assertTrue(formatted.contains("() -> String"), "Format should show no params and return type"); - } - - @Test - void formatWithParams() throws Exception { - Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); - FuncType funcType = FuncType.parse(method); + void parseGenericMethodWithTypeParameters() throws Exception { + Method method = TestMethods.class.getDeclaredMethod("genericMethod", Object.class); + FuncType result = FuncType.parse(method); - String formatted = funcType.format(); - assertTrue(formatted.contains("Integer, String"), "Format should show param types"); - assertTrue(formatted.contains("Boolean"), "Format should show return type"); + // The method has a type parameter T, so we expect a Var in the return type + FuncType expected = + new FuncType( + method, + List.of(new ParsedType.Var(method.getTypeParameters()[0])), + new ParsedType.Var(method.getTypeParameters()[0])); + assertEquals(expected, result); } // Test helper class with various method signatures @@ -104,6 +109,10 @@ public static void genericParams(List list) {} public static void withPrimitives(int i, boolean b) {} + public static T genericMethod(T value) { + return value; + } + public String nonStatic() { return ""; } diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 265a452..5f78b1e 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -69,31 +69,6 @@ void parseNestedParameterizedType() throws Exception { assertEquals(expected, result); } - @Test - void formatConst() { - ParsedType type = new ParsedType.Const(String.class); - assertEquals("String", type.format()); - } - - @Test - void formatApp() { - ParsedType type = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); - assertEquals("List[E](String)", type.format()); - } - - @Test - void formatArrayOf() { - ParsedType type = new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)); - assertEquals("int[]", type.format()); - } - - @Test - void formatPrimitive() { - ParsedType type = new ParsedType.Primitive(int.class); - assertEquals("int", type.format()); - } - @Test void parseAll() throws Exception { Type[] types = {String.class, Integer.class, int.class}; diff --git a/src/test/java/com/garciat/typeclasses/testclasses/TestEq.java b/src/test/java/com/garciat/typeclasses/testclasses/TestEq.java new file mode 100644 index 0000000..cb8fbd2 --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/testclasses/TestEq.java @@ -0,0 +1,50 @@ +package com.garciat.typeclasses.testclasses; + +import com.garciat.typeclasses.api.TypeClass; +import java.util.List; +import java.util.Map; + +@TypeClass +public interface TestEq { + boolean eq(A a1, A a2); + + @TypeClass.Witness + static TestEq stringEq() { + return String::equals; + } + + @TypeClass.Witness + static TestEq integerEq() { + return Integer::equals; + } + + @TypeClass.Witness + static TestEq> listEq(TestEq eqA) { + return (l1, l2) -> { + if (l1.size() != l2.size()) return false; + for (int i = 0; i < l1.size(); i++) { + if (!eqA.eq(l1.get(i), l2.get(i))) return false; + } + return true; + }; + } + + @TypeClass.Witness + static TestEq> mapEq(TestEq eqK, TestEq eqV) { + return (map1, map2) -> { + if (map1.size() != map2.size()) return false; + for (Map.Entry entry1 : map1.entrySet()) { + boolean found = false; + for (Map.Entry entry2 : map2.entrySet()) { + if (eqK.eq(entry1.getKey(), entry2.getKey()) + && eqV.eq(entry1.getValue(), entry2.getValue())) { + found = true; + break; + } + } + if (!found) return false; + } + return true; + }; + } +} diff --git a/src/test/java/com/garciat/typeclasses/testclasses/TestShow.java b/src/test/java/com/garciat/typeclasses/testclasses/TestShow.java new file mode 100644 index 0000000..9a6d317 --- /dev/null +++ b/src/test/java/com/garciat/typeclasses/testclasses/TestShow.java @@ -0,0 +1,35 @@ +package com.garciat.typeclasses.testclasses; + +import com.garciat.typeclasses.api.TypeClass; +import java.util.List; +import java.util.Optional; + +@TypeClass +public interface TestShow { + String show(A a); + + @TypeClass.Witness + static TestShow stringShow() { + return s -> "string:" + s; + } + + @TypeClass.Witness + static TestShow integerShow() { + return i -> "int:" + i; + } + + @TypeClass.Witness + static TestShow> optionalShow(TestShow showA) { + return optA -> optA.map(a -> "opt(" + showA.show(a) + ")").orElse("empty"); + } + + @TypeClass.Witness + static TestShow> listShow(TestShow showA) { + return listA -> + listA.stream() + .map(showA::show) + .reduce((a, b) -> a + "," + b) + .map(s -> "[" + s + "]") + .orElse("[]"); + } +} From 69e136b803b0fd7740907c2dc784513411377210 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:20:13 +0000 Subject: [PATCH 07/12] Switch from JUnit assertions to AssertJ assertions Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- pom.xml | 6 + .../garciat/typeclasses/TypeClassesTest.java | 105 ++++++++---------- .../typeclasses/impl/FuncTypeTest.java | 16 +-- .../typeclasses/impl/ParsedTypeTest.java | 18 +-- .../typeclasses/impl/UnificationTest.java | 30 ++--- 5 files changed, 83 insertions(+), 92 deletions(-) diff --git a/pom.xml b/pom.xml index 0f66a65..6aed270 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,12 @@ 6.0.0 test + + org.assertj + assertj-core + 3.27.3 + test + diff --git a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java index d443337..626aaeb 100644 --- a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java +++ b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java @@ -1,7 +1,7 @@ package com.garciat.typeclasses; import static com.garciat.typeclasses.TypeClasses.witness; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; import com.garciat.typeclasses.api.Ctx; import com.garciat.typeclasses.api.Ty; @@ -21,24 +21,24 @@ final class TypeClassesTest { @Test void witnessSimpleTypeClass() { TestShow showString = witness(new Ty<>() {}); - assertNotNull(showString); - assertEquals("string:test", showString.show("test")); + assertThat(showString).isNotNull(); + assertThat(showString.show("test")).isEqualTo("string:test"); } @Test void witnessWithDependency() { TestShow> showOptional = witness(new Ty<>() {}); - assertNotNull(showOptional); - assertEquals("opt(string:test)", showOptional.show(Optional.of("test"))); - assertEquals("empty", showOptional.show(Optional.empty())); + assertThat(showOptional).isNotNull(); + assertThat(showOptional.show(Optional.of("test"))).isEqualTo("opt(string:test)"); + assertThat(showOptional.show(Optional.empty())).isEqualTo("empty"); } @Test void witnessWithMultipleDependencies() { TestEq> eqList = witness(new Ty<>() {}); - assertNotNull(eqList); - assertTrue(eqList.eq(List.of("a", "b"), List.of("a", "b"))); - assertFalse(eqList.eq(List.of("a", "b"), List.of("a", "c"))); + assertThat(eqList).isNotNull(); + assertThat(eqList.eq(List.of("a", "b"), List.of("a", "b"))).isTrue(); + assertThat(eqList.eq(List.of("a", "b"), List.of("a", "c"))).isFalse(); } // ============================================ @@ -49,14 +49,14 @@ void witnessWithMultipleDependencies() { void witnessLookupsInTypeClass() { // Lookup should find witnesses in the type class interface (TestShow) TestShow show = witness(new Ty<>() {}); - assertNotNull(show); + assertThat(show).isNotNull(); } @Test void witnessLookupsInTypeArguments() { // Lookup should find witnesses in type arguments (String has witness in TestShow) TestShow> show = witness(new Ty<>() {}); - assertNotNull(show); + assertThat(show).isNotNull(); } // ============================================ @@ -67,15 +67,14 @@ void witnessLookupsInTypeArguments() { void witnessRequiresPublicStaticMethod() { // This should work - witness is public static TestShow show = witness(new Ty<>() {}); - assertNotNull(show); + assertThat(show).isNotNull(); } @Test void witnessNotFoundForUnannotatedTypes() { // NoWitnessType has no @TypeClass.Witness methods - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + assertThatThrownBy(() -> witness(new Ty>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class); } // ============================================ @@ -89,17 +88,16 @@ void witnessRecursiveDependencies() { // 2. optionalShow(TestShow) // 3. stringShow() TestShow>> show = witness(new Ty<>() {}); - assertNotNull(show); - assertEquals( - "[opt(string:a),empty,opt(string:b)]", - show.show(List.of(Optional.of("a"), Optional.empty(), Optional.of("b")))); + assertThat(show).isNotNull(); + assertThat(show.show(List.of(Optional.of("a"), Optional.empty(), Optional.of("b")))) + .isEqualTo("[opt(string:a),empty,opt(string:b)]"); } @Test void witnessDeepRecursion() { // List>> should resolve recursively TestShow>>> show = witness(new Ty<>() {}); - assertNotNull(show); + assertThat(show).isNotNull(); } // ============================================ @@ -111,17 +109,17 @@ void overlappingInstancesMoreSpecificWins() { // When we have both general and specific instances, // the more specific (overlapping) one should win com.garciat.typeclasses.testclasses.OverlapShow show = witness(new Ty<>() {}); - assertNotNull(show); + assertThat(show).isNotNull(); // Should use the specific Integer instance - assertEquals("Integer: 42", show.show(42)); + assertThat(show.show(42)).isEqualTo("Integer: 42"); } @Test void overlappableInstanceCanBeOverridden() { // OVERLAPPABLE instances can be overridden by more specific ones com.garciat.typeclasses.testclasses.OverlapShow show = witness(new Ty<>() {}); - assertNotNull(show); - assertEquals("String: test", show.show("test")); + assertThat(show).isNotNull(); + assertThat(show.show("test")).isEqualTo("String: test"); } // ============================================ @@ -131,9 +129,9 @@ void overlappableInstanceCanBeOverridden() { @Test void ambiguousWitnessesThrow() { // AmbiguousShow has two witness constructors without overlap markers - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + assertThatThrownBy( + () -> witness(new Ty>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class); } // ============================================ @@ -142,17 +140,15 @@ void ambiguousWitnessesThrow() { @Test void witnessNotFoundThrows() { - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); + assertThatThrownBy(() -> witness(new Ty>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class); } @Test void witnessNotFoundNestedThrows() { // List - the dependency TestShow cannot be found - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>>() {})); + assertThatThrownBy(() -> witness(new Ty>>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class); } // ============================================ @@ -167,9 +163,9 @@ void witnessSummoningWithContext() { TestShow> listShow = witness(new Ty<>() {}, new Ctx<>(customShow) {}); - assertNotNull(listShow); - assertEquals( - "[custom:a,custom:b]", listShow.show(List.of(new CustomType("a"), new CustomType("b")))); + assertThat(listShow).isNotNull(); + assertThat(listShow.show(List.of(new CustomType("a"), new CustomType("b")))) + .isEqualTo("[custom:a,custom:b]"); } @Test @@ -178,9 +174,9 @@ void witnessSummoningBuildsTree() { // by checking its behavior with nested types TestShow>> show = witness(new Ty<>() {}); - assertEquals( - "opt([string:a,string:b,string:c])", show.show(Optional.of(List.of("a", "b", "c")))); - assertEquals("empty", show.show(Optional.empty())); + assertThat(show.show(Optional.of(List.of("a", "b", "c")))) + .isEqualTo("opt([string:a,string:b,string:c])"); + assertThat(show.show(Optional.empty())).isEqualTo("empty"); } // ============================================ @@ -189,42 +185,31 @@ void witnessSummoningBuildsTree() { @Test void witnessResolutionExceptionHasMessage() { - TypeClasses.WitnessResolutionException ex = - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); - - assertNotNull(ex.getMessage()); - assertTrue( - ex.getMessage().contains("NoWitnessType"), - "Error message should mention the type that failed: " + ex.getMessage()); + assertThatThrownBy(() -> witness(new Ty>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class) + .hasMessageContaining("NoWitnessType"); } @Test void witnessResolutionExceptionForAmbiguous() { - TypeClasses.WitnessResolutionException ex = - assertThrows( - TypeClasses.WitnessResolutionException.class, - () -> witness(new Ty>() {})); - - assertNotNull(ex.getMessage()); - assertTrue( - ex.getMessage().toLowerCase().contains("ambiguous"), - "Error message should mention ambiguity: " + ex.getMessage()); + assertThatThrownBy( + () -> witness(new Ty>() {})) + .isInstanceOf(TypeClasses.WitnessResolutionException.class) + .hasMessageContaining("mbiguous"); } @Test void witnessMapWithDependencies() { // Map requires TestEq and TestEq TestEq> eqMap = witness(new Ty<>() {}); - assertNotNull(eqMap); + assertThat(eqMap).isNotNull(); Map map1 = Map.of("a", 1, "b", 2); Map map2 = Map.of("a", 1, "b", 2); Map map3 = Map.of("a", 1, "b", 3); - assertTrue(eqMap.eq(map1, map2)); - assertFalse(eqMap.eq(map1, map3)); + assertThat(eqMap.eq(map1, map2)).isTrue(); + assertThat(eqMap.eq(map1, map3)).isFalse(); } // ============================================ diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java index 7706760..bfd038c 100644 --- a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -1,6 +1,6 @@ package com.garciat.typeclasses.impl; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; import java.lang.reflect.Method; import java.util.List; @@ -13,7 +13,7 @@ void parseSimpleStaticMethod() throws Exception { FuncType result = FuncType.parse(method); FuncType expected = new FuncType(method, List.of(), new ParsedType.Const(String.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -26,7 +26,7 @@ void parseMethodWithParameters() throws Exception { method, List.of(new ParsedType.Const(Integer.class), new ParsedType.Const(String.class)), new ParsedType.Const(Boolean.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -40,7 +40,7 @@ void parseMethodWithGenericReturn() throws Exception { List.of(), new ParsedType.App( new ParsedType.Const(List.class), new ParsedType.Const(String.class))); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -55,7 +55,7 @@ void parseMethodWithGenericParams() throws Exception { new ParsedType.App( new ParsedType.Const(List.class), new ParsedType.Const(Integer.class))), new ParsedType.Primitive(void.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -68,13 +68,13 @@ void parseMethodWithPrimitives() throws Exception { method, List.of(new ParsedType.Primitive(int.class), new ParsedType.Primitive(boolean.class)), new ParsedType.Primitive(void.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test void parseNonStaticMethodThrows() throws Exception { Method method = TestMethods.class.getDeclaredMethod("nonStatic"); - assertThrows(IllegalArgumentException.class, () -> FuncType.parse(method)); + assertThatThrownBy(() -> FuncType.parse(method)).isInstanceOf(IllegalArgumentException.class); } @Test @@ -88,7 +88,7 @@ void parseGenericMethodWithTypeParameters() throws Exception { method, List.of(new ParsedType.Var(method.getTypeParameters()[0])), new ParsedType.Var(method.getTypeParameters()[0])); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } // Test helper class with various method signatures diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 5f78b1e..52ea936 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -1,6 +1,6 @@ package com.garciat.typeclasses.impl; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; import com.garciat.typeclasses.api.Ty; import java.lang.reflect.Type; @@ -13,25 +13,25 @@ final class ParsedTypeTest { @Test void parseClass() { ParsedType result = ParsedType.parse(String.class); - assertEquals(new ParsedType.Const(String.class), result); + assertThat(result).isEqualTo(new ParsedType.Const(String.class)); } @Test void parsePrimitiveType() { ParsedType result = ParsedType.parse(int.class); - assertEquals(new ParsedType.Primitive(int.class), result); + assertThat(result).isEqualTo(new ParsedType.Primitive(int.class)); } @Test void parseArrayType() { ParsedType result = ParsedType.parse(int[].class); - assertEquals(new ParsedType.ArrayOf(new ParsedType.Primitive(int.class)), result); + assertThat(result).isEqualTo(new ParsedType.ArrayOf(new ParsedType.Primitive(int.class))); } @Test void parseObjectArrayType() { ParsedType result = ParsedType.parse(String[].class); - assertEquals(new ParsedType.ArrayOf(new ParsedType.Const(String.class)), result); + assertThat(result).isEqualTo(new ParsedType.ArrayOf(new ParsedType.Const(String.class))); } @Test @@ -41,7 +41,7 @@ void parseParameterizedType() throws Exception { ParsedType expected = new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -53,7 +53,7 @@ void parseMultipleTypeParameters() throws Exception { new ParsedType.App( new ParsedType.App(new ParsedType.Const(Map.class), new ParsedType.Const(String.class)), new ParsedType.Const(Integer.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -66,7 +66,7 @@ void parseNestedParameterizedType() throws Exception { new ParsedType.Const(List.class), new ParsedType.App( new ParsedType.Const(Optional.class), new ParsedType.Const(String.class))); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -79,6 +79,6 @@ void parseAll() throws Exception { new ParsedType.Const(String.class), new ParsedType.Const(Integer.class), new ParsedType.Primitive(int.class)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } } diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index 546cb3e..3a48bf2 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -1,6 +1,6 @@ package com.garciat.typeclasses.impl; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; import com.garciat.typeclasses.types.Maybe; import java.lang.reflect.TypeVariable; @@ -16,7 +16,7 @@ void unifyEqualConsts() { Maybe> result = Unification.unify(t1, t2); - assertEquals(Maybe.just(Map.of()), result); + assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test @@ -26,7 +26,7 @@ void unifyDifferentConsts() { Maybe> result = Unification.unify(t1, t2); - assertEquals(Maybe.nothing(), result); + assertThat(result).isEqualTo(Maybe.nothing()); } @Test @@ -37,7 +37,7 @@ void unifyVarWithConst() throws Exception { Maybe> result = Unification.unify(t1, t2); - assertEquals(Maybe.just(Map.of(t1, t2)), result); + assertThat(result).isEqualTo(Maybe.just(Map.of(t1, t2))); } @Test @@ -48,7 +48,7 @@ void unifyVarWithPrimitiveFails() throws Exception { Maybe> result = Unification.unify(t1, t2); - assertEquals(Maybe.nothing(), result); + assertThat(result).isEqualTo(Maybe.nothing()); } @Test @@ -60,7 +60,7 @@ void unifyApps() { Maybe> result = Unification.unify(list1, list2); - assertEquals(Maybe.just(Map.of()), result); + assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test @@ -72,7 +72,7 @@ void unifyAppsDifferentArgs() { Maybe> result = Unification.unify(list1, list2); - assertEquals(Maybe.nothing(), result); + assertThat(result).isEqualTo(Maybe.nothing()); } @Test @@ -82,7 +82,7 @@ void unifyArrays() { Maybe> result = Unification.unify(arr1, arr2); - assertEquals(Maybe.just(Map.of()), result); + assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test @@ -92,7 +92,7 @@ void unifyPrimitives() { Maybe> result = Unification.unify(p1, p2); - assertEquals(Maybe.just(Map.of()), result); + assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test @@ -102,7 +102,7 @@ void unifyDifferentPrimitives() { Maybe> result = Unification.unify(p1, p2); - assertEquals(Maybe.nothing(), result); + assertThat(result).isEqualTo(Maybe.nothing()); } @Test @@ -114,7 +114,7 @@ void substituteVar() throws Exception { ParsedType result = Unification.substitute(map, var); - assertEquals(replacement, result); + assertThat(result).isEqualTo(replacement); } @Test @@ -124,7 +124,7 @@ void substituteConst() { ParsedType result = Unification.substitute(map, type); - assertEquals(type, result); + assertThat(result).isEqualTo(type); } @Test @@ -138,7 +138,7 @@ void substituteApp() throws Exception { ParsedType result = Unification.substitute(map, app); ParsedType expected = new ParsedType.App(new ParsedType.Const(List.class), replacement); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -152,7 +152,7 @@ void substituteArrayOf() throws Exception { ParsedType result = Unification.substitute(map, arrayType); ParsedType expected = new ParsedType.ArrayOf(replacement); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } @Test @@ -174,7 +174,7 @@ void substituteAll() throws Exception { replacement, new ParsedType.Const(Integer.class), new ParsedType.App(new ParsedType.Const(List.class), replacement)); - assertEquals(expected, result); + assertThat(result).isEqualTo(expected); } // Helper to get a type variable for testing From b64d0d41155459c68bf6e24024b99b12d38a4ccd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:28:59 +0000 Subject: [PATCH 08/12] Add test cases for type variables, wildcards, and inline unnecessary variables in UnificationTest Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/ParsedTypeTest.java | 38 +++++++++ .../typeclasses/impl/UnificationTest.java | 82 ++++++++++++------- 2 files changed, 92 insertions(+), 28 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 52ea936..5df989d 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -4,6 +4,7 @@ import com.garciat.typeclasses.api.Ty; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.List; import java.util.Map; import java.util.Optional; @@ -81,4 +82,41 @@ void parseAll() throws Exception { new ParsedType.Primitive(int.class)); assertThat(result).isEqualTo(expected); } + + @Test + void parseTypeVariable() throws Exception { + class TestClass {} + TypeVariable tv = TestClass.class.getTypeParameters()[0]; + + ParsedType result = ParsedType.parse(tv); + + assertThat(result).isEqualTo(new ParsedType.Var(tv)); + } + + @Test + void parseTypeVariableInParameterizedType() throws Exception { + class TestClass { + List field; + } + Type fieldType = TestClass.class.getDeclaredField("field").getGenericType(); + TypeVariable tv = TestClass.class.getTypeParameters()[0]; + + ParsedType result = ParsedType.parse(fieldType); + + ParsedType expected = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Var(tv)); + assertThat(result).isEqualTo(expected); + } + + @Test + void parseWildcardTypeThrows() throws Exception { + class TestClass { + List field; + } + Type fieldType = TestClass.class.getDeclaredField("field").getGenericType(); + + assertThatThrownBy(() -> ParsedType.parse(fieldType)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("wildcard"); + } } diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index 3a48bf2..9d33ee6 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -75,6 +75,37 @@ void unifyAppsDifferentArgs() { assertThat(result).isEqualTo(Maybe.nothing()); } + @Test + void unifyAppsWithVar() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + ParsedType list1 = new ParsedType.App(new ParsedType.Const(List.class), var); + ParsedType list2 = + new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + + Maybe> result = Unification.unify(list1, list2); + + assertThat(result).isEqualTo(Maybe.just(Map.of(var, new ParsedType.Const(String.class)))); + } + + @Test + void unifyNestedAppsWithVar() throws Exception { + TypeVariable tv = getTypeVariable(); + ParsedType.Var var = new ParsedType.Var(tv); + ParsedType map1 = + new ParsedType.App( + new ParsedType.App(new ParsedType.Const(Map.class), var), + new ParsedType.Const(Integer.class)); + ParsedType map2 = + new ParsedType.App( + new ParsedType.App(new ParsedType.Const(Map.class), new ParsedType.Const(String.class)), + new ParsedType.Const(Integer.class)); + + Maybe> result = Unification.unify(map1, map2); + + assertThat(result).isEqualTo(Maybe.just(Map.of(var, new ParsedType.Const(String.class)))); + } + @Test void unifyArrays() { ParsedType arr1 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); @@ -110,9 +141,8 @@ void substituteVar() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); ParsedType replacement = new ParsedType.Const(String.class); - Map map = Map.of(var, replacement); - ParsedType result = Unification.substitute(map, var); + ParsedType result = Unification.substitute(Map.of(var, replacement), var); assertThat(result).isEqualTo(replacement); } @@ -120,9 +150,8 @@ void substituteVar() throws Exception { @Test void substituteConst() { ParsedType type = new ParsedType.Const(String.class); - Map map = Map.of(); - ParsedType result = Unification.substitute(map, type); + ParsedType result = Unification.substitute(Map.of(), type); assertThat(result).isEqualTo(type); } @@ -131,50 +160,47 @@ void substituteConst() { void substituteApp() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); - ParsedType app = new ParsedType.App(new ParsedType.Const(List.class), var); ParsedType replacement = new ParsedType.Const(String.class); - Map map = Map.of(var, replacement); - ParsedType result = Unification.substitute(map, app); + ParsedType result = + Unification.substitute( + Map.of(var, replacement), new ParsedType.App(new ParsedType.Const(List.class), var)); - ParsedType expected = new ParsedType.App(new ParsedType.Const(List.class), replacement); - assertThat(result).isEqualTo(expected); + assertThat(result).isEqualTo(new ParsedType.App(new ParsedType.Const(List.class), replacement)); } @Test void substituteArrayOf() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); - ParsedType arrayType = new ParsedType.ArrayOf(var); ParsedType replacement = new ParsedType.Const(String.class); - Map map = Map.of(var, replacement); - ParsedType result = Unification.substitute(map, arrayType); + ParsedType result = + Unification.substitute(Map.of(var, replacement), new ParsedType.ArrayOf(var)); - ParsedType expected = new ParsedType.ArrayOf(replacement); - assertThat(result).isEqualTo(expected); + assertThat(result).isEqualTo(new ParsedType.ArrayOf(replacement)); } @Test void substituteAll() throws Exception { TypeVariable tv = getTypeVariable(); ParsedType.Var var = new ParsedType.Var(tv); - List types = - List.of( - var, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), var)); ParsedType replacement = new ParsedType.Const(String.class); - Map map = Map.of(var, replacement); - - List result = Unification.substituteAll(map, types); - List expected = - List.of( - replacement, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), replacement)); - assertThat(result).isEqualTo(expected); + List result = + Unification.substituteAll( + Map.of(var, replacement), + List.of( + var, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), var))); + + assertThat(result) + .isEqualTo( + List.of( + replacement, + new ParsedType.Const(Integer.class), + new ParsedType.App(new ParsedType.Const(List.class), replacement))); } // Helper to get a type variable for testing From d167c72a6297caa52f2218ac2385ed747b98a403 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:50:57 +0000 Subject: [PATCH 09/12] Inline expected variables and import ParsedType subtypes in FuncTypeTest Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/FuncTypeTest.java | 83 +++++++++---------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java index bfd038c..56fac49 100644 --- a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -2,6 +2,10 @@ import static org.assertj.core.api.Assertions.*; +import com.garciat.typeclasses.impl.ParsedType.App; +import com.garciat.typeclasses.impl.ParsedType.Const; +import com.garciat.typeclasses.impl.ParsedType.Primitive; +import com.garciat.typeclasses.impl.ParsedType.Var; import java.lang.reflect.Method; import java.util.List; import org.junit.jupiter.api.Test; @@ -10,65 +14,55 @@ final class FuncTypeTest { @Test void parseSimpleStaticMethod() throws Exception { Method method = TestMethods.class.getDeclaredMethod("simple"); - FuncType result = FuncType.parse(method); - FuncType expected = new FuncType(method, List.of(), new ParsedType.Const(String.class)); - assertThat(result).isEqualTo(expected); + assertThat(FuncType.parse(method)) + .isEqualTo(new FuncType(method, List.of(), new Const(String.class))); } @Test void parseMethodWithParameters() throws Exception { Method method = TestMethods.class.getDeclaredMethod("withParams", Integer.class, String.class); - FuncType result = FuncType.parse(method); - - FuncType expected = - new FuncType( - method, - List.of(new ParsedType.Const(Integer.class), new ParsedType.Const(String.class)), - new ParsedType.Const(Boolean.class)); - assertThat(result).isEqualTo(expected); + + assertThat(FuncType.parse(method)) + .isEqualTo( + new FuncType( + method, + List.of(new Const(Integer.class), new Const(String.class)), + new Const(Boolean.class))); } @Test void parseMethodWithGenericReturn() throws Exception { Method method = TestMethods.class.getDeclaredMethod("genericReturn"); - FuncType result = FuncType.parse(method); - - FuncType expected = - new FuncType( - method, - List.of(), - new ParsedType.App( - new ParsedType.Const(List.class), new ParsedType.Const(String.class))); - assertThat(result).isEqualTo(expected); + + assertThat(FuncType.parse(method)) + .isEqualTo( + new FuncType( + method, List.of(), new App(new Const(List.class), new Const(String.class)))); } @Test void parseMethodWithGenericParams() throws Exception { Method method = TestMethods.class.getDeclaredMethod("genericParams", List.class); - FuncType result = FuncType.parse(method); - - FuncType expected = - new FuncType( - method, - List.of( - new ParsedType.App( - new ParsedType.Const(List.class), new ParsedType.Const(Integer.class))), - new ParsedType.Primitive(void.class)); - assertThat(result).isEqualTo(expected); + + assertThat(FuncType.parse(method)) + .isEqualTo( + new FuncType( + method, + List.of(new App(new Const(List.class), new Const(Integer.class))), + new Primitive(void.class))); } @Test void parseMethodWithPrimitives() throws Exception { Method method = TestMethods.class.getDeclaredMethod("withPrimitives", int.class, boolean.class); - FuncType result = FuncType.parse(method); - - FuncType expected = - new FuncType( - method, - List.of(new ParsedType.Primitive(int.class), new ParsedType.Primitive(boolean.class)), - new ParsedType.Primitive(void.class)); - assertThat(result).isEqualTo(expected); + + assertThat(FuncType.parse(method)) + .isEqualTo( + new FuncType( + method, + List.of(new Primitive(int.class), new Primitive(boolean.class)), + new Primitive(void.class))); } @Test @@ -80,15 +74,14 @@ void parseNonStaticMethodThrows() throws Exception { @Test void parseGenericMethodWithTypeParameters() throws Exception { Method method = TestMethods.class.getDeclaredMethod("genericMethod", Object.class); - FuncType result = FuncType.parse(method); // The method has a type parameter T, so we expect a Var in the return type - FuncType expected = - new FuncType( - method, - List.of(new ParsedType.Var(method.getTypeParameters()[0])), - new ParsedType.Var(method.getTypeParameters()[0])); - assertThat(result).isEqualTo(expected); + assertThat(FuncType.parse(method)) + .isEqualTo( + new FuncType( + method, + List.of(new Var(method.getTypeParameters()[0])), + new Var(method.getTypeParameters()[0]))); } // Test helper class with various method signatures From ced34aa26816b850560a6e4fbf9be476db4ffa55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:58:16 +0000 Subject: [PATCH 10/12] Import ParsedType subtypes in ParsedTypeTest and UnificationTest Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/ParsedTypeTest.java | 35 +++-- .../typeclasses/impl/UnificationTest.java | 126 ++++++++---------- 2 files changed, 73 insertions(+), 88 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 5df989d..ff7dc1b 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -3,6 +3,11 @@ import static org.assertj.core.api.Assertions.*; import com.garciat.typeclasses.api.Ty; +import com.garciat.typeclasses.impl.ParsedType.App; +import com.garciat.typeclasses.impl.ParsedType.ArrayOf; +import com.garciat.typeclasses.impl.ParsedType.Const; +import com.garciat.typeclasses.impl.ParsedType.Primitive; +import com.garciat.typeclasses.impl.ParsedType.Var; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.List; @@ -14,25 +19,25 @@ final class ParsedTypeTest { @Test void parseClass() { ParsedType result = ParsedType.parse(String.class); - assertThat(result).isEqualTo(new ParsedType.Const(String.class)); + assertThat(result).isEqualTo(new Const(String.class)); } @Test void parsePrimitiveType() { ParsedType result = ParsedType.parse(int.class); - assertThat(result).isEqualTo(new ParsedType.Primitive(int.class)); + assertThat(result).isEqualTo(new Primitive(int.class)); } @Test void parseArrayType() { ParsedType result = ParsedType.parse(int[].class); - assertThat(result).isEqualTo(new ParsedType.ArrayOf(new ParsedType.Primitive(int.class))); + assertThat(result).isEqualTo(new ArrayOf(new Primitive(int.class))); } @Test void parseObjectArrayType() { ParsedType result = ParsedType.parse(String[].class); - assertThat(result).isEqualTo(new ParsedType.ArrayOf(new ParsedType.Const(String.class))); + assertThat(result).isEqualTo(new ArrayOf(new Const(String.class))); } @Test @@ -40,8 +45,7 @@ void parseParameterizedType() throws Exception { Type listType = new Ty>() {}.type(); ParsedType result = ParsedType.parse(listType); - ParsedType expected = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + ParsedType expected = new App(new Const(List.class), new Const(String.class)); assertThat(result).isEqualTo(expected); } @@ -51,9 +55,7 @@ void parseMultipleTypeParameters() throws Exception { ParsedType result = ParsedType.parse(mapType); ParsedType expected = - new ParsedType.App( - new ParsedType.App(new ParsedType.Const(Map.class), new ParsedType.Const(String.class)), - new ParsedType.Const(Integer.class)); + new App(new App(new Const(Map.class), new Const(String.class)), new Const(Integer.class)); assertThat(result).isEqualTo(expected); } @@ -63,10 +65,7 @@ void parseNestedParameterizedType() throws Exception { ParsedType result = ParsedType.parse(nestedType); ParsedType expected = - new ParsedType.App( - new ParsedType.Const(List.class), - new ParsedType.App( - new ParsedType.Const(Optional.class), new ParsedType.Const(String.class))); + new App(new Const(List.class), new App(new Const(Optional.class), new Const(String.class))); assertThat(result).isEqualTo(expected); } @@ -76,10 +75,7 @@ void parseAll() throws Exception { List result = ParsedType.parseAll(types); List expected = - List.of( - new ParsedType.Const(String.class), - new ParsedType.Const(Integer.class), - new ParsedType.Primitive(int.class)); + List.of(new Const(String.class), new Const(Integer.class), new Primitive(int.class)); assertThat(result).isEqualTo(expected); } @@ -90,7 +86,7 @@ class TestClass {} ParsedType result = ParsedType.parse(tv); - assertThat(result).isEqualTo(new ParsedType.Var(tv)); + assertThat(result).isEqualTo(new Var(tv)); } @Test @@ -103,8 +99,7 @@ class TestClass { ParsedType result = ParsedType.parse(fieldType); - ParsedType expected = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Var(tv)); + ParsedType expected = new App(new Const(List.class), new Var(tv)); assertThat(result).isEqualTo(expected); } diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index 9d33ee6..b101109 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -2,6 +2,11 @@ import static org.assertj.core.api.Assertions.*; +import com.garciat.typeclasses.impl.ParsedType.App; +import com.garciat.typeclasses.impl.ParsedType.ArrayOf; +import com.garciat.typeclasses.impl.ParsedType.Const; +import com.garciat.typeclasses.impl.ParsedType.Primitive; +import com.garciat.typeclasses.impl.ParsedType.Var; import com.garciat.typeclasses.types.Maybe; import java.lang.reflect.TypeVariable; import java.util.List; @@ -11,20 +16,20 @@ final class UnificationTest { @Test void unifyEqualConsts() { - ParsedType t1 = new ParsedType.Const(String.class); - ParsedType t2 = new ParsedType.Const(String.class); + ParsedType t1 = new Const(String.class); + ParsedType t2 = new Const(String.class); - Maybe> result = Unification.unify(t1, t2); + Maybe> result = Unification.unify(t1, t2); assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test void unifyDifferentConsts() { - ParsedType t1 = new ParsedType.Const(String.class); - ParsedType t2 = new ParsedType.Const(Integer.class); + ParsedType t1 = new Const(String.class); + ParsedType t2 = new Const(Integer.class); - Maybe> result = Unification.unify(t1, t2); + Maybe> result = Unification.unify(t1, t2); assertThat(result).isEqualTo(Maybe.nothing()); } @@ -32,10 +37,10 @@ void unifyDifferentConsts() { @Test void unifyVarWithConst() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType t1 = new ParsedType.Var(tv); - ParsedType t2 = new ParsedType.Const(String.class); + ParsedType t1 = new Var(tv); + ParsedType t2 = new Const(String.class); - Maybe> result = Unification.unify(t1, t2); + Maybe> result = Unification.unify(t1, t2); assertThat(result).isEqualTo(Maybe.just(Map.of(t1, t2))); } @@ -43,34 +48,30 @@ void unifyVarWithConst() throws Exception { @Test void unifyVarWithPrimitiveFails() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType t1 = new ParsedType.Var(tv); - ParsedType t2 = new ParsedType.Primitive(int.class); + ParsedType t1 = new Var(tv); + ParsedType t2 = new Primitive(int.class); - Maybe> result = Unification.unify(t1, t2); + Maybe> result = Unification.unify(t1, t2); assertThat(result).isEqualTo(Maybe.nothing()); } @Test void unifyApps() { - ParsedType list1 = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); - ParsedType list2 = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + ParsedType list1 = new App(new Const(List.class), new Const(String.class)); + ParsedType list2 = new App(new Const(List.class), new Const(String.class)); - Maybe> result = Unification.unify(list1, list2); + Maybe> result = Unification.unify(list1, list2); assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test void unifyAppsDifferentArgs() { - ParsedType list1 = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); - ParsedType list2 = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(Integer.class)); + ParsedType list1 = new App(new Const(List.class), new Const(String.class)); + ParsedType list2 = new App(new Const(List.class), new Const(Integer.class)); - Maybe> result = Unification.unify(list1, list2); + Maybe> result = Unification.unify(list1, list2); assertThat(result).isEqualTo(Maybe.nothing()); } @@ -78,60 +79,54 @@ void unifyAppsDifferentArgs() { @Test void unifyAppsWithVar() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType list1 = new ParsedType.App(new ParsedType.Const(List.class), var); - ParsedType list2 = - new ParsedType.App(new ParsedType.Const(List.class), new ParsedType.Const(String.class)); + Var var = new Var(tv); + ParsedType list1 = new App(new Const(List.class), var); + ParsedType list2 = new App(new Const(List.class), new Const(String.class)); - Maybe> result = Unification.unify(list1, list2); + Maybe> result = Unification.unify(list1, list2); - assertThat(result).isEqualTo(Maybe.just(Map.of(var, new ParsedType.Const(String.class)))); + assertThat(result).isEqualTo(Maybe.just(Map.of(var, new Const(String.class)))); } @Test void unifyNestedAppsWithVar() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType map1 = - new ParsedType.App( - new ParsedType.App(new ParsedType.Const(Map.class), var), - new ParsedType.Const(Integer.class)); + Var var = new Var(tv); + ParsedType map1 = new App(new App(new Const(Map.class), var), new Const(Integer.class)); ParsedType map2 = - new ParsedType.App( - new ParsedType.App(new ParsedType.Const(Map.class), new ParsedType.Const(String.class)), - new ParsedType.Const(Integer.class)); + new App(new App(new Const(Map.class), new Const(String.class)), new Const(Integer.class)); - Maybe> result = Unification.unify(map1, map2); + Maybe> result = Unification.unify(map1, map2); - assertThat(result).isEqualTo(Maybe.just(Map.of(var, new ParsedType.Const(String.class)))); + assertThat(result).isEqualTo(Maybe.just(Map.of(var, new Const(String.class)))); } @Test void unifyArrays() { - ParsedType arr1 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); - ParsedType arr2 = new ParsedType.ArrayOf(new ParsedType.Const(String.class)); + ParsedType arr1 = new ArrayOf(new Const(String.class)); + ParsedType arr2 = new ArrayOf(new Const(String.class)); - Maybe> result = Unification.unify(arr1, arr2); + Maybe> result = Unification.unify(arr1, arr2); assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test void unifyPrimitives() { - ParsedType p1 = new ParsedType.Primitive(int.class); - ParsedType p2 = new ParsedType.Primitive(int.class); + ParsedType p1 = new Primitive(int.class); + ParsedType p2 = new Primitive(int.class); - Maybe> result = Unification.unify(p1, p2); + Maybe> result = Unification.unify(p1, p2); assertThat(result).isEqualTo(Maybe.just(Map.of())); } @Test void unifyDifferentPrimitives() { - ParsedType p1 = new ParsedType.Primitive(int.class); - ParsedType p2 = new ParsedType.Primitive(boolean.class); + ParsedType p1 = new Primitive(int.class); + ParsedType p2 = new Primitive(boolean.class); - Maybe> result = Unification.unify(p1, p2); + Maybe> result = Unification.unify(p1, p2); assertThat(result).isEqualTo(Maybe.nothing()); } @@ -139,8 +134,8 @@ void unifyDifferentPrimitives() { @Test void substituteVar() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType replacement = new ParsedType.Const(String.class); + Var var = new Var(tv); + ParsedType replacement = new Const(String.class); ParsedType result = Unification.substitute(Map.of(var, replacement), var); @@ -149,7 +144,7 @@ void substituteVar() throws Exception { @Test void substituteConst() { - ParsedType type = new ParsedType.Const(String.class); + ParsedType type = new Const(String.class); ParsedType result = Unification.substitute(Map.of(), type); @@ -159,48 +154,43 @@ void substituteConst() { @Test void substituteApp() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType replacement = new ParsedType.Const(String.class); + Var var = new Var(tv); + ParsedType replacement = new Const(String.class); ParsedType result = - Unification.substitute( - Map.of(var, replacement), new ParsedType.App(new ParsedType.Const(List.class), var)); + Unification.substitute(Map.of(var, replacement), new App(new Const(List.class), var)); - assertThat(result).isEqualTo(new ParsedType.App(new ParsedType.Const(List.class), replacement)); + assertThat(result).isEqualTo(new App(new Const(List.class), replacement)); } @Test void substituteArrayOf() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType replacement = new ParsedType.Const(String.class); + Var var = new Var(tv); + ParsedType replacement = new Const(String.class); - ParsedType result = - Unification.substitute(Map.of(var, replacement), new ParsedType.ArrayOf(var)); + ParsedType result = Unification.substitute(Map.of(var, replacement), new ArrayOf(var)); - assertThat(result).isEqualTo(new ParsedType.ArrayOf(replacement)); + assertThat(result).isEqualTo(new ArrayOf(replacement)); } @Test void substituteAll() throws Exception { TypeVariable tv = getTypeVariable(); - ParsedType.Var var = new ParsedType.Var(tv); - ParsedType replacement = new ParsedType.Const(String.class); + Var var = new Var(tv); + ParsedType replacement = new Const(String.class); List result = Unification.substituteAll( Map.of(var, replacement), - List.of( - var, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), var))); + List.of(var, new Const(Integer.class), new App(new Const(List.class), var))); assertThat(result) .isEqualTo( List.of( replacement, - new ParsedType.Const(Integer.class), - new ParsedType.App(new ParsedType.Const(List.class), replacement))); + new Const(Integer.class), + new App(new Const(List.class), replacement))); } // Helper to get a type variable for testing From 43a45e306d5182762d922d71c175a8d717af9c6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 11:06:49 +0000 Subject: [PATCH 11/12] Replace wildcard imports with explicit imports and inline expected variables in ParsedTypeTest Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../garciat/typeclasses/TypeClassesTest.java | 3 +- .../typeclasses/impl/FuncTypeTest.java | 3 +- .../typeclasses/impl/ParsedTypeTest.java | 53 ++++++++----------- .../typeclasses/impl/UnificationTest.java | 2 +- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java index 626aaeb..5532d67 100644 --- a/src/test/java/com/garciat/typeclasses/TypeClassesTest.java +++ b/src/test/java/com/garciat/typeclasses/TypeClassesTest.java @@ -1,7 +1,8 @@ package com.garciat.typeclasses; import static com.garciat.typeclasses.TypeClasses.witness; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.garciat.typeclasses.api.Ctx; import com.garciat.typeclasses.api.Ty; diff --git a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java index 56fac49..0fcd868 100644 --- a/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/FuncTypeTest.java @@ -1,6 +1,7 @@ package com.garciat.typeclasses.impl; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.garciat.typeclasses.impl.ParsedType.App; import com.garciat.typeclasses.impl.ParsedType.Const; diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index ff7dc1b..5466b29 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -1,6 +1,7 @@ package com.garciat.typeclasses.impl; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.garciat.typeclasses.api.Ty; import com.garciat.typeclasses.impl.ParsedType.App; @@ -18,65 +19,60 @@ final class ParsedTypeTest { @Test void parseClass() { - ParsedType result = ParsedType.parse(String.class); - assertThat(result).isEqualTo(new Const(String.class)); + assertThat(ParsedType.parse(String.class)).isEqualTo(new Const(String.class)); } @Test void parsePrimitiveType() { - ParsedType result = ParsedType.parse(int.class); - assertThat(result).isEqualTo(new Primitive(int.class)); + assertThat(ParsedType.parse(int.class)).isEqualTo(new Primitive(int.class)); } @Test void parseArrayType() { - ParsedType result = ParsedType.parse(int[].class); - assertThat(result).isEqualTo(new ArrayOf(new Primitive(int.class))); + assertThat(ParsedType.parse(int[].class)).isEqualTo(new ArrayOf(new Primitive(int.class))); } @Test void parseObjectArrayType() { - ParsedType result = ParsedType.parse(String[].class); - assertThat(result).isEqualTo(new ArrayOf(new Const(String.class))); + assertThat(ParsedType.parse(String[].class)).isEqualTo(new ArrayOf(new Const(String.class))); } @Test void parseParameterizedType() throws Exception { Type listType = new Ty>() {}.type(); - ParsedType result = ParsedType.parse(listType); - ParsedType expected = new App(new Const(List.class), new Const(String.class)); - assertThat(result).isEqualTo(expected); + assertThat(ParsedType.parse(listType)) + .isEqualTo(new App(new Const(List.class), new Const(String.class))); } @Test void parseMultipleTypeParameters() throws Exception { Type mapType = new Ty>() {}.type(); - ParsedType result = ParsedType.parse(mapType); - ParsedType expected = - new App(new App(new Const(Map.class), new Const(String.class)), new Const(Integer.class)); - assertThat(result).isEqualTo(expected); + assertThat(ParsedType.parse(mapType)) + .isEqualTo( + new App( + new App(new Const(Map.class), new Const(String.class)), new Const(Integer.class))); } @Test void parseNestedParameterizedType() throws Exception { Type nestedType = new Ty>>() {}.type(); - ParsedType result = ParsedType.parse(nestedType); - ParsedType expected = - new App(new Const(List.class), new App(new Const(Optional.class), new Const(String.class))); - assertThat(result).isEqualTo(expected); + assertThat(ParsedType.parse(nestedType)) + .isEqualTo( + new App( + new Const(List.class), + new App(new Const(Optional.class), new Const(String.class)))); } @Test void parseAll() throws Exception { Type[] types = {String.class, Integer.class, int.class}; - List result = ParsedType.parseAll(types); - List expected = - List.of(new Const(String.class), new Const(Integer.class), new Primitive(int.class)); - assertThat(result).isEqualTo(expected); + assertThat(ParsedType.parseAll(types)) + .isEqualTo( + List.of(new Const(String.class), new Const(Integer.class), new Primitive(int.class))); } @Test @@ -84,9 +80,7 @@ void parseTypeVariable() throws Exception { class TestClass {} TypeVariable tv = TestClass.class.getTypeParameters()[0]; - ParsedType result = ParsedType.parse(tv); - - assertThat(result).isEqualTo(new Var(tv)); + assertThat(ParsedType.parse(tv)).isEqualTo(new Var(tv)); } @Test @@ -97,10 +91,7 @@ class TestClass { Type fieldType = TestClass.class.getDeclaredField("field").getGenericType(); TypeVariable tv = TestClass.class.getTypeParameters()[0]; - ParsedType result = ParsedType.parse(fieldType); - - ParsedType expected = new App(new Const(List.class), new Var(tv)); - assertThat(result).isEqualTo(expected); + assertThat(ParsedType.parse(fieldType)).isEqualTo(new App(new Const(List.class), new Var(tv))); } @Test diff --git a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java index b101109..f83fa48 100644 --- a/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/UnificationTest.java @@ -1,6 +1,6 @@ package com.garciat.typeclasses.impl; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import com.garciat.typeclasses.impl.ParsedType.App; import com.garciat.typeclasses.impl.ParsedType.ArrayOf; From 72a4ec6f2a0a3ba84df1ae1de05086d06a4ac87b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 11:18:51 +0000 Subject: [PATCH 12/12] Inline remaining variables in ParsedTypeTest (listType, mapType, nestedType, types, fieldType) Co-authored-by: Garciat <118277+Garciat@users.noreply.github.com> --- .../typeclasses/impl/ParsedTypeTest.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java index 5466b29..66f12fe 100644 --- a/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java +++ b/src/test/java/com/garciat/typeclasses/impl/ParsedTypeTest.java @@ -39,17 +39,13 @@ void parseObjectArrayType() { @Test void parseParameterizedType() throws Exception { - Type listType = new Ty>() {}.type(); - - assertThat(ParsedType.parse(listType)) + assertThat(ParsedType.parse(new Ty>() {}.type())) .isEqualTo(new App(new Const(List.class), new Const(String.class))); } @Test void parseMultipleTypeParameters() throws Exception { - Type mapType = new Ty>() {}.type(); - - assertThat(ParsedType.parse(mapType)) + assertThat(ParsedType.parse(new Ty>() {}.type())) .isEqualTo( new App( new App(new Const(Map.class), new Const(String.class)), new Const(Integer.class))); @@ -57,9 +53,7 @@ void parseMultipleTypeParameters() throws Exception { @Test void parseNestedParameterizedType() throws Exception { - Type nestedType = new Ty>>() {}.type(); - - assertThat(ParsedType.parse(nestedType)) + assertThat(ParsedType.parse(new Ty>>() {}.type())) .isEqualTo( new App( new Const(List.class), @@ -68,9 +62,7 @@ void parseNestedParameterizedType() throws Exception { @Test void parseAll() throws Exception { - Type[] types = {String.class, Integer.class, int.class}; - - assertThat(ParsedType.parseAll(types)) + assertThat(ParsedType.parseAll(new Type[] {String.class, Integer.class, int.class})) .isEqualTo( List.of(new Const(String.class), new Const(Integer.class), new Primitive(int.class))); } @@ -88,10 +80,10 @@ void parseTypeVariableInParameterizedType() throws Exception { class TestClass { List field; } - Type fieldType = TestClass.class.getDeclaredField("field").getGenericType(); TypeVariable tv = TestClass.class.getTypeParameters()[0]; - assertThat(ParsedType.parse(fieldType)).isEqualTo(new App(new Const(List.class), new Var(tv))); + assertThat(ParsedType.parse(TestClass.class.getDeclaredField("field").getGenericType())) + .isEqualTo(new App(new Const(List.class), new Var(tv))); } @Test @@ -99,9 +91,9 @@ void parseWildcardTypeThrows() throws Exception { class TestClass { List field; } - Type fieldType = TestClass.class.getDeclaredField("field").getGenericType(); - assertThatThrownBy(() -> ParsedType.parse(fieldType)) + assertThatThrownBy( + () -> ParsedType.parse(TestClass.class.getDeclaredField("field").getGenericType())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("wildcard"); }