async-graphql icon indicating copy to clipboard operation
async-graphql copied to clipboard

SimpleObject: error: lifetime may not live long enough

Open bbigras opened this issue 2 years ago • 26 comments

I got some weird error after using #[graphql(complex)] and #[ComplexObject] on other struct.

So the affected struct doesn't have the "complex" thing.

I'm probably using nested ComplexObject that uses some dataloaders.

I'll try to make a minimal, reproducible example.

Expected Behavior

Actual Behavior

13 | #[derive(Debug, PartialEq, SimpleObject)]
   |                            -^^^^^^^^^^^
   |                            |
   |                            let's call the lifetime of this reference `'2`
   |                            let's call the lifetime of this reference `'1`
   |                            associated function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'2`
   |                            in this derive macro expansion
   |
  ::: /home/bbigras/.cargo/registry/src/github.com-1ecc6299db9ec823/async-graphql-derive-3.0.38/src/lib.rs:47:1

Steps to Reproduce the Problem

Specifications

  • Version: 3.0.38
  • Platform: NixOS 22.05 (Quokka) x86_64
  • Subsystem:

bbigras avatar Apr 20 '22 23:04 bbigras

Please provide an example that reproduces this error.

sunli829 avatar May 01 '22 01:05 sunli829

I get the same thing locally since i've updated async-graphql, it happens sporadically

kennetpostigo avatar May 01 '22 16:05 kennetpostigo

After a clean build it goes away but comes back after a while

kennetpostigo avatar May 01 '22 16:05 kennetpostigo

After a clean build it goes away but comes back after a while

I had something similar too.

bbigras avatar May 01 '22 17:05 bbigras

Could you please provide an example that can reproduce this problem?😁

sunli829 avatar May 02 '22 03:05 sunli829

Yes sorry. I'll be back home tomorrow. (I should have still made one earlier as promised when I opened the issue though)

bbigras avatar May 02 '22 03:05 bbigras

I think I'm able to reproduce. There's a compiler bug, but I'm not sure yet if both problems are related.

error: lifetime may not live long enough
  --> src/query.rs:53:26
   |
53 | #[derive(Clone, Default, SimpleObject)]
   |                          -^^^^^^^^^^^
   |                          |
   |                          let's call the lifetime of this reference `'1`
   |                          let's call the lifetime of this reference `'2`
   |                          associated function was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
   |
   = note: this error originates in the derive macro `SimpleObject` (in Nightly builds, run with -Z macro-backtrace for more info)

error: internal compiler error: encountered incremental compilation error with mir_borrowck(async_graphql_900[a851]::query::{impl#16}::name)
  |
  = help: This is a known issue with the compiler. Run `cargo clean -p async_graphql_900` or `cargo clean` to allow your project to compile
  = note: Please follow the instructions below to create a bug report with the provided information
  = note: See <https://github.com/rust-lang/rust/issues/84970> for more information

bbigras avatar May 02 '22 22:05 bbigras

Now I can't reproduce it. I made a backup when it failed, but excluding my target folder.

So I never had the This is a known issue with the compiler. Run cargo clean -p async_graphql_900 or message before, but if I try cargo clean -p async_graphql_900 with my real project, the next build is fine.

Should we just assume that it's a bug with the compiler?

Here's my "minimal test case" https://github.com/bbigras/async-graphql-900 , but I can't reproduce the error at will.

EDIT: another weird thing is that I'm using the same compiler for my real project and the test case.

bbigras avatar May 02 '22 22:05 bbigras

Same on NixOS 22.05

rbozan avatar May 09 '22 18:05 rbozan

I have met this, run cargo clean first, then this should work.

al8n avatar May 23 '22 02:05 al8n

I have a simple script now for this until there is some workaround as doing cargo clean just takes way too long for me. The issue happens quite often for me, when the compiler complains about other things beforehand for some reason this issue pops up even though the other issues have been fixed. If your workflow consists of cargo watch -x "run" and having an autosave in your editor just makes this issue very common.

cargo clean -p async-graphql -p *your crate name*

rbozan avatar May 23 '22 06:05 rbozan

I'm seeing this as well. I'm wondering whether it's a bug in Rust itself. Not sure exactly how to repro the issue, although I see it quite frequently.

emwalker avatar Jun 01 '22 02:06 emwalker

cargo clean and cargo build works for me.

To reproduce -- I had async-graphql=3.0.38 and I upgraded to async-graphql=4.0.1 which immediately showed the error in my schema.

vaikzs avatar Jun 05 '22 04:06 vaikzs

Can confirm that it is happening frequently with both async-graphql 3.0.38 and 4.0.1 on both Rust v1.61 and v1.62

adzialocha avatar Jul 01 '22 19:07 adzialocha

@adzialocha It stopped happening. Not able to reproduce now either.

vaikzs avatar Jul 02 '22 00:07 vaikzs

Just started getting this, can't reliably trigger or reliably stop it from happening 😂 cargo clean temporarily fixes it as others said, perhaps something related to compilation order, or something not being recompiled?

async-graphql and async-graphql-axum 4.0.4 - stable-aarch64-apple-darwin

tbillington avatar Jul 03 '22 06:07 tbillington

I've been dealing with this for the past two days to no avail. cargo build -r works fine, but cargo check and cargo build (no release mode) fails. I've also been wondering whether it's got something to do with incremental compilation, but I'm really not sure, since I only get this when using the SimpleObject macro. I'm running the latest stable release of rustc and latest version of async-graphql.

Bberky avatar Jul 03 '22 20:07 Bberky

I was having this problem as well, and then it went away for good. Not sure what changed.

emwalker avatar Jul 03 '22 21:07 emwalker

I am having this problem too, in Rust 1.62.0. But it works fine if I switch back to Rust 1.61.0.

Tsing avatar Jul 04 '22 10:07 Tsing

Click to see how I reached that situation

Ok, I have investigated a bit the @Bberky's situation. They managed to reduce the issue to:

  #[derive(::async_graphql::SimpleObject)]
  pub struct A {
      field: String,
  }
  
+ pub struct B { field: String }

with a cargo c before and after that diff, on 1.62.0 , triggering a lifetime error message as reported by other people in this repo. By using an intermediary expansion trick, I got the expansion of SimpleObject on A to be:

Click to see the expansion
#[allow(clippy::all, clippy::pedantic)]
impl A {
    #[inline]
    #[allow(missing_docs)]
    async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
        ::std::result::Result::Ok(&self.field)
    }
}
#[allow(clippy::all, clippy::pedantic)]
#[async_graphql::async_trait::async_trait]
impl async_graphql::resolver_utils::ContainerType for A {
    async fn resolve_field(
        &self,
        ctx: &async_graphql::Context<'_>,
    ) -> async_graphql::ServerResult<::std::option::Option<async_graphql::Value>> {
        if ctx.item.node.name.node == "field" {
            let f = async move {
                self.field(ctx)
                    .await
                    .map_err(|err| err.into_server_error(ctx.item.pos))
            };
            let obj = f.await.map_err(|err| ctx.set_error_path(err))?;
            let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
            return async_graphql::OutputType::resolve(&obj, &ctx_obj, ctx.item)
                .await
                .map(::std::option::Option::Some);
        }
        ::std::result::Result::Ok(::std::option::Option::None)
    }
}
#[allow(clippy::all, clippy::pedantic)]
#[async_graphql::async_trait::async_trait]
impl async_graphql::OutputType for A {
    fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
        ::std::borrow::Cow::Borrowed("A")
    }
    fn create_type_info(registry: &mut async_graphql::registry::Registry) -> ::std::string::String {
        registry.create_output_type::<Self, _>(
            async_graphql::registry::MetaTypeId::Object,
            |registry| async_graphql::registry::MetaType::Object {
                name: ::std::borrow::Cow::into_owned(::std::borrow::Cow::Borrowed("A")),
                description: ::std::option::Option::None,
                fields: {
                    let mut fields = async_graphql::indexmap::IndexMap::new();
                    fields.insert(
                        ::std::borrow::ToOwned::to_owned("field"),
                        async_graphql::registry::MetaField {
                            name: ::std::borrow::ToOwned::to_owned("field"),
                            description: ::std::option::Option::None,
                            args: ::std::default::Default::default(),
                            ty: <String as async_graphql::OutputType>::create_type_info(registry),
                            deprecation: async_graphql::registry::Deprecation::NoDeprecated,
                            cache_control: async_graphql::CacheControl {
                                public: true,
                                max_age: 0usize,
                            },
                            external: false,
                            provides: ::std::option::Option::None,
                            requires: ::std::option::Option::None,
                            visible: ::std::option::Option::None,
                            compute_complexity: ::std::option::Option::None,
                        },
                    );
                    fields
                },
                cache_control: async_graphql::CacheControl {
                    public: true,
                    max_age: 0usize,
                },
                extends: false,
                keys: ::std::option::Option::None,
                visible: ::std::option::Option::None,
                is_subscription: false,
                rust_typename: ::std::any::type_name::<Self>(),
            },
        )
    }
    async fn resolve(
        &self,
        ctx: &async_graphql::ContextSelectionSet<'_>,
        _field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
    ) -> async_graphql::ServerResult<async_graphql::Value> {
        async_graphql::resolver_utils::resolve_container(ctx, self).await
    }
}
impl async_graphql::ObjectType for A {}

From there, once that diff is applied in between cargo checks, I get:

error: lifetime may not live long enough
 --> /private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.qeHHjilha7/target/debug-proc-macros/test-f4020daedf6df8e8.rs:5:95
  |
5 |       async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
  |  ____________________-___________________________________--_____________________________________^
  | |                    |                                   |
  | |                    |                                   let's call the lifetime of this reference `'2`
  | |                    let's call the lifetime of this reference `'1`
6 | |         ::std::result::Result::Ok(&self.field)
7 | |     }
  | |_____^ associated function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'2`

error: lifetime may not live long enough
 --> /private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.qeHHjilha7/target/debug-proc-macros/test-f4020daedf6df8e8.rs:6:9
  |
5 |     async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
  |                    -                                   -- let's call the lifetime of this reference `'2`
  |                    |
  |                    let's call the lifetime of this reference `'1`
6 |         ::std::result::Result::Ok(&self.field)
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`

So the interesting part is that this does not involve async_trait whatsoever (I was afraid it would), and so the following part of the expansion is the one hitting the incremental compilation bug:

#[allow(clippy::all, clippy::pedantic)]
impl A {
    #[inline]
    #[allow(missing_docs)]
    async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
        ::std::result::Result::Ok(&self.field)
    }
}

Finally, removing SimpleObject altogether from the equation, we end up with the following diff in between cargo checks running into the incremental compilation issue:

  pub struct A {
      field: String,
  }
  
  #[allow(clippy::all, clippy::pedantic)]
  impl A {
      #[inline]
      #[allow(missing_docs)]
      async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
          ::std::result::Result::Ok(&self.field)
      }
  }
  
+ pub struct B { field: String, }

which ends up yielding the following stand-alone repro which I'll report to rust [EDIT: done]:

fn main(){let _=std::process::Command::new("/bin/bash").args(&["-c", concat!(r##"{
set -euo pipefail
cd $(mktemp -d)


cargo init -q --name example --lib

cat> src/lib.rs <<'EOF'
    #![allow(unused)]
    struct Foo;
    
    impl Foo {
        async fn f(&self, _: &&()) -> &() {
            &()
        }
    }
EOF

(set -x
    cargo c -q
    { set +x; echo 'enum Bar {}'; } 2>/dev/null | tee -a src/lib.rs
    cargo c -q
)


echo ✅
} 2>&1"##)]).status();}

In the meantime, @sunli829 (or other maintainers of this crate), you may be able to dodge the compiler limitation by using https://docs.rs/fix-hidden-lifetime-bug on the non-async_trait-annotated async fns 🙂

danielhenrymantilla avatar Jul 04 '22 13:07 danielhenrymantilla

Workaround

I've submitted #972 which implements the usage of fix_hidden_lifetime_bug as a palliative workaround for that compiler regression. To use it, follow the instructions described in that PR.

danielhenrymantilla avatar Jul 04 '22 14:07 danielhenrymantilla

I was able to fix this issue by simple running export CARGO_INCREMENTAL=0, requires a better workstation to remain productive but it does work

ControlCplusControlV avatar Jul 05 '22 03:07 ControlCplusControlV

@ControlCplusControlV you should be able to save in compile times by using that PR through a patch rather than fully disabling incremental altogether 🙂:

[patch.crates-io.async-graphql]
git = "https://github.com/danielhenrymantilla/async-graphql.git"
branch = "workaround-for-1-62-bug"

danielhenrymantilla avatar Jul 05 '22 17:07 danielhenrymantilla

Here are some hopefully complete but overspecific repro steps:

rustup install nightly-2022-06-30 --force
rustup default nightly-2022-06-30
git clone https://github.com/bkonkle/rust-example-caster-api
cd rust-example-caster-api
cargo build

Edit ./libs/shows/src/episodes_service.rs:

diff --git a/libs/shows/src/episodes_service.rs b/libs/shows/src/episodes_service.rs
index a1b4c9f..e7dfddd 100644
--- a/libs/shows/src/episodes_service.rs
+++ b/libs/shows/src/episodes_service.rs
@@ -70,20 +70,7 @@ impl DefaultEpisodesService {
 #[async_trait]
 impl EpisodesService for DefaultEpisodesService {
     async fn get(&self, id: &str, with_show: &bool) -> Result<Option<Episode>> {
-        let query = episode_model::Entity::find_by_id(id.to_owned());
-
-        let episode = if *with_show {
-            query
-                .find_also_related(show_model::Entity)
-                .one(&*self.db)
-                .await?
-        } else {
-            query.one(&*self.db).await?.map(|u| (u, None))
-        };
-
-        let episode: EpisodeOption = episode.into();
-
-        Ok(episode.into())
+        Ok(None)
     }

     async fn get_by_ids(&self, ids: Vec<String>) -> Result<Vec<Episode>> {
cargo build # (assuming you didn't wrap cargo or turn off incremental builds)

and you'll see

error: lifetime may not live long enough
 --> libs/shows/src/show_model.rs:9:77
  |
9 |     Clone, Debug, Eq, PartialEq, DeriveEntityModel, Deserialize, Serialize, SimpleObject, PolarClass,
  |                                                                             -^^^^^^^^^^^
  |                                                                             |
  |                                                                             let's call the lifetime of this reference `'2`
  |                                                                             let's call the lifetime of this reference `'1`
  |                                                                             associated function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'2`
  |
  = note: this error originates in the derive macro `SimpleObject` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider introducing a named lifetime parameter and update trait if needed
  |
9 ~     Clone, Debug, Eq, PartialEq, DeriveEntityModel, Deserialize, Serialize, 'a'aimpleObject, PolarClass,
10| )]
...
16|     #[polar(attribute)]
17~     pub id<'a>: String,
  |

If you rm -rf target, it will then build fine.

ivan avatar Jul 06 '22 05:07 ivan

This is probably fixed now in Rust 1.62.1: https://github.com/rust-lang/rust/issues/98890

adzialocha avatar Jul 21 '22 14:07 adzialocha

I tested my https://github.com/async-graphql/async-graphql/issues/900#issuecomment-1175787840 to confirm nightly-2022-07-23 (rustc 1.64.0-nightly (848090dcd 2022-07-22)) is fixed.

ivan avatar Jul 23 '22 18:07 ivan

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Aug 27 '22 02:08 github-actions[bot]

This issue was closed because it has been stalled for 5 days with no activity.

github-actions[bot] avatar Sep 01 '22 02:09 github-actions[bot]