Prevent circular secondary constructors delegation
This commit is contained in:
@@ -153,6 +153,10 @@ public interface Errors {
|
||||
DiagnosticFactory0<JetNullableType> NULLABLE_SUPERTYPE = DiagnosticFactory0.create(ERROR, NULLABLE_TYPE);
|
||||
DiagnosticFactory0<JetTypeReference> DYNAMIC_SUPERTYPE = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
// Secondary constructors
|
||||
|
||||
DiagnosticFactory0<JetConstructorDelegationCall> CYCLIC_CONSTRUCTOR_DELEGATION_CALL = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
// Trait-specific
|
||||
|
||||
DiagnosticFactory0<JetModifierListOwner> ABSTRACT_MODIFIER_IN_TRAIT = DiagnosticFactory0
|
||||
|
||||
+2
@@ -413,6 +413,8 @@ public class DefaultErrorMessages {
|
||||
MAP.put(FINAL_SUPERTYPE, "This type is final, so it cannot be inherited from");
|
||||
MAP.put(SINGLETON_IN_SUPERTYPE, "Cannot inherit from a singleton");
|
||||
|
||||
MAP.put(CYCLIC_CONSTRUCTOR_DELEGATION_CALL, "There's a cycle in the delegation calls chain");
|
||||
|
||||
MAP.put(ILLEGAL_SELECTOR, "Expression ''{0}'' cannot be a selector (occur after a dot)", STRING);
|
||||
|
||||
MAP.put(NO_TAIL_CALLS_FOUND, "A function is marked as tail-recursive but no tail calls are found");
|
||||
|
||||
@@ -127,6 +127,11 @@ public class BodyResolver {
|
||||
for (Map.Entry<JetSecondaryConstructor, ConstructorDescriptor> entry : c.getSecondaryConstructors().entrySet()) {
|
||||
resolveSecondaryConstructorBody(c, entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (c.getSecondaryConstructors().isEmpty()) return;
|
||||
Set<ConstructorDescriptor> visitedConstructors = Sets.newHashSet();
|
||||
for (Map.Entry<JetSecondaryConstructor, ConstructorDescriptor> entry : c.getSecondaryConstructors().entrySet()) {
|
||||
checkCyclicConstructorDelegationCall(entry.getValue(), visitedConstructors);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveSecondaryConstructorBody(
|
||||
@@ -174,6 +179,58 @@ public class BodyResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCyclicConstructorDelegationCall(
|
||||
@NotNull ConstructorDescriptor constructorDescriptor,
|
||||
@NotNull Set<ConstructorDescriptor> visitedConstructors
|
||||
) {
|
||||
if (visitedConstructors.contains(constructorDescriptor)) return;
|
||||
|
||||
// if visit constructor that is already in current chain
|
||||
// such constructor is on cycle
|
||||
Set<ConstructorDescriptor> visitedInCurrentChain = Sets.newHashSet();
|
||||
ConstructorDescriptor currentConstructorDescriptor = constructorDescriptor;
|
||||
while (true) {
|
||||
visitedInCurrentChain.add(currentConstructorDescriptor);
|
||||
ConstructorDescriptor delegatedConstructorDescriptor = getDelegatedConstructor(currentConstructorDescriptor);
|
||||
if (delegatedConstructorDescriptor == null) break;
|
||||
|
||||
// if next delegation call is super or primary constructor or already visited
|
||||
if (!constructorDescriptor.getContainingDeclaration().equals(delegatedConstructorDescriptor.getContainingDeclaration()) ||
|
||||
delegatedConstructorDescriptor.isPrimary() ||
|
||||
visitedConstructors.contains(delegatedConstructorDescriptor)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (visitedInCurrentChain.contains(delegatedConstructorDescriptor)) {
|
||||
reportEachConstructorOnCycle(delegatedConstructorDescriptor);
|
||||
break;
|
||||
}
|
||||
currentConstructorDescriptor = delegatedConstructorDescriptor;
|
||||
}
|
||||
visitedConstructors.addAll(visitedInCurrentChain);
|
||||
}
|
||||
|
||||
private void reportEachConstructorOnCycle(@NotNull ConstructorDescriptor startConstructor) {
|
||||
ConstructorDescriptor currentConstructor = startConstructor;
|
||||
do {
|
||||
PsiElement constructorToReport = DescriptorToSourceUtils.descriptorToDeclaration(currentConstructor);
|
||||
if (constructorToReport != null) {
|
||||
JetConstructorDelegationCall call = ((JetSecondaryConstructor) constructorToReport).getDelegationCall();
|
||||
assert call != null : "resolved call can't be null";
|
||||
trace.report(CYCLIC_CONSTRUCTOR_DELEGATION_CALL.on(call));
|
||||
}
|
||||
|
||||
currentConstructor = getDelegatedConstructor(currentConstructor);
|
||||
assert currentConstructor != null : "Delegated constructor should not be null in cycle";
|
||||
} while (startConstructor != currentConstructor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ConstructorDescriptor getDelegatedConstructor(@NotNull ConstructorDescriptor constructor) {
|
||||
ResolvedCall<ConstructorDescriptor> call = trace.get(CONSTRUCTOR_RESOLVED_DELEGATION_CALL, constructor);
|
||||
return call == null ? null : call.getResultingDescriptor();
|
||||
}
|
||||
|
||||
public void resolveBodies(@NotNull BodiesResolveContext c) {
|
||||
resolveBehaviorDeclarationBodies(c);
|
||||
controlFlowAnalyzer.process(c);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
class A1 {
|
||||
constructor(): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this()<!> {}
|
||||
}
|
||||
|
||||
class A2(x: Byte) {
|
||||
constructor(x1: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, 1)<!> {}
|
||||
constructor(x1: Int, x2: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, 2)<!> {}
|
||||
constructor(x1: Int, x2: Int, x3: Int): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1)<!> {}
|
||||
|
||||
// delegating to previously declared cycle
|
||||
constructor(x1: Double): this(1) {}
|
||||
|
||||
|
||||
// delegating to cycle declared after
|
||||
constructor(x1: String): this(1L) {}
|
||||
|
||||
constructor(x1: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, 1L)<!> {}
|
||||
constructor(x1: Long, x2: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, 2L)<!> {}
|
||||
constructor(x1: Long, x2: Long, x3: Long): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1)<!> {}
|
||||
|
||||
// no cycle, just call to primary constuctor
|
||||
constructor(x1: Double, x2: Double): this(x1, x2, 1.0) {}
|
||||
constructor(x1: Double, x2: Double, x3: Double): this(x1, x2, x3, 1.0) {}
|
||||
constructor(x1: Double, x2: Double, x3: Double, x4: Double): this(1.toByte()) {}
|
||||
|
||||
constructor(): this("x", "y") {}
|
||||
|
||||
constructor(x1: String, x2: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2, "")<!> {}
|
||||
constructor(x1: String, x2: String, x3: String): <!CYCLIC_CONSTRUCTOR_DELEGATION_CALL!>this(x1, x2)<!> {}
|
||||
}
|
||||
|
||||
open class B(x: Byte)
|
||||
class A : B {
|
||||
// no cycle, just call to super constuctor
|
||||
constructor(x1: Double, x2: Double): this(x1, x2, 1.0) {}
|
||||
constructor(x1: Double, x2: Double, x3: Double): this(x1, x2, x3, 1.0) {}
|
||||
constructor(x1: Double, x2: Double, x3: Double, x4: Double): super(1.toByte()) {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package
|
||||
|
||||
internal final class A : B {
|
||||
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double)
|
||||
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double)
|
||||
public constructor A(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double, /*3*/ x4: kotlin.Double)
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
internal final class A1 {
|
||||
public constructor A1()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
internal final class A2 {
|
||||
public constructor A2()
|
||||
public constructor A2(/*0*/ x: kotlin.Byte)
|
||||
public constructor A2(/*0*/ x1: kotlin.Double)
|
||||
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double)
|
||||
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double)
|
||||
public constructor A2(/*0*/ x1: kotlin.Double, /*1*/ x2: kotlin.Double, /*2*/ x3: kotlin.Double, /*3*/ x4: kotlin.Double)
|
||||
public constructor A2(/*0*/ x1: kotlin.Int)
|
||||
public constructor A2(/*0*/ x1: kotlin.Int, /*1*/ x2: kotlin.Int)
|
||||
public constructor A2(/*0*/ x1: kotlin.Int, /*1*/ x2: kotlin.Int, /*2*/ x3: kotlin.Int)
|
||||
public constructor A2(/*0*/ x1: kotlin.Long)
|
||||
public constructor A2(/*0*/ x1: kotlin.Long, /*1*/ x2: kotlin.Long)
|
||||
public constructor A2(/*0*/ x1: kotlin.Long, /*1*/ x2: kotlin.Long, /*2*/ x3: kotlin.Long)
|
||||
public constructor A2(/*0*/ x1: kotlin.String)
|
||||
public constructor A2(/*0*/ x1: kotlin.String, /*1*/ x2: kotlin.String)
|
||||
public constructor A2(/*0*/ x1: kotlin.String, /*1*/ x2: kotlin.String, /*2*/ x3: kotlin.String)
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
internal open class B {
|
||||
public constructor B(/*0*/ x: kotlin.Byte)
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
@@ -10415,6 +10415,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("cyclicDelegationCalls.kt")
|
||||
public void testCyclicDelegationCalls() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/cyclicDelegationCalls.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("generics.kt")
|
||||
public void testGenerics() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/generics.kt");
|
||||
|
||||
Reference in New Issue
Block a user