Skip to content

Conversation

@FeizaiYiHao
Copy link
Contributor

@FeizaiYiHao FeizaiYiHao commented Jun 26, 2025

This PR adds basic support for opaque types defined by function returns. The changes include:

  • In external.rs, all opaque type definitions are visited, and only opaque types under external functions are marked as VerifOrExternal::External , since the opaque type is part of the function declaration.
  • In lifetime check, all opaque types are normalized to concrete types.
  • In HIR to VIR:
    • a new enum TypX::Opaque{def_path:Path,args:Typs} is added, in which the def_path is the path where the opaque type is defined. And args are the type parameters used to instantiate this instance of the opaque type.
    • a new structOpaquetypeX{pub name: Path, pub typ_params: Typs, pub typ_bounds: GenericBounds,} is added for each opaque type definition. A KrateX carries a list of OpaquetypeX that are used in the crate.
  • in VIR to AIR
    • each OpaquetypeX generates two functions DCR%path and TYPE%path (or consts if takes no args) that return a Dcr and Type for given args. And also generates a range of axioms to encode the trait bounds for the instantiated opaque types.
    • for each instance of opaque type with args, we instantiate the opaque type by calling the DCR%path args and TYPE%path args functions.

If in opaque.rs we have the following Rust code

trait Tr{
    type Output;
}
impl<T> Tr for T{
    type Output = T;
}
fn foo<T>() -> impl Tr{
    true
}
fn bar(){
    let x = foo::<usize>();
    assert(x == x);
}

Produces the following z3 code

;; Opaque-Type-Constructors
(declare-fun DCR%opaque!foo.opaque0. (Dcr Type) Dcr)
(declare-fun TYPE%opaque!foo.opaque0. (Dcr Type) Type)
(assert
 (forall ((T&. Dcr) (T& Type)) (!
   (and
    (tr_bound%opaque!Tr. (DCR%opaque!foo.opaque0. T&. T&) (TYPE%opaque!foo.opaque0. T&.
      T&
    ))
    (sized (DCR%opaque!foo.opaque0. T&. T&))
   )
   :pattern ((DCR%opaque!foo.opaque0. T&. T&))
   :pattern ((TYPE%opaque!foo.opaque0. T&. T&))
   :qid internal_opaque__foo__opaque0_opaque_type_bound_definition
   :skolemid skolem_internal_opaque__foo__opaque0_opaque_type_bound_definition
)))

;; Function-Specs opaque::foo
(declare-fun ens%opaque!foo. (Dcr Type Poly) Bool)
(assert
 (forall ((T&. Dcr) (T& Type) (%return! Poly)) (!
   (= (ens%opaque!foo. T&. T& %return!) (has_type %return! (TYPE%opaque!foo.opaque0. T&.
      T&
   )))
   :pattern ((ens%opaque!foo. T&. T& %return!))
   :qid internal_ens__opaque!foo._definition
   :skolemid skolem_internal_ens__opaque!foo._definition
)))

;; Function-Def opaque::foo
;; ../examples/opaque.rs:16:1: 16:23 (#0)
(get-info :all-statistics)
(push)
 (declare-const T&. Dcr)
 (declare-const T& Type)
 (declare-const %return! Poly)
 (assert
  fuel_defaults
 )
 (assert
  (sized T&.)
 )
 (assert
  (not true)
 )
 (get-info :version)
 (set-option :rlimit 30000000)
 (check-sat)
 (set-option :rlimit 0)
(pop)

;; Function-Def opaque::bar
;; ../examples/opaque.rs:19:1: 19:9 (#0)
(get-info :all-statistics)
(push)
 (declare-const no%param Int)
 (declare-const tmp%1 Bool)
 (declare-const x@ Poly)
 (assert
  fuel_defaults
 )
 ;; assertion failed
 (declare-const %%location_label%%0 Bool)
 (assert
  (not (=>
    (ens%opaque!foo. $ USIZE x@)
    (=>
     (= tmp%1 (= x@ x@))
     (=>
      %%location_label%%0
      tmp%1
 )))))
 (get-info :version)
 (set-option :rlimit 30000000)
 (check-sat)
 (set-option :rlimit 0)
(pop)

This seems to also enable returning impl Fn().

TODO

  • Figure out where to do the well-form checks for OpaqueTy
  • There are a few places where the typing mode is changed from post-analysis to non_body_analysis to stop normalizing opaque types based on the body of the functions that define the opaque types. I'm not sure if I have changed all the right places.

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

@FeizaiYiHao FeizaiYiHao reopened this Aug 12, 2025
@FeizaiYiHao FeizaiYiHao marked this pull request as draft August 15, 2025 21:45
@FeizaiYiHao FeizaiYiHao marked this pull request as ready for review August 18, 2025 21:44
@FeizaiYiHao
Copy link
Contributor Author

Passing all the tests now. I'm looking at all the usages of PostAnalysis...

@FeizaiYiHao
Copy link
Contributor Author

@tjhance fixed most of the things. Would you take another look? TY!

Copy link
Collaborator

@tjhance tjhance left a comment

Choose a reason for hiding this comment

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

This mostly looks good to me; with respect to the code, I only have some minor nitpicks.

The last main thing I would like to see is a few more tests regarding interacting features:

  • A function marked external_body which returns an opaque type.
  • An assume_specification for a function that returns an opaque type.
  • A function with a returns clause and which returns an opaque type.
  • A function with a returns clause and which returns an opaque type, and which is marked allow_in_spec

It's fine if some of these are "not implemented" errors, I mostly want to get tests in so it's clear what the expected behavior is in these cases.

@FeizaiYiHao
Copy link
Contributor Author

@tjhance
external_body works with no problem.
assume_specification requires some additional checks for the trait bounds of the two OpaqueDefs from the external function and the assume_specification . Not sure if I did the right thing in compare_clasue_kind. Would you take a look?
returns will cause an error now. Not sure we should ever support it, since no way we can make the expression opaque?

@FeizaiYiHao FeizaiYiHao requested a review from tjhance September 2, 2025 02:46
@FeizaiYiHao
Copy link
Contributor Author

@tjhance ping in case the email notification didn't work

Copy link
Collaborator

@tjhance tjhance left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. This looks good to me.

TypX::ConstInt(_) => R::ret(|| typ.clone()),
TypX::ConstBool(_) => R::ret(|| typ.clone()),
TypX::Air(_) => R::ret(|| typ.clone()),
TypX::Opaque { .. } => R::ret(|| typ.clone()),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to visit the args, like the TypX::Datatype and TypX::Primitive cases.

pub typ_bounds: GenericBounds,
}

pub type Opaquetype = Arc<Spanned<OpaquetypeX>>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor comment: OpaqueType would be better than Opaquetype, since it's two words (whereas "datatype" is often just one word).

@parno
Copy link
Collaborator

parno commented Oct 13, 2025

@FeizaiYiHao Is this ready to be merged?

@FeizaiYiHao
Copy link
Contributor Author

@FeizaiYiHao Is this ready to be merged?

Wow sorry. Somehow the notification didn't work... Will quickly address the comments from @Chris-Hawblitzel and resolve the conflicts and then it should be good to be merged.

@parno
Copy link
Collaborator

parno commented Oct 13, 2025

Great, thanks!

@FeizaiYiHao
Copy link
Contributor Author

@parno Somehow the comments from Chirs were addressed a long time ago but the they poped out again in the threads. But anyways, this is ready to be merged. Sorry for the delay

@FeizaiYiHao
Copy link
Contributor Author

I found an unhandled case: for nested opaque types, we are missing the equality statements in AIR generation. Let me quickly fix and push again. Sorry.

Example:

    trait DummyTrait{
        spec fn dummy_spec(&self) -> bool;
    }
    impl DummyTrait for bool{
        spec fn dummy_spec(&self) -> bool{
            true
        }
    }    
    async fn foo() -> (ret: (impl DummyTrait, impl DummyTrait))
        ensures
            ret.awaited() ==> [email protected]_spec(),
    {
        (true, true)
    }
    async fn bar() -> (ret:())
    {
        let future = foo();
        assert(future.awaited() ==> [email protected]_spec());
        let ret_0 = future.await.0;
        assert(ret_0.dummy_spec());
    }
    fn baz() -> (ret: impl Future<Output = (impl DummyTrait, impl DummyTrait)>)
        ensures
            ret.awaited() ==> [email protected]_spec(),
    {
        foo() // Here Z3 doesn't know that the Output type of baz()'s return value equals to Output type of foo()'s
    }

@FeizaiYiHao
Copy link
Contributor Author

OK. fixed. Ready to be merged

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.

4 participants