All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aya.tyck.pat.PatClassifier Maven / Gradle / Ivy

There is a newer version: 0.36.0
Show newest version
// Copyright (c) 2020-2024 Tesla (Yinsen) Zhang.
// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file.
package org.aya.tyck.pat;

import kala.collection.Seq;
import kala.collection.SeqView;
import kala.collection.immutable.ImmutableSeq;
import kala.collection.immutable.primitive.ImmutableIntSeq;
import kala.collection.mutable.MutableArrayList;
import kala.collection.mutable.MutableList;
import kala.control.Result;
import org.aya.generic.State;
import org.aya.generic.term.DTKind;
import org.aya.pretty.doc.Doc;
import org.aya.syntax.core.def.ConDefLike;
import org.aya.syntax.core.pat.Pat;
import org.aya.syntax.core.pat.PatToTerm;
import org.aya.syntax.core.term.*;
import org.aya.syntax.core.term.call.ConCall;
import org.aya.syntax.core.term.call.ConCallLike;
import org.aya.syntax.core.term.call.DataCall;
import org.aya.syntax.ref.LocalVar;
import org.aya.tyck.TyckState;
import org.aya.tyck.error.ClausesProblem;
import org.aya.tyck.tycker.AbstractTycker;
import org.aya.tyck.tycker.Problematic;
import org.aya.tyck.tycker.Stateful;
import org.aya.util.Pair;
import org.aya.util.error.SourceNode;
import org.aya.util.error.SourcePos;
import org.aya.util.reporter.Reporter;
import org.aya.util.tyck.pat.ClassifierUtil;
import org.aya.util.tyck.pat.Indexed;
import org.aya.util.tyck.pat.PatClass;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Comparator;
import java.util.stream.Collectors;

public record PatClassifier(
  @NotNull AbstractTycker delegate, @NotNull SourcePos pos
) implements ClassifierUtil, Term, Param, Pat>, Stateful, Problematic {
  @Override public Param subst(ImmutableSeq subst, Param param) {
    return param.instTele(subst.view());
  }
  @Override public @NotNull TyckState state() { return delegate.state(); }
  @Override public @NotNull Reporter reporter() { return delegate.reporter(); }
  @Override public Pat normalize(Pat pat) { return pat.inline((_, _) -> { }); }
  @Override public ImmutableSeq add(ImmutableSeq subst, Term term) {
    return subst.appended(term);
  }

  public static @NotNull ImmutableSeq>> classify(
    @NotNull SeqView> clauses,
    @NotNull SeqView telescope, @NotNull AbstractTycker tycker,
    @NotNull SourcePos pos
  ) {
    var classifier = new PatClassifier(tycker, pos);
    var cl = classifier.classifyN(ImmutableSeq.empty(), telescope, clauses
      .mapIndexed((i, clause) -> new Indexed<>(clause.pats().view(), i))
      .toImmutableSeq(), 4);
    var p = cl.partition(c -> c.cls().isEmpty());
    var missing = p.component1();
    if (missing.isNotEmpty()) tycker.fail(
      new ClausesProblem.MissingCase(pos, missing));
    return p.component2();
  }

  @Override public @NotNull ImmutableSeq> classify1(
    @NotNull ImmutableSeq subst, @NotNull Param param,
    @NotNull ImmutableSeq> clauses, int fuel
  ) {
    var whnfTy = whnf(param.type());
    switch (whnfTy) {
      // Note that we cannot have ill-typed patterns such as constructor patterns under sigma,
      // since patterns here are already well-typed
      case DepTypeTerm(var kind, var lT, var rT) when kind == DTKind.Sigma -> {
        // The type is sigma type, and do we have any non-catchall patterns?
        // In case we do,
        if (clauses.anyMatch(i -> i.pat() instanceof Pat.Tuple)) {
          var matches = clauses.mapIndexedNotNull((i, subPat) -> switch (subPat.pat()) {
            case Pat.Tuple(var l, var r) -> new Indexed<>(new Pair<>(l, r), i);
            case Pat.Bind b -> {
              var name = b.bind().name();
              var ref = LocalVar.generate(name + ".1");
              yield new Indexed<>(new Pair(new Pat.Bind(ref, lT),
                new Pat.Bind(LocalVar.generate(name + ".2"), rT.apply(ref))), i);
            }
            default -> null;
          });
          var classes = classify2(subst, new Param("1", lT, true),
            term -> new Param("2", rT.apply(term), true), matches, fuel);
          // ^ the licit shall not matter
          return classes.map(args -> new PatClass<>(new TupTerm(
            args.term().component1(), args.term().component2()), args.cls()));
        }
      }
      // THE BIG GAME
      case DataCall dataCall -> {
        // In case clauses are empty, we're just making sure that the type is uninhabited,
        // so proceed as if we have valid patterns
        if (clauses.isNotEmpty() &&
          // there are no clauses starting with a constructor pattern -- we don't need a split!
          clauses.noneMatch(subPat -> subPat.pat() instanceof Pat.Con || subPat.pat() instanceof Pat.ShapedInt)
        ) break;
        var body = dataCall.ref().body();

        // Special optimization for literals
        var lits = clauses.mapNotNull(cl -> cl.pat() instanceof Pat.ShapedInt i ?
          new Indexed<>(i, cl.ix()) : null);
        var binds = Indexed.indices(clauses.filter(cl -> cl.pat() instanceof Pat.Bind));
        if (clauses.isNotEmpty() && lits.size() + binds.size() == clauses.size()) {
          // There is only literals and bind patterns, no constructor patterns
          var classes = ImmutableSeq.from(lits.collect(
              Collectors.groupingBy(i -> i.pat().repr())).values())
            .map(i -> new PatClass<>(PatToTerm.visit(i.getFirst().pat()),
              Indexed.indices(Seq.wrapJava(i)).concat(binds)));
          var ml = MutableArrayList.>create(classes.size() + 1);
          ml.appendAll(classes);
          var maxInt = lits.max(Comparator.comparing(p -> p.pat().repr())).pat();
          var onePlus = maxInt.map(x -> x + 1).toTerm();
          ml.append(new PatClass<>(onePlus, binds));
          return ml.toImmutableSeq();
        }

        var buffer = MutableList.>create();
        var missedCon = 0;
        // For all constructors,
        for (var con : body) {
          var fuel1 = fuel;
          var conTeleResult = conTele(clauses, dataCall, con);
          if (conTeleResult == null) continue;
          var conTele = conTeleResult.tele;
          // Find all patterns that are either catchall or splitting on this constructor,
          // e.g. for `suc`, `suc (suc a)` will be picked
          var matches = clauses.mapIndexedNotNull((ix, subPat) ->
            // Convert to constructor form
            matches(conTele, con, ix, subPat));
          var conHead = new ConCallLike.Head(con, dataCall.ulift(), conTeleResult.ownerArgs);
          // The only matching cases are catch-all cases, and we skip these
          if (matches.isEmpty()) {
            missedCon++;
            fuel1--;
            // In this case we give up and do not split on this constructor
            if (conTele.isEmpty() || fuel1 <= 0) {
              var err = new ErrorTerm(Doc.plain("..."), false);
              var missingCon = new ConCall(conHead, conTele.isEmpty() ? ImmutableSeq.empty() : ImmutableSeq.of(err));
              buffer.append(new PatClass<>(missingCon, ImmutableIntSeq.empty()));
              continue;
            }
          }
          var classes = classifyN(subst, conTele.view(), matches, fuel1);
          buffer.appendAll(classes.map(args ->
            new PatClass<>(new ConCall(conHead, args.term()), args.cls())));
        }
        // If we missed all constructors, we combine the cases to a catch-all case
        if (missedCon >= body.size()) {
          return ImmutableSeq.of(new PatClass<>(param.toFreshTerm(), ImmutableIntSeq.empty()));
        }
        return buffer.toImmutableSeq();
      }
      default -> { }
    }
    return ImmutableSeq.of(new PatClass<>(param.toFreshTerm(), Indexed.indices(clauses)));
  }

  private static @Nullable Indexed> matches(
    ImmutableSeq conTele, ConDefLike con, int ix, Indexed subPat
  ) {
    return switch (subPat.pat() instanceof Pat.ShapedInt i ? i.constructorForm() : subPat.pat()) {
      case Pat.Con c when c.ref().equals(con) -> new Indexed<>(c.args().view(), ix);
      case Pat.Bind _ -> new Indexed<>(conTele.view().map(Param::toFreshPat), ix);
      default -> null;
    };
  }

  public static int[] firstMatchDomination(
    @NotNull ImmutableSeq clauses,
    @NotNull Problematic reporter, @NotNull ImmutableSeq> classes
  ) {
    return ClassifierUtil.firstMatchDomination(clauses, (pos, i) -> reporter.fail(
      new ClausesProblem.FMDomination(i, pos)), classes);
  }

  private record ConTele(
    @NotNull ImmutableSeq tele,
    @NotNull ImmutableSeq ownerArgs
  ) { }
  private @Nullable ConTele
  conTele(@NotNull ImmutableSeq> clauses, DataCall dataCall, ConDefLike con) {
    // Check if this constructor is available by doing the obvious thing
    return switch (PatternTycker.checkAvail(dataCall, con, state())) {
      // If not, check the reason why: it may fail negatively or positively
      case Result.Err(var e) -> {
        // Index unification fails negatively
        if (e == State.Stuck) {
          // If clauses is empty, we continue splitting to see
          // if we can ensure that the other cases are impossible, it would be fine.
          if (clauses.isNotEmpty() &&
            // If clauses has catch-all pattern(s), it would also be fine.
            clauses.noneMatch(seq -> seq.pat() instanceof Pat.Bind)
          ) {
            fail(new ClausesProblem.UnsureCase(pos, con, dataCall));
            yield null;
          }
          yield new ConTele(con.selfTele(ImmutableSeq.empty()), dataCall.args());
        } else yield null;
        // ^ If fails positively, this would be an impossible case
      }
      case Result.Ok(var ok) -> new ConTele(con.selfTele(ok), ok);
    };
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy