org.aya.tyck.pat.PatternTycker Maven / Gradle / Ivy
// 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.mutable.MutableList;
import kala.control.Result;
import kala.value.MutableValue;
import org.aya.generic.Constants;
import org.aya.generic.Renamer;
import org.aya.generic.State;
import org.aya.normalize.Normalizer;
import org.aya.syntax.compile.JitCon;
import org.aya.syntax.concrete.Expr;
import org.aya.syntax.concrete.Pattern;
import org.aya.syntax.core.def.ConDef;
import org.aya.syntax.core.def.ConDefLike;
import org.aya.syntax.core.pat.Pat;
import org.aya.syntax.core.pat.PatMatcher;
import org.aya.syntax.core.pat.PatToTerm;
import org.aya.syntax.core.repr.AyaShape;
import org.aya.syntax.core.repr.CodeShape;
import org.aya.syntax.core.term.DepTypeTerm;
import org.aya.generic.term.DTKind;
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.core.term.call.ConCallLike;
import org.aya.syntax.core.term.call.DataCall;
import org.aya.syntax.ref.LocalVar;
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.Arg;
import org.aya.util.error.Panic;
import org.aya.util.error.SourcePos;
import org.aya.util.error.WithPos;
import org.aya.util.reporter.Problem;
import org.aya.util.reporter.Reporter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Tyck for {@link Pattern}'s, the left hand side of one clause.
*/
public class PatternTycker implements Problematic, Stateful {
private final @NotNull ExprTycker exprTycker;
private final boolean allowImplicit;
/**
* A bound telescope (i.e. all the reference to the former parameter are LocalTerm)
*/
private @NotNull SeqView telescope;
/** Substitution for parameter, in the same order as parameter */
private final @NotNull MutableList paramSubst;
/**
* Substitution for `as` pattern
*/
private final @NotNull LocalLet asSubst;
private @UnknownNullability Param currentParam = null;
private boolean hasError = false;
private final @NotNull Renamer nameGen;
/**
* @see #tyckInner(SeqView, SeqView, WithPos)
*/
private PatternTycker(
@NotNull ExprTycker tycker,
@NotNull SeqView tele,
@NotNull LocalLet sub,
@NotNull Renamer nameGen
) {
this(tycker, tele, sub, true, nameGen);
}
public PatternTycker(
@NotNull ExprTycker exprTycker,
@NotNull SeqView telescope,
@NotNull LocalLet asSubst,
boolean allowImplicit,
@NotNull Renamer nameGen
) {
this.exprTycker = exprTycker;
this.telescope = telescope;
this.paramSubst = MutableList.create();
this.asSubst = asSubst;
this.allowImplicit = allowImplicit;
this.nameGen = nameGen;
nameGen.store(exprTycker.localCtx());
}
public record TyckResult(
@NotNull ImmutableSeq wellTyped,
@NotNull ImmutableSeq paramSubst,
@NotNull LocalLet asSubst,
@Nullable WithPos newBody,
boolean hasError
) {
public @NotNull SeqView paramSubstObj() {
return paramSubst.view().map(Jdg::wellTyped);
}
}
/**
* Tyck a {@param type} against {@param type}
*
* @param pattern a concrete {@link Pattern}
* @param type the type of {@param pattern}, it probably contains {@link MetaPatTerm}
* @return a well-typed {@link Pat}, but still need to be inline!
*/
private @NotNull Pat doTyck(@NotNull WithPos pattern, @NotNull Term type) {
return switch (pattern.data()) {
case Pattern.Absurd _ -> {
var selection = makeSureEmpty(type, pattern);
if (selection != null) {
foundError(new PatternProblem.PossiblePat(pattern, selection));
}
yield Pat.Misc.Absurd;
}
case Pattern.Tuple(var l, var r) -> {
if (!(exprTycker.whnf(type) instanceof DepTypeTerm(var kind, var lT, var rT) && kind == DTKind.Sigma)) {
var frozen = freezeHoles(type);
yield withError(new PatternProblem.TupleNonSig(pattern, frozen), frozen);
}
var lhs = doTyck(l, lT);
yield new Pat.Tuple(lhs, doTyck(r, rT.apply(PatToTerm.visit(lhs))));
}
case Pattern.Con con -> {
var realCon = makeSureAvail(type, con.resolved().data(), pattern);
if (realCon == null) yield randomPat(type);
var conCore = realCon.conHead.ref();
// It is possible that `con.params()` is empty.
var patterns = tyckInner(
conCore.selfTele(realCon.ownerArgs).view(),
con.params().view(),
pattern);
// check if this Con is a ShapedCon
var typeRecog = state().shapeFactory.find(conCore.dataRef()).getOrNull();
yield new Pat.Con(conCore, patterns, realCon.conHead);
}
case Pattern.Bind(var bind, var tyRef) -> {
exprTycker.localCtx().put(bind, type);
tyRef.set(type);
yield new Pat.Bind(bind, type);
}
case Pattern.CalmFace.INSTANCE ->
new Pat.Meta(MutableValue.create(), Constants.ANONYMOUS_PREFIX, type, pattern.sourcePos());
case Pattern.Number(var number) -> {
var ty = whnf(type);
if (ty instanceof DataCall dataCall) {
var data = dataCall.ref();
var shape = state().shapeFactory.find(data);
if (shape.isDefined() && shape.get().shape() == AyaShape.NAT_SHAPE)
yield new Pat.ShapedInt(number,
shape.get().getCon(CodeShape.GlobalId.ZERO),
shape.get().getCon(CodeShape.GlobalId.SUC),
dataCall);
}
yield withError(new PatternProblem.BadLitPattern(pattern, ty), ty);
}
case Pattern.List(var el) -> {
// desugar `Pattern.List` to `Pattern.Con` here, but use `CodeShape` !
// Note: this is a special case (maybe), If there is another similar requirement,
// a PatternDesugarer is recommended.
var ty = whnf(type);
if (ty instanceof DataCall dataCall) {
var data = dataCall.ref();
var shape = state().shapeFactory.find(data).getOrNull();
if (shape != null && shape.shape() == AyaShape.LIST_SHAPE)
yield doTyck(new Pattern.FakeShapedList(pattern.sourcePos(), el,
shape.getCon(CodeShape.GlobalId.NIL), shape.getCon(CodeShape.GlobalId.CONS), dataCall)
.constructorForm(), type);
}
yield withError(new PatternProblem.BadLitPattern(pattern, ty), ty);
}
case Pattern.As(var inner, var as, var typeRef) -> {
var innerPat = doTyck(inner, type);
typeRef.set(type);
addAsSubst(as, innerPat, type);
// exprTycker.localCtx().put(as, type);
yield innerPat;
}
case Pattern.Salt _ -> Panic.unreachable();
};
}
private void moveNext() { currentParam = telescope.getFirstOrNull(); }
/**
* Find the next param against to {@param pattern}
*
* @return null if failed, i.e. too many pattern
* @apiNote after call: {@param currentParam} is an unchecked parameter, and {@code currentParam.explicit == pattern.explicit}
*/
private @Nullable ImmutableSeq nextParam(@NotNull Arg> pattern) {
var generatedPats = MutableList.create();
while (currentParam != null && pattern.explicit() != currentParam.explicit()) {
// Hwhile : pattern.explicit != currentParam.explicit
if (pattern.explicit()) {
// Hif : pattern.explicit = true
// Corollary : currentParam.explicit = false
// then generate pattern
generatedPats.append(generatePattern());
// [generatePattern] drops the first parameter
moveNext();
} else {
// Hif = pattern.explicit = false
// Corollary : currentParam.explicit = true
// too many implicit pattern!
foundError(new PatternProblem.TooManyImplicitPattern(pattern.term(), currentParam));
return null;
}
}
// Hwhile : currentParam == null || pattern.explicit = currentParam.explicit
if (currentParam == null) {
// too many pattern
foundError(new PatternProblem.TooManyPattern(pattern.term()));
return null;
}
// Hwhile : pattern.explicit = currentParam.explicit
// good, this is the parameter we want!
return generatedPats.toImmutableSeq();
}
record PushTelescope(@NotNull ImmutableSeq wellTyped, @NotNull WithPos newBody) { }
/**
* @apiNote requires: {@code currentParam} is not null and is an unchecked parameter, say, no well typed pat for it.
* after call: {@code currentParam} is an unchecked parameter if not null
* @implNote No need to report if too many parameter
*/
private @NotNull PushTelescope pushTelescope(@NotNull WithPos body) {
var wellTyped = MutableList.create();
while (currentParam != null && body.data() instanceof Expr.Lambda lam) {
// good, we can use the parameter of [lam] as pattern
var pat = new Pattern.Bind(lam.param().ref());
wellTyped.append(tyckPattern(body.replace(pat)));
// update state
body = lam.body();
moveNext();
}
// Hwhile : currentParam = null || body is not Expr.Lambda
return new PushTelescope(wellTyped.toImmutableSeq(), body);
}
public @NotNull TyckResult tyck(
@NotNull SeqView>> patterns,
@Nullable WithPos outerPattern,
@Nullable WithPos body
) {
assert currentParam == null : "this tycker is dirty";
var wellTyped = MutableList.create();
// last user given pattern, that is, not aya generated
@Nullable Arg> lastPat = null;
moveNext();
// loop invariant: currentParam is the last unchecked parameter if not null
while (currentParam != null && patterns.isNotEmpty()) {
var currentPat = patterns.getFirst();
patterns = patterns.drop(1);
lastPat = currentPat;
if (!(currentPat.explicit() || allowImplicit)) {
// TODO: implicit disallowed
throw new UnsupportedOperationException("TODO");
}
// find the next appropriate parameter
var generated = nextParam(currentPat);
if (generated == null) {
return done(wellTyped, body);
}
wellTyped.appendAll(generated);
wellTyped.append(tyckPattern(currentPat.term()));
moveNext();
}
// Hwhile : currentParam == null || patterns.isEmpty()
// [currentParam] is the next unchecked parameter if not null (by loop invariant)
if (currentParam == null && patterns.isNotEmpty()) {
var pattern = patterns.getFirst();
// too many pattern
foundError(new PatternProblem.TooManyPattern(pattern.term()));
}
// the telescope may end with implicit parameters
// loop invariant: [currentParam] is the next unchecked parameter if not null
while (currentParam != null && !currentParam.explicit()) {
wellTyped.append(generatePattern());
// [currentParam] is checked!
moveNext();
// [currentParam] is unchecked if not null.
}
if (currentParam != null && body != null) {
var result = pushTelescope(body);
wellTyped.appendAll(result.wellTyped);
body = result.newBody;
}
// [currentParam] is the next unchecked parameter if not null
if (currentParam != null) {
// too few patterns !
// the body does not have (enough) pattern, too sad
WithPos errorPattern = lastPat == null
? Objects.requireNonNull(outerPattern)
: lastPat.term();
foundError(new PatternProblem.InsufficientPattern(errorPattern, currentParam));
return done(wellTyped, body);
}
// [currentParam] = null
return done(wellTyped, body);
}
private T onTyck(@NotNull Supplier block) {
currentParam = currentParam.descent(t -> t.instantiateTele(paramSubst.view().map(Jdg::wellTyped)));
var result = block.get();
telescope = telescope.drop(1);
return result;
}
/**
* Checking {@param pattern} with {@link PatternTycker#currentParam}
*/
private @NotNull Pat tyckPattern(@NotNull WithPos pattern) {
return onTyck(() -> {
var result = doTyck(pattern, currentParam.type());
addArgSubst(result, currentParam.type());
return result;
});
}
/**
* For every implicit parameter which is not explicitly (not user given pattern) matched,
* we generate a MetaPat for each,
* so that they can be inferred during {@link ClauseTycker}
*/
private @NotNull Pat generatePattern() {
return onTyck(() -> {
var type = currentParam.type();
Pat pat;
var freshVar = nameGen.bindName(currentParam.name());
if (exprTycker.whnf(type) instanceof DataCall dataCall) {
// this pattern would be a Con, it can be inferred
// TODO: I NEED A SOURCE POS!!
pat = new Pat.Meta(MutableValue.create(), freshVar.name(), dataCall, SourcePos.NONE);
} else {
// If the type is not a DataCall, then the only available pattern is Pat.Bind
pat = new Pat.Bind(freshVar, type);
exprTycker.localCtx().put(freshVar, type);
}
addArgSubst(pat, currentParam.type());
return pat;
});
}
private @NotNull ImmutableSeq tyckInner(
@NotNull SeqView telescope,
@NotNull SeqView>> patterns,
@NotNull WithPos outerPattern
) {
var sub = new PatternTycker(exprTycker, telescope, asSubst, nameGen);
var tyckResult = sub.tyck(patterns, outerPattern, null);
hasError = hasError || sub.hasError;
return tyckResult.wellTyped;
}
private void addArgSubst(@NotNull Pat pattern, @NotNull Term type) {
paramSubst.append(new Jdg.Default(PatToTerm.visit(pattern), type));
}
private void addAsSubst(@NotNull LocalVar as, @NotNull Pat pattern, @NotNull Term type) {
asSubst.put(as, new Jdg.Default(PatToTerm.visit(pattern), type));
}
private @NotNull TyckResult done(@NotNull MutableList wellTyped, @Nullable WithPos newBody) {
var paramSubst = this.paramSubst.toImmutableSeq();
return new TyckResult(wellTyped.toImmutableSeq(), paramSubst, asSubst, newBody, hasError);
}
private record Selection(
@NotNull DataCall data,
@NotNull ImmutableSeq ownerArgs,
@NotNull ConCallLike.Head conHead
) { }
private @Nullable ConCallLike.Head makeSureEmpty(Term type, @NotNull WithPos pattern) {
if (!(exprTycker.whnf(type) instanceof DataCall dataCall)) {
foundError(new PatternProblem.SplittingOnNonData(pattern, type));
return null;
}
var core = dataCall.ref();
// If name != null, only one iteration of this loop is not skipped
for (var con : core.body()) {
switch (checkAvail(dataCall, con, exprTycker.state)) {
case Result.Ok(var subst) -> {
return new ConCallLike.Head(con, dataCall.ulift(), subst);
}
// Is blocked
case Result.Err(var st) when st == State.Stuck -> {
foundError(new PatternProblem.BlockedEval(pattern, dataCall));
return null;
}
default -> { }
}
}
return null;
}
private @Nullable Selection makeSureAvail(Term type, @NotNull ConDefLike name, @NotNull WithPos pattern) {
if (!(exprTycker.whnf(type) instanceof DataCall dataCall)) {
foundError(new PatternProblem.SplittingOnNonData(pattern, type));
return null;
}
if (!name.dataRef().equals(dataCall.ref())) {
foundError(new PatternProblem.UnknownCon(pattern));
return null;
}
return switch (checkAvail(dataCall, name, exprTycker.state)) {
case Result.Ok(var subst) -> new Selection(
(DataCall) dataCall.replaceTeleFrom(name.selfTeleSize(), subst.view()),
subst, new ConCallLike.Head(name, dataCall.ulift(), subst));
case Result.Err(_) -> {
// Here, name != null, and is not in the list of checked body
foundError(new PatternProblem.UnavailableCon(pattern, dataCall));
yield null;
}
};
}
/**
* Check whether {@param con} is available under {@param type}
*/
public static @NotNull Result, State> checkAvail(
@NotNull DataCall type, @NotNull ConDefLike con, @NotNull TyckState state
) {
return switch (con) {
case JitCon jitCon -> jitCon.isAvailable(type.args());
case ConDef.Delegate conDef -> {
var pats = conDef.core().pats;
if (pats.isNotEmpty()) {
var matcher = new PatMatcher(true, new Normalizer(state));
yield matcher.apply(pats, type.args());
}
yield Result.ok(type.args());
}
};
}
/// region Helper
private @NotNull Pat randomPat(Term param) {
return new Pat.Bind(nameGen.bindName(param), param);
}
/**
* Generate names for core telescope
*/
private @NotNull SeqView generateNames(@NotNull ImmutableSeq telescope) {
return telescope.view().mapIndexed((_, t) ->
new Param(nameGen.bindName(exprTycker.whnf(t)).name(), t, true));
}
/// endregion Helper
/// region Error Reporting
@Override public @NotNull Reporter reporter() { return exprTycker.reporter; }
@Override public @NotNull TyckState state() { return exprTycker.state; }
private @NotNull Pat withError(Problem problem, Term param) {
foundError(problem);
// In case something's wrong, produce a random pattern
return randomPat(param);
}
private void foundError(@Nullable Problem problem) {
hasError = true;
if (problem != null) fail(problem);
}
/// endregion Error Reporting
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy