Skip to content

New fixes in type inference / specialization for ABI encoding library#464

Open
rodrigogribeiro wants to merge 8 commits into
mainfrom
new-infer-specialize-fixes
Open

New fixes in type inference / specialization for ABI encoding library#464
rodrigogribeiro wants to merge 8 commits into
mainfrom
new-infer-specialize-fixes

Conversation

@rodrigogribeiro

Copy link
Copy Markdown
Collaborator

No description provided.

@rodrigogribeiro rodrigogribeiro requested a review from mbenke June 14, 2026 20:06

@mbenke mbenke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ cabal run sol-core -- -f test/examples/cases/bug-mptc-template-a.solc 
PANIC: ! specCall: no resolution found for Encoder.encode : Foo -> word -> word -> word

// Bug: tryResolveMPTC Template A wrongly binds `rep` when a class method takes
// more than one argument.
//
// Template A matches storedTy against (mainTy :-> freshV).
// For Encoder.encode specialised at Foo, storedTy = Foo -> word -> word.
// specmgu (Foo -> word -> word) (Foo -> freshV) succeeds with freshV = word -> word
// because `word -> word` is a fully-concrete type (no free vars), the guard
// `null (freetv concrete)` passes, and rep gets bound to word -> word instead
// of word.
//
// The phantom variable `rep` has no return-type context here (encodeAndDiscard
// returns ()) so it cannot be resolved from the call site -- tryResolveMPTC is
// the only mechanism that fires, and it produces the wrong answer.
//
// Expected (main, without tryResolveMPTC): clear "unresolved type variable" error.
// Actual (PR branch, with tryResolveMPTC): panic -- no resolution for
//   Encoder.encode : Foo -> word -> word -> word  (wrong 4-arg type from rep=word->word)

forall self rep.
class self:Encoder(rep) {
    function encode(x:self, hint:word) -> rep;
}

data Foo = Foo(word);
instance Foo:Encoder(word) {
    function encode(x:Foo, hint:word) -> word {
        match x { | Foo(w) => return w; }
    }
}

forall a rep . a:Encoder(rep) =>
function encodeAndDiscard(x:a) -> () {
    let enc : rep = Encoder.encode(x, 0);
    return ();
}

contract C {
    public function main() -> word {
        encodeAndDiscard(Foo(42));
        return 0;
    }
}

@mbenke

mbenke commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Another regression:

data TagA = TagA(word);
data TagB = TagB(word);

forall self rep.
class self:Tag(rep) {
    function getTag(x:self) -> rep;
}

data TypeA = TypeA(word);
instance TypeA:Tag(TagA) {
    function getTag(x:TypeA) -> TagA {
        match x { | TypeA(w) => return TagA(w); }
    }
}

data TypeB = TypeB(word);
instance TypeB:Tag(TagB) {
    function getTag(x:TypeB) -> TagB {
        match x { | TypeB(w) => return TagB(w); }
    }
}

forall a b rep1 rep2 . a:Tag(rep1), b:Tag(rep2) =>
function tagFirst(x:a, y:b) -> rep1 {
    return Tag.getTag(x);
}

contract C {
    constructor() {}

    public function main() -> word {
        let r : TagA = tagFirst(TypeA(42), TypeB(7));
        match r { | TagA(w) => return w; }
    }
}

this seems to compile fine on main, but here fails with

PANIC: ! specCall: no resolution found for tagFirst : TypeA -> TypeB -> TagA

My understanding is that phantomMatchingPreds pairs EVERY annotation Tag with EVERY inferred Tag (a full 2x2 cross product), and unifyPredExtras then does unify mt1 mt2 on each pair -- including the (a-pred, b-pred) pairing, which unifies the two distinct type parameters a ~ b.

@mbenke

mbenke commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Some tests would be nice too :)

Comment thread src/Solcore/Backend/Specialise.hs Outdated
Comment on lines +518 to +525
Right phi -> do
let concrete = applytv phi freshV
when (null (freetv concrete)) $
forM_ extras $ \extra ->
case specmgu extra concrete of
Left _ -> return ()
Right phi2 -> extSpSubst phi2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Right phi -> do
let concrete = applytv phi freshV
when (null (freetv concrete)) $
forM_ extras $ \extra ->
case specmgu extra concrete of
Left _ -> return ()
Right phi2 -> extSpSubst phi2
Right phi -> do
let concrete = applytv phi freshV
-- A representation type is never a function type. An arrow here means we
-- matched a multi-argument method (e.g. self -> word -> rep) against the
-- unary template (self -> freshV) and captured the trailing argument
-- types by mistake; reject so the phantom var stays unresolved and the
-- caller reports it cleanly instead of panicking on a bogus arity later.
when (null (freetv concrete) && not (isArrow concrete)) $
forM_ extras $ \extra ->
case specmgu extra concrete of
Left _ -> return ()
Right phi2 -> extSpSubst phi2
where
isArrow (_ :-> _) = True
isArrow _ = False

-- predicates, not in the function type). sig2's context uses annotation
-- TyVars (e.g. "rep"), but the body uses body TyVars (e.g. "$106550").
-- Build a TyVar-level renaming from the delta and patch sig2's context.
phantomDelta <- findPhantomPredBindings pann pinf

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compute the set of metavariables that occur in the function type (funMetas = mv tinf' ∪ mv tann') and exclude them from delta, keeping only genuinely phantom variables (present in predicates, absent from the function type) - also requires changing findPhantomPredBindings

Suggested change
phantomDelta <- findPhantomPredBindings pann pinf
let funMetas = mv tinf' `union` mv tann'
phantomDelta <- findPhantomPredBindings funMetas pann pinf

Comment on lines +778 to +787
findPhantomPredBindings :: [Pred] -> [Pred] -> TcM [(MetaTv, Ty)]
findPhantomPredBindings pann pinf = do
s0 <- getSubst
let pann' = apply s0 (everywhere (mkT toMeta) pann)
pinf' = everywhere (mkT toMeta) pinf
dom_s0 = map fst (unSubst s0)
forM_ (phantomMatchingPreds dom_s0 pann' pinf') $ \(pa, pi_) ->
catchError (unifyPredExtras pa pi_) (\_ -> return ())
s_full <- getSubst
let delta = filter (\(v, _) -> v `notElem` dom_s0) (unSubst s_full)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
findPhantomPredBindings :: [Pred] -> [Pred] -> TcM [(MetaTv, Ty)]
findPhantomPredBindings pann pinf = do
s0 <- getSubst
let pann' = apply s0 (everywhere (mkT toMeta) pann)
pinf' = everywhere (mkT toMeta) pinf
dom_s0 = map fst (unSubst s0)
forM_ (phantomMatchingPreds dom_s0 pann' pinf') $ \(pa, pi_) ->
catchError (unifyPredExtras pa pi_) (\_ -> return ())
s_full <- getSubst
let delta = filter (\(v, _) -> v `notElem` dom_s0) (unSubst s_full)
findPhantomPredBindings :: [MetaTv] -> [Pred] -> [Pred] -> TcM [(MetaTv, Ty)]
findPhantomPredBindings funMetas pann pinf = do
s0 <- getSubst
let pann' = apply s0 (everywhere (mkT toMeta) pann)
pinf' = everywhere (mkT toMeta) pinf
dom_s0 = map fst (unSubst s0)
forM_ (phantomMatchingPreds dom_s0 pann' pinf') $ \(pa, pi_) ->
catchError (unifyPredExtras pa pi_) (\_ -> return ())
s_full <- getSubst
-- Keep only bindings for genuinely phantom vars: newly introduced (not in
-- dom_s0) AND absent from the function type (funMetas). This drops stray
-- bindings such as `a := b` produced by cross-product pairing of two
-- same-class constraints, which would otherwise corrupt the signature.
let delta = filter (\(v, _) -> v `notElem` dom_s0 && v `notElem` funMetas) (unSubst s_full)

@mbenke

mbenke commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Added suggested fixes for the two issues mentioned in the review yesterday.

@mbenke

mbenke commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

We need some real tests - currently all test pass even when resolveMPTCFromPreds is replaced with return ()

@rodrigogribeiro rodrigogribeiro requested a review from mbenke June 16, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants