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

org.aya.unify.Unifier 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.unify;

import kala.collection.mutable.MutableArrayList;
import kala.collection.mutable.MutableList;
import org.aya.prettier.FindUsage;
import org.aya.syntax.core.term.*;
import org.aya.syntax.core.term.call.MetaCall;
import org.aya.syntax.core.term.marker.Formation;
import org.aya.syntax.ref.LocalCtx;
import org.aya.syntax.ref.LocalVar;
import org.aya.syntax.ref.MetaVar;
import org.aya.tyck.TyckState;
import org.aya.tyck.error.MetaVarError;
import org.aya.util.Ordering;
import org.aya.util.error.SourcePos;
import org.aya.util.reporter.Reporter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class Unifier extends TermComparator {
  private final boolean allowDelay;
  public boolean allowVague = false;
  public Unifier(
    @NotNull TyckState state, @NotNull LocalCtx ctx,
    @NotNull Reporter reporter, @NotNull SourcePos pos, @NotNull Ordering cmp,
    boolean allowDelay
  ) {
    super(state, ctx, reporter, pos, cmp);
    this.allowDelay = allowDelay;
  }

  public @NotNull TyckState.Eqn createEqn(@NotNull MetaCall lhs, @NotNull Term rhs, @Nullable Term type) {
    return new TyckState.Eqn(lhs, rhs, type, cmp, pos, localCtx().clone());
  }

  public @NotNull Unifier derive(@NotNull SourcePos pos, Ordering ordering) {
    return new Unifier(state, localCtx().derive(), reporter, pos, ordering, allowDelay);
  }

  @Override protected @Nullable Term doSolveMeta(@NotNull MetaCall meta, @NotNull Term rhs, @Nullable Term type) {
    if (rhs instanceof MetaCall rMeta && rMeta.ref() == meta.ref())
      return sameMeta(meta, type, rMeta);
    // Assumption: rhs is in whnf
    var spine = meta.args();

    var inverted = MutableArrayList.create(spine.size());
    var overlap = MutableList.create();
    var wantToReturn = false;
    for (var arg : spine) {
      // TODO: apply uneta
      if (whnf(arg) instanceof FreeTerm(var var)) {
        if (inverted.contains(var)) overlap.append(var);
        inverted.append(var);
      } else if (allowVague) {
        inverted.append(LocalVar.generate("_"));
      } else if (allowDelay) {
        wantToReturn = true;
        break;
      } else {
        reportBadSpine(meta, rhs);
        return null;
      }
    }

    var returnType = computeReturnType(meta, rhs, type);
    if (wantToReturn) {
      state.addEqn(createEqn(meta, rhs, returnType));
      return returnType;
    }
    if (returnType == null) return null;

    // In this case, the solution may not be unique (see #608),
    // so we may delay its resolution to the end of the tycking when we disallow delayed unification.
    var tmpRhs = rhs; // to get away with Java warning
    if (!allowVague && overlap.anyMatch(var -> FindUsage.free(tmpRhs, var) > 0)) {
      if (allowDelay) {
        state.addEqn(createEqn(meta, rhs, returnType));
        return returnType;
      } else {
        reportBadSpine(meta, rhs);
        return null;
      }
    }
    // Now we are sure that the variables in overlap are all unused.

    var findUsage = FindUsage.unfree(rhs, inverted);
    if (findUsage.termUsage > 0) {
      rhs = fullNormalize(rhs);
      findUsage = FindUsage.unfree(rhs, inverted);
    }
    if (findUsage.termUsage > 0) {
      fail(new MetaVarError.BadlyScopedError(meta, rhs, inverted));
      return null;
    }
    if (findUsage.metaUsage > 0) {
      if (allowDelay) {
        state.addEqn(createEqn(meta, rhs, returnType));
        return returnType;
      } else {
        fail(new MetaVarError.BadlyScopedError(meta, rhs, inverted));
        return null;
      }
    }
    var ref = meta.ref();
    if (FindUsage.meta(rhs, ref) > 0) {
      fail(new MetaVarError.RecursionError(meta, rhs));
      return null;
    }
    var candidate = rhs.bindTele(inverted.view());
    // It might have extra arguments, in those cases we need to abstract them out.
    solve(ref, LamTerm.make(spine.size() - ref.ctxSize(), candidate));
    return returnType;
  }

  /** The "flex-flex" case with identical meta ref */
  private @Nullable Term sameMeta(@NotNull MetaCall meta, @Nullable Term type, MetaCall rMeta) {
    if (meta.args().size() != rMeta.args().size()) return null;
    for (var i = 0; i < meta.args().size(); i++) {
      if (!compare(meta.args().get(i), rMeta.args().get(i), null)) {
        return null;
      }
    }
    if (type != null) return type;
    if (meta.ref().req() instanceof MetaVar.OfType(var ty)) return ty;
    return ErrorTerm.typeOf(meta);
  }

  /**
   * @return null if ill-typed
   */
  private @Nullable Term computeReturnType(@NotNull MetaCall meta, @NotNull Term rhs, @Nullable Term type) {
    var needUnify = true;
    var returnType = type;
    var ref = meta.ref();
    // Running double checker is important, see #327 last task (`coe'`)
    var checker = new DoubleChecker(derive(ref.pos(), cmp));
    switch (ref.req()) {
      case MetaVar.Misc.Whatever -> needUnify = false;
      case MetaVar.Misc.IsType -> {
        switch (rhs) {
          case Formation _ -> { }
          case MetaCall rMeta -> {
            if (!checker.synthesizer().isTypeMeta(rMeta.ref().req())) {
              reportIllTyped(meta, rhs);
              return null;
            }
          }
          default -> {
            var synthesize = checker.synthesizer().trySynth(rhs);
            if (!(synthesize instanceof SortTerm)) {
              reportIllTyped(meta, rhs);
              return null;
            }
            if (returnType == null) returnType = synthesize;
          }
        }
        needUnify = false;
      }
      case MetaVar.OfType(var target) -> {
        target = MetaCall.appType(meta, target);
        if (type != null && !compare(type, target, null)) {
          reportIllTyped(meta, rhs);
          return null;
        }
        returnType = freezeHoles(target);
      }
      case MetaVar.PiDom(var sort) -> {
        if (!checker.synthesizer().inheritPiDom(rhs, sort)) reportIllTyped(meta, rhs);
      }
    }
    if (needUnify) {
      // Check the solution.
      if (returnType != null) {
        // resultTy might be an ErrorTerm, what to do?
        if (!checker.inherit(rhs, returnType))
          reportIllTyped(meta, rhs);
      } else {
        returnType = checker.synthesizer().trySynth(rhs);
        if (returnType == null) {
          throw new UnsupportedOperationException("TODO: add an error report for this");
        }
      }
    }
    if (!needUnify && returnType == null) return SortTerm.Type0;
    else return returnType;
  }

  private void reportBadSpine(@NotNull MetaCall meta, @NotNull Term rhs) {
    fail(new MetaVarError.BadSpineError(meta, state, rhs));
  }
  private void reportIllTyped(@NotNull MetaCall meta, @NotNull Term rhs) {
    fail(new MetaVarError.IllTypedError(meta, state, rhs));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy