eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
157 stars 125 forks source link

A sealed interface with generic causes IllegalStateException and nothing can be done then #3009

Open NolwennD opened 3 days ago

NolwennD commented 3 days ago

eclipse.buildId=4.33.0.20240905-0613 java.version=21.0.4 java.vendor=Eclipse Adoptium

The behavior is the same on previous releases (march or june)

The following code leads to more or less freeze UI with error pop-up.

package fail;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

public sealed interface Editable<T> permits Editable.NotEdited, Editable.Edited {

  static <T> Editable<T> notEdited() {
    return (Editable<T>) NotEdited.NOT_EDITED;
  }

  static <T> Editable<T> emptyValue() {
    return (Editable<T>) Edited.EditedWithoutValue.NO_VALUE;
  }

  static <T> Editable<T> of(T value) {
    return new Edited.EditedWithValue<>(value);
  }

  static <T> Editable<T> ofNullable(T value) {
    if (value == null) {
      return emptyValue();
    }

    return new Edited.EditedWithValue<>(value);
  }

  default boolean isEdited() {
    return this instanceof Editable.Edited<T>;
  }

  default boolean isNotEdited() {
    return this instanceof Editable.NotEdited<T>;
  }

  default Optional<T> editedValue() {
    return switch (this) {
      case Edited<T> edited -> Optional.of(edited).flatMap(Edited::value);
      case NotEdited<T> ignored -> Optional.empty();
    };
  }

  boolean hasNotChanged(T otherValue);

  Optional<T> or(Supplier<Optional<T>> supplier);

  void ifEdited(Consumer<Optional<T>> action);

  final class NotEdited<T> implements Editable<T> {

    private static final Editable<?> NOT_EDITED = new NotEdited<>();

    private NotEdited() {
    }

    @Override
    public boolean hasNotChanged(T otherValue) {
      return true;
    }

    @Override
    public Optional<T> or(Supplier<Optional<T>> supplier) {
      Objects.requireNonNull(supplier);

      return supplier.get();
    }

    @Override
    public void ifEdited(Consumer<Optional<T>> action) {
      // Nothing to do
    }
  }

  abstract sealed class Edited<T> implements Editable<T> permits Edited.EditedWithoutValue, Edited.EditedWithValue {

    protected abstract Optional<T> value();

    @Override
    public void ifEdited(Consumer<Optional<T>> action) {
      Objects.requireNonNull(action);

      action.accept(value());
    }

    @Override
    public Optional<T> or(Supplier<Optional<T>> supplier) {
      return value();
    }

    static final class EditedWithoutValue<T> extends Edited<T> {

      private static final Edited<?> NO_VALUE = new EditedWithoutValue<>();

      private EditedWithoutValue() {
      }

      @Override
      protected Optional<T> value() {
        return Optional.empty();
      }

      @Override
      public boolean hasNotChanged(T otherValue) {
        return otherValue == null;
      }
    }

    static final class EditedWithValue<T> extends Edited<T> {

      private final T value;

      private EditedWithValue(T value) {
        this.value = Objects.requireNonNull(value);
      }

      @Override
      protected Optional<T> value() {
        return Optional.of(value);
      }

      @Override
      public boolean hasNotChanged(T otherValue) {
        return Objects.equals(value, otherValue);
      }
    }
  }
}

And some logs

java.lang.IllegalStateException
    at org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.<init>(ParameterizedTypeBinding.java:86)
    at org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding.permittedTypes(ParameterizedTypeBinding.java:1120)
    at org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding.getAllEnumerableReferenceTypes(ReferenceBinding.java:2564)
    at org.eclipse.jdt.internal.compiler.ast.SwitchStatement.checkAndFlagDefaultSealed(SwitchStatement.java:1376)
    at org.eclipse.jdt.internal.compiler.ast.SwitchStatement.resolve(SwitchStatement.java:1247)
    at org.eclipse.jdt.internal.compiler.ast.SwitchExpression.resolveType(SwitchExpression.java:400)
    at org.eclipse.jdt.internal.compiler.ast.ReturnStatement.resolve(ReturnStatement.java:362)
    at org.eclipse.jdt.internal.compiler.ast.Statement.resolveWithBindings(Statement.java:498)
    at org.eclipse.jdt.internal.compiler.ast.ASTNode.resolveStatements(ASTNode.java:725)
    at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolveStatements(AbstractMethodDeclaration.java:713)
    at org.eclipse.jdt.internal.compiler.ast.MethodDeclaration.resolveStatements(MethodDeclaration.java:409)
    at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolve(AbstractMethodDeclaration.java:611)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1514)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1643)
    at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:666)
    at org.eclipse.jdt.internal.compiler.Compiler.resolve(Compiler.java:1088)
    at org.eclipse.jdt.internal.compiler.Compiler.resolve(Compiler.java:1130)
    at org.eclipse.jdt.internal.core.CompilationUnitProblemFinder.process(CompilationUnitProblemFinder.java:280)
    at org.eclipse.jdt.internal.core.CompilationUnitProblemFinder.process(CompilationUnitProblemFinder.java:346)
    at org.eclipse.jdt.internal.core.ReconcileWorkingCopyOperation.makeConsistent(ReconcileWorkingCopyOperation.java:186)
    at org.eclipse.jdt.internal.core.ReconcileWorkingCopyOperation.executeOperation(ReconcileWorkingCopyOperation.java:92)
    at org.eclipse.jdt.internal.core.JavaModelOperation.run(JavaModelOperation.java:739)
    at org.eclipse.jdt.internal.core.JavaModelOperation.runOperation(JavaModelOperation.java:804)
    at org.eclipse.jdt.internal.core.CompilationUnit.reconcile(CompilationUnit.java:1303)
    at org.eclipse.jdt.internal.ui.text.java.JavaReconcilingStrategy.reconcile(JavaReconcilingStrategy.java:132)
    at org.eclipse.jdt.internal.ui.text.java.JavaReconcilingStrategy$1.run(JavaReconcilingStrategy.java:94)
    at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:47)
    at org.eclipse.jdt.internal.ui.text.java.JavaReconcilingStrategy.reconcile(JavaReconcilingStrategy.java:91)
    at org.eclipse.jdt.internal.ui.text.java.JavaReconcilingStrategy.reconcile(JavaReconcilingStrategy.java:158)
    at org.eclipse.jdt.internal.ui.text.CompositeReconcilingStrategy.reconcile(CompositeReconcilingStrategy.java:94)
    at org.eclipse.jdt.internal.ui.text.JavaCompositeReconcilingStrategy.reconcile(JavaCompositeReconcilingStrategy.java:107)
    at org.eclipse.jface.text.reconciler.MonoReconciler.process(MonoReconciler.java:78)
    at org.eclipse.jface.text.reconciler.AbstractReconciler$BackgroundThread.run(AbstractReconciler.java:207)
srikanth-sankaran commented 3 days ago

Reproduced. Smaller test case that compiles with javac and fails with ECJ:

public sealed interface X<T> permits X.Y {

    default Object foo() {
        return switch (this) {
                    case X<T> x -> null;
               };
    }

    sealed class Y<T> implements X<T> permits Y.Z {
        static final class Z<T> extends Y<T> {}
    }
}