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

org.aya.tyck.pat.ClauseTycker 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.SeqView;
import kala.collection.immutable.ImmutableSeq;
import kala.collection.immutable.primitive.ImmutableIntSeq;
import kala.value.primitive.MutableBooleanValue;
import org.aya.generic.Renamer;
import org.aya.prettier.AyaPrettierOptions;
import org.aya.syntax.concrete.Expr;
import org.aya.syntax.concrete.Pattern;
import org.aya.syntax.core.pat.Pat;
import org.aya.syntax.core.pat.TypeEraser;
import org.aya.syntax.core.term.ErrorTerm;
import org.aya.syntax.core.term.MetaPatTerm;
import org.aya.syntax.core.term.Param;
import org.aya.syntax.core.term.Term;
import org.aya.syntax.ref.LocalCtx;
import org.aya.syntax.ref.LocalVar;
import org.aya.syntax.telescope.Signature;
import org.aya.tyck.ExprTycker;
import org.aya.tyck.Jdg;
import org.aya.tyck.TyckState;
import org.aya.tyck.ctx.LocalLet;
import org.aya.tyck.error.PatternProblem;
import org.aya.tyck.tycker.Problematic;
import org.aya.tyck.tycker.Stateful;
import org.aya.util.error.Panic;
import org.aya.util.error.SourcePos;
import org.aya.util.error.WithPos;
import org.aya.util.reporter.Reporter;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.UnaryOperator;

public record ClauseTycker(@NotNull ExprTycker exprTycker) implements Problematic, Stateful {
  public record TyckResult(
    @NotNull ImmutableSeq> clauses,
    boolean hasLhsError
  ) {
    public @NotNull ImmutableSeq> wellTyped() {
      return clauses.flatMap(Pat.Preclause::lift);
    }
  }

  /**
   * @param paramSubst substitution for parameter, in the same order as parameter.
   *                   See {@link PatternTycker#paramSubst}
   * @param freePats   a free version of the patterns.
   *                   In most cases you want to use {@code clause.pats} instead
   * @param allBinds   all binders in the patterns
   * @param asSubst    substitution of the {@code as} patterns
   */
  public record LhsResult(
    @NotNull LocalCtx localCtx,
    @NotNull Term type,
    @NotNull ImmutableSeq allBinds,
    @NotNull ImmutableSeq freePats,
    @NotNull ImmutableSeq paramSubst,
    @NotNull LocalLet asSubst,
    @NotNull Pat.Preclause clause,
    boolean hasError
  ) {
    @Contract(mutates = "param2")
    public void addLocalLet(@NotNull ImmutableSeq teleBinds, @NotNull ExprTycker exprTycker) {
      teleBinds.forEachWith(paramSubst, exprTycker.localLet()::put);
      exprTycker.setLocalLet(new LocalLet(exprTycker.localLet(), asSubst.subst()));
    }
  }

  public record Worker(
    @NotNull ClauseTycker parent,
    @NotNull ImmutableSeq teleVars,
    @NotNull Signature signature,
    @NotNull ImmutableSeq clauses,
    @NotNull ImmutableSeq elims,
    boolean isFn
  ) {
    public @NotNull TyckResult check(@NotNull SourcePos overallPos) {
      var lhsResult = parent.checkAllLhs(computeIndices(), signature, clauses.view(), isFn);

      if (lhsResult.noneMatch(r -> r.hasError)) {
        var classes = PatClassifier.classify(lhsResult.view().map(LhsResult::clause),
          signature.params().view(), parent.exprTycker, overallPos);
        if (clauses.isNotEmpty()) {
          var usages = PatClassifier.firstMatchDomination(clauses, parent, classes);
          // refinePatterns(lhsResults, usages, classes);
        }
      }

      lhsResult = lhsResult.map(cl -> new LhsResult(cl.localCtx, cl.type, cl.allBinds,
        cl.freePats.map(TypeEraser::erase),
        cl.paramSubst, cl.asSubst, cl.clause, cl.hasError));
      return parent.checkAllRhs(teleVars, lhsResult);
    }
    private @Nullable ImmutableIntSeq computeIndices() {
      return elims.isEmpty() ? null : elims.mapToInt(ImmutableIntSeq.factory(),
        teleVars::indexOf);
    }
    public @NotNull TyckResult checkNoClassify() {
      return parent.checkAllRhs(teleVars, parent.checkAllLhs(computeIndices(), signature, clauses.view(), isFn));
    }
  }

  public @NotNull ImmutableSeq checkAllLhs(
    @Nullable ImmutableIntSeq indices, @NotNull Signature signature,
    @NotNull SeqView clauses, boolean isFn
  ) {
    return clauses.map(c -> checkLhs(signature, indices, c, isFn)).toImmutableSeq();
  }

  public @NotNull TyckResult checkAllRhs(
    @NotNull ImmutableSeq vars,
    @NotNull ImmutableSeq lhsResults
  ) {
    var lhsError = lhsResults.anyMatch(LhsResult::hasError);
    var rhsResult = lhsResults.map(x -> checkRhs(vars, x));

    // inline terms in rhsResult
    rhsResult = rhsResult.map(preclause -> new Pat.Preclause<>(
      preclause.sourcePos(),
      preclause.pats().map(p -> p.descentTerm(exprTycker::zonk)),
      preclause.bindCount(), preclause.expr()
    ));

    return new TyckResult(rhsResult, lhsError);
  }

  @Override public @NotNull Reporter reporter() { return exprTycker.reporter; }
  @Override public @NotNull TyckState state() { return exprTycker.state; }
  private @NotNull PatternTycker newPatternTycker(
    @Nullable ImmutableIntSeq indices,
    @NotNull SeqView telescope
  ) {
    telescope = indices != null
      ? telescope.mapIndexed((idx, p) -> indices.contains(idx) ? p.explicitize() : p.implicitize())
      : telescope;

    return new PatternTycker(exprTycker, telescope, new LocalLet(), indices == null,
      new Renamer());
  }

  public @NotNull LhsResult checkLhs(
    @NotNull Signature signature,
    @Nullable ImmutableIntSeq indices,
    @NotNull Pattern.Clause clause,
    boolean isFn
  ) {
    var tycker = newPatternTycker(indices, signature.params().view());
    return exprTycker.subscoped(() -> {
      // If a pattern occurs in elimination environment, then we check if it contains absurd pattern.
      // If it is not the case, the pattern must be accompanied by a body.
      if (isFn && !clause.patterns.anyMatch(p -> hasAbsurdity(p.term().data())) && clause.expr.isEmpty()) {
        clause.hasError = true;
        exprTycker.fail(new PatternProblem.InvalidEmptyBody(clause));
      }

      var patResult = tycker.tyck(clause.patterns.view(), null, clause.expr.getOrNull());
      var ctx = exprTycker.localCtx();   // No need to copy the context here

      clause.hasError |= patResult.hasError();
      patResult = inline(patResult, ctx);
      var resultTerm = signature.result().instantiateTele(patResult.paramSubstObj()).descent(new TermInline());
      clause.patterns.view().map(it -> it.term().data()).forEach(TermInPatInline::apply);

      // It is safe to replace ctx:
      // * telescope are well-typed and no Meta
      // * PatternTycker doesn't introduce any Meta term
      ctx = ctx.map(new TermInline());
      var patWithTypeBound = Pat.collectVariables(patResult.wellTyped().view());

      var allBinds = patWithTypeBound.component1().toImmutableSeq();
      var newClause = new Pat.Preclause<>(clause.sourcePos, patWithTypeBound.component2(),
        allBinds.size(), patResult.newBody());
      return new LhsResult(ctx, resultTerm, allBinds,
        patResult.wellTyped(), patResult.paramSubst(), patResult.asSubst(), newClause, patResult.hasError());
    });
  }

  /**
   * Tyck the rhs of some clause.
   *
   * @param result the tyck result of the corresponding patterns
   */
  private @NotNull Pat.Preclause checkRhs(
    @NotNull ImmutableSeq teleBinds,
    @NotNull LhsResult result
  ) {
    return exprTycker.subscoped(() -> {
      var clause = result.clause;
      var bodyExpr = clause.expr();
      Term wellBody;
      var bindCount = 0;
      if (bodyExpr == null) wellBody = null;
      else if (result.hasError) {
        // In case the patterns are malformed, do not check the body
        // as we bind local variables in the pattern checker,
        // and in case the patterns are malformed, some bindings may
        // not be added to the localCtx of tycker, causing assertion errors
        wellBody = new ErrorTerm(result.clause.expr().data());
      } else {
        // the localCtx will be restored after exiting [subscoped]e
        exprTycker.setLocalCtx(result.localCtx);
        result.addLocalLet(teleBinds, exprTycker);
        // now exprTycker has all substitutions that PatternTycker introduced.
        wellBody = exprTycker.inherit(bodyExpr, result.type).wellTyped();
        exprTycker.solveMetas();
        wellBody = exprTycker.zonk(wellBody);

        // bind all pat bindings
        var patBindTele = Pat.collectVariables(result.clause.pats().view()).component1();
        bindCount = patBindTele.size();
        wellBody = wellBody.bindTele(patBindTele.view());
      }

      return new Pat.Preclause<>(clause.sourcePos(), clause.pats(), bindCount,
        wellBody == null ? null : WithPos.dummy(wellBody));
    });
  }

  private static final class TermInline implements UnaryOperator {
    @Override public @NotNull Term apply(@NotNull Term term) {
      if (term instanceof MetaPatTerm metaPat) {
        var isEmpty = metaPat.meta().solution().isEmpty();
        if (isEmpty) throw new Panic("Unable to inline " + metaPat.toDoc(AyaPrettierOptions.debug()));
        // the solution may contain other MetaPatTerm
        return metaPat.inline(this);
      } else {
        return term.descent(this);
      }
    }
  }

  private static boolean hasAbsurdity(@NotNull Pattern term) {
    return hasAbsurdity(term, MutableBooleanValue.create());
  }
  private static boolean hasAbsurdity(@NotNull Pattern term, @NotNull MutableBooleanValue b) {
    if (term == Pattern.Absurd.INSTANCE) b.set(true);
    else term.forEach((_, p) -> b.set(b.get() || hasAbsurdity(p, b)));
    return b.get();
  }

  /**
   * Inline terms which in pattern
   */
  private static final class TermInPatInline {
    public static void apply(@NotNull Pattern pat) {
      var typeRef = switch (pat) {
        case Pattern.Bind bind -> bind.type();
        case Pattern.As as -> as.type();
        default -> null;
      };

      if (typeRef != null) typeRef.update(it -> it == null ? null :
        it.descent(new TermInline()));

      pat.forEach((_, p) -> apply(p));
    }
  }

  private static @NotNull Jdg inlineTerm(@NotNull Jdg r) {
    return r.map(new TermInline());
  }

  /**
   * Inline terms in {@param result}, please do this after inline all patterns
   */
  private static @NotNull PatternTycker.TyckResult inline(@NotNull PatternTycker.TyckResult result, @NotNull LocalCtx ctx) {
    // inline {Pat.Meta} before inline {MetaPatTerm}s
    var wellTyped = result.wellTyped().map(x ->
      x.inline(ctx::put).descentTerm(new TermInline()));
    // so that {MetaPatTerm}s can be inlined safely
    var paramSubst = result.paramSubst().map(ClauseTycker::inlineTerm);

    // map in place 😱😱😱😱
    result.asSubst().subst().replaceAll((_, t) -> inlineTerm(t));

    return new PatternTycker.TyckResult(wellTyped, paramSubst, result.asSubst(), result.newBody(), result.hasError());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy