From 5f9e8852093997408cdb638d41798e080ec59b42 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 06:46:32 +0000 Subject: [PATCH] feat(compiler): autorise la covariance du type de retour des overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Une méthode qui en surcharge une autre peut désormais réduire son type de retour à une sous-classe (covariance), comme en Java, au lieu d'exiger un type strictement identique. Les types des paramètres restent invariants. La covariance est limitée aux types classe, seul cas que le Java généré peut représenter via un retour covariant (u_Enfant extends u_Parent) ; Java n'autorise pas non plus la covariance entre primitifs (double -> long). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Mvgkm33dCbqx9fcGnhVk3E --- .../ClassDeclarationInstruction.java | 37 +++++++- src/test/java/test/TestAnnotations.java | 92 +++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/main/java/leekscript/compiler/instruction/ClassDeclarationInstruction.java b/src/main/java/leekscript/compiler/instruction/ClassDeclarationInstruction.java index dae691d8..2ab0410d 100644 --- a/src/main/java/leekscript/compiler/instruction/ClassDeclarationInstruction.java +++ b/src/main/java/leekscript/compiler/instruction/ClassDeclarationInstruction.java @@ -486,10 +486,41 @@ public void analyze(WordCompiler compiler) throws LeekCompilerException { var parentVersion = parentMethod.get(version.getKey()); if (parentVersion != null) { foundInParent = true; - if (block.getType().accepts(parentVersion.block.getType()) != CastType.EQUALS) { + // Compatibilité de type entre la méthode surchargée et celle du parent, + // comme en Java : + // - le type de retour peut être réduit (covariance) : l'enfant peut + // retourner un sous-type de ce que retourne le parent ; + // - les types des paramètres restent invariants (doivent être identiques). + var childType = block.getType(); + var parentType = parentVersion.block.getType(); + var parentReturn = parentType.returnType(); + var childReturn = childType.returnType(); + + // Type de retour : identique, ou réduit à une sous-classe (covariance). + // On limite la covariance aux types classe : c'est le seul cas que le + // Java généré peut représenter (u_Enfant extends u_Parent permet un + // retour covariant). Java n'autorise pas non plus la covariance entre + // primitifs (ex. double -> long), on reste donc cohérent. + var returnCast = parentReturn.accepts(childReturn); + boolean compatible = returnCast == CastType.EQUALS + || (returnCast == CastType.UPCAST && parentReturn instanceof ClassType && childReturn instanceof ClassType); + + // Paramètres : chaque type doit rester identique (invariant). + if (compatible) { + var parentArgs = parentType.getArguments(); + var childArgs = childType.getArguments(); + for (int a = 0; a < Math.min(parentArgs.size(), childArgs.size()); ++a) { + if (parentArgs.get(a).accepts(childArgs.get(a)) != CastType.EQUALS) { + compatible = false; + break; + } + } + } + + if (!compatible) { compiler.addError(new AnalyzeError(block.getLocation(), AnalyzeErrorLevel.ERROR, Error.OVERRIDDEN_METHOD_DIFFERENT_TYPE, new String[] { - block.getType().toString(), - parentVersion.block.getType().toString() + childType.toString(), + parentType.toString() })); } // Une override ne peut pas réduire la visibilité (LSP : ce qui est public dans le parent doit le rester). diff --git a/src/test/java/test/TestAnnotations.java b/src/test/java/test/TestAnnotations.java index 9b85f018..1ffba734 100644 --- a/src/test/java/test/TestAnnotations.java +++ b/src/test/java/test/TestAnnotations.java @@ -133,6 +133,98 @@ class Dog extends Animal { } return 0; """).error(Error.ANNOTATION_OVERRIDE_NO_PARENT); + + section("@override — covariant return type"); + + // Narrowing the return type (covariance, like Java): the child may return a + // subtype of what the parent returns. Here self() narrows Animal → Dog, so the + // statically-typed result can call a Dog-only method. + code_v4_(""" + class Animal { + Animal self() { return this; } + } + class Dog extends Animal { + @override + Dog self() { return this; } + bark() { return "woof"; } + } + return new Dog().self().bark(); + """).equals("\"woof\""); + + // Covariance is limited to class types, like Java: a primitive return type + // cannot be narrowed (Java forbids real → integer covariant returns too). + code_v4_(""" + class A { + real get() { return 1.5; } + } + class B extends A { + @override + integer get() { return 2; } + } + return new B().get(); + """).error(Error.OVERRIDDEN_METHOD_DIFFERENT_TYPE); + + // Covariance over several levels of inheritance. + code_v4_(""" + class Animal { + Animal self() { return this; } + } + class Dog extends Animal {} + class Puppy extends Dog { + @override + Puppy self() { return this; } + yip() { return "yip"; } + } + return new Puppy().self().yip(); + """).equals("\"yip\""); + + // Identical return type is still accepted. + code_v4_(""" + class Animal { + Animal self() { return this; } + } + class Dog extends Animal { + @override + Animal self() { return this; } + } + return new Dog().self() == null ? 0 : 1; + """).equals("1"); + + // Widening the return type (supertype) is rejected. + code_v4_(""" + class Animal { + Dog get() { return new Dog(); } + } + class Dog extends Animal { + @override + Animal get() { return new Animal(); } + } + return 0; + """).error(Error.OVERRIDDEN_METHOD_DIFFERENT_TYPE); + + // Unrelated return type is rejected. + code_v4_(""" + class Animal { + integer get() { return 1; } + } + class Dog extends Animal { + @override + string get() { return "x"; } + } + return 0; + """).error(Error.OVERRIDDEN_METHOD_DIFFERENT_TYPE); + + // Parameter types remain invariant (like Java): changing them is rejected. + code_v4_(""" + class Animal { + set(integer x) { return x; } + } + class Dog extends Animal { + @override + set(string x) { return x; } + } + return 0; + """).error(Error.OVERRIDDEN_METHOD_DIFFERENT_TYPE); } @Test