Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
92 changes: 92 additions & 0 deletions src/test/java/test/TestAnnotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading