Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalization in type inference needs to generalize unbound type variables too #18653

Closed
nikomatsakis opened this issue Nov 5, 2014 · 6 comments
Labels
A-type-system Area: Type system

Comments

@nikomatsakis
Copy link
Contributor

I came across this subtle bug while working on the operator-dispatch branch. It has taken me quite some time to reduce it to a standalone test case. The fundamental problem is that, as currently implemented, when the type inferencer process a constraint like &'_0 _1 <: _2, where '_0, _1, and _2 are variables, it will create a new region variable but leave the type variable alone, so that _2 winds up being instantiated with &'_3 _1, where _0 : _3. That is good as far as it goes, but if _1 winds up being instantiated with something that contains regions, we lose degrees of freedom. Imagine for example that _1 winds up being instantiated with &'_4 int, then (substituting back to our original constraint), we have &'_0 &'_4 int <: &'_3 &'_4 int where '_0 : '_3. Again, this is true, but not as flexible we as we might like, since '_4 appears on both sides, depriving the region inferencer the change to adjust them independently.

To actually make this bug have a problem you seem to need a lot of moving ingredients. Here is my reduced (but probably not maximally...) example of code that ought to compile but doesn't:

use std::cell::RefCell;

enum Wrap<A> {
    WrapSome(A),
    WrapNone
}

struct T;
struct U;

// The Get trait. Key part here is that Get::get(X), where X has type
// Wrap<_>, cannot be resolved immediately.

trait Get<Sized? T> {
    fn get(&self) -> &T;
}

impl Get<MyShow+'static> for Wrap<T> {
    fn get(&self) -> &MyShow+'static {
        static x: uint = 42;
        &x
    }
}

impl Get<uint> for Wrap<U> {
    fn get(&self) -> &uint {
        static x: uint = 55;
        &x
    }
}

// The MyShow trait. In the original example, this was Show, but I
// pulled it out to isolate the test from changes to libstd.

trait MyShow { fn dummy(&self) { } }
impl<'a> MyShow for &'a MyShow+'a { }
impl MyShow for uint { }

fn constrain<'a>(rc: RefCell<&'a MyShow+'a>) { }

fn main() {
    // Here we do not know the full type of collection,
    // so the type of `collection` is `Wrap<_#0>`.
    let mut collection: Wrap<_> = WrapNone;

    {
        // ...and so we cannot resolve `Get::get` to a concrete
        // instance. Hence the type of `__arg0` is just `&_#1`, and
        // we know that `Wrap<_#0> : Get<_#1>`. Later `_#1` will be
        // resolved to `&MyShow+'static`, but the inference doesn't
        // know that yet.
        let __arg0 = Get::get(&collection);

        // Here we create a `RefCell`. This instantiates the type
        // parameter of `RefCell::new` with a fresh variable (`_#2`)
        // and then requires that `&'_3 _#1 <: _#2`. Because of the
        // bug, this means that `_#2` is instantiated with `&'_4 _#1`
        // (where `'_3 : '_4`). The important part is that only thing
        // that changes is that a fresh region is created, `_#1` is
        // carried though. (Without this bug, we would introduce a
        // fresh type variable, and everything that follows woudl play
        // out differently.)
        let __args_cell = RefCell::new(__arg0);

        // Finally we call `constrain`, whose argument is of type
        // `RefCell<&'_5 MyShow+'_5>` (once bound regions are
        // instantiated). Because `RefCell` is invariant, this means:
        //
        //     RefCell<_#2> <: RefCell<&'_5 MyShow+'_5>
        //     RefCell<&'_4 _#1> <: RefCell<&'_5 MyShow+'_5> // ...expand _#2
        //     &'_4 _#1 == &'_5 MyShow+'_5 // ...RefCell is invariant
        //
        // and hence:
        //
        //     '_4 == '_5
        //     _#1 == MyShow+'_5
        //
        // Now, recall that the type of `__arg0` was `&'_3 _#1`. If we consider
        // the various constraints we've accumulatd, then `__arg0` is now
        // constrained to be `&'_3 MyShow+'_5` where `'_3 : '_4`.
        constrain(__args_cell);
    }

    // Now we inform the inferencer that `_#0` is `Wrap<T>`. When
    // trait inference runs, this will infer `_#1` to
    // `MyShow+'static`. This in turn implies that the type of
    // `__arg0` winds up as `&'static MyShow+'static`, and hence that
    // `collections` must be borrowed for the static lifetime,
    // yielding an error.
    collection = WrapSome(T);
}

I've spelled out what happens in comments. The fix for this is this simple patch:

diff --git a/src/librustc/middle/typeck/infer/combine.rs b/src/librustc/middle/typeck/infer/combine.rs
index e51eb33..5857c4d 100644
--- a/src/librustc/middle/typeck/infer/combine.rs
+++ b/src/librustc/middle/typeck/infer/combine.rs
@@ -752,9 +752,14 @@ impl<'cx, 'tcx> ty_fold::TypeFolder<'tcx> for Generalizer<'cx, 'tcx> {
                     self.cycle_detected = true;
                     ty::mk_err()
                 } else {
-                    match self.infcx.type_variables.borrow().probe(vid) {
-                        Some(u) => self.fold_ty(u),
-                        None => t,
+                    let result = self.infcx.type_variables.borrow().probe(vid);
+                    match result {
+                        Some(u) => {
+                            return self.fold_ty(u);
+                        }
+                        None => {
+                            self.infcx.next_ty_var()
+                        }
                     }
                 }
             }

which causes unbound variables to be replaced with fresh variables. I am unsure of the performance impact of this patch, though, and I want to test.

@nikomatsakis
Copy link
Contributor Author

Hmm, the fix as expressed seems to have some issues as well, particularly around the test for #11384.

@nikomatsakis
Copy link
Contributor Author

(The problem with the fix, for reference, is that it creates an unbounded number of type variables, so somehow inference seems to get into a loop. I haven't dug deeply enough yet to really understand what's going on; I'm hoping to workaround this issue instead for time being in the place it's blocking me.)

@steveklabnik steveklabnik added the A-type-system Area: Type system label Jan 27, 2015
arielb1 pushed a commit to arielb1/rust that referenced this issue Sep 29, 2015
looks like some mix of rust-lang#18653 and `projection_must_outlive`, but
that needs to be investigated further (crater run?)
arielb1 pushed a commit to arielb1/rust that referenced this issue Oct 2, 2015
looks like some mix of rust-lang#18653 and `projection_must_outlive`, but
that needs to be investigated further (crater run?)
bors added a commit that referenced this issue Oct 3, 2015
By RFC1214:
>    Before calling a fn, we check that its argument and return types are WF.
    
The previous code only checked the trait-ref, which was not enough
in several cases.
    
As this is a soundness fix, it is a [breaking-change]. Some new annotations are needed, which I think are because of #18653 and the imperfection of `projection_must_outlive` (that can probably be worked around by moving the wf obligation later).
    
Fixes #28609

r? @nikomatsakis
@steveklabnik
Copy link
Member

An updated code sample:

use std::cell::RefCell;

enum Wrap<A> {
    WrapSome(A),
    WrapNone
}

use Wrap::*;

struct T;
struct U;

trait Get<T: ?Sized> {
    fn get(&self) -> &T;
}

impl Get<MyShow + 'static> for Wrap<T> {
    fn get(&self) -> &(MyShow + 'static) {
        static x: usize = 42;
        &x
    }
}

impl Get<usize> for Wrap<U> {
    fn get(&self) -> &usize {
        static x: usize = 55;
        &x
    }
}

trait MyShow { fn dummy(&self) { } }
impl<'a> MyShow for &'a (MyShow + 'a) { }
impl MyShow for usize { }
fn constrain<'a>(rc: RefCell<&'a (MyShow + 'a)>) { }

fn main() {
    let mut collection: Wrap<_> = WrapNone;

    {
        let __arg0 = Get::get(&collection);
        let __args_cell = RefCell::new(__arg0);
        constrain(__args_cell);
    }
    collection = WrapSome(T);
}fn constrain<'a>(rc: RefCell<&'a (MyShow + 'a)>) { }

fn main() {
    let mut collection: Wrap<_> = WrapNone;

    {
        let __arg0 = Get::get(&collection);
        let __args_cell = RefCell::new(__arg0);
        constrain(__args_cell);
    }
    collection = WrapSome(T);
}

Today's error:

hello.rs:41:32: 41:42 error: `collection` does not live long enough
hello.rs:41         let __arg0 = Get::get(&collection);
                                           ^~~~~~~~~~
note: reference must be valid for the static lifetime...
hello.rs:38:44: 46:2 note: ...but borrowed value is only valid for the block suffix following statement 0 at 38:43
hello.rs:38     let mut collection: Wrap<_> = WrapNone;
hello.rs:39 
hello.rs:40     {
hello.rs:41         let __arg0 = Get::get(&collection);
hello.rs:42         let __args_cell = RefCell::new(__arg0);
hello.rs:43         constrain(__args_cell);
            ...

@nikomatsakis
Copy link
Contributor Author

I've been doing some investigation here. Just backporting the patch above definitely leads to trouble (unconstrained type variables, at the moment). I'm trying to figure out the full story here, I'm not quite sure what's going wrong yet.

nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Apr 8, 2017
@eddyb eddyb mentioned this issue Apr 8, 2017
@nikomatsakis
Copy link
Contributor Author

Update: I've got this mostly working atop of PR #40570. Still working through some minor changes in behavior in the compile-fail tests, trying to decide how many lengths to go through to preserve the existing errors. Not sure yet of the best way to produce a backportable patch.

nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Apr 12, 2017
nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Apr 12, 2017
When we are generalizing a super/sub-type, we have to replace type
variables with a fresh variable (and not just region variables).  So if
we know that `Box<?T> <: ?U`, for example, we instantiate `?U` with
`Box<?V>` and then relate `Box<?T>` to `Box<?V>` (and hence require that
`?T <: ?V`).

This change has some complex interactions, however:

First, the occurs check must be updated to detect constraints like `?T
<: ?U` and `?U <: Box<?T>`. If we're not careful, we'll create a
never-ending sequence of new variables. To address this, we add a second
unification set into `type_variables` that tracks type variables related
through **either** equality **or** subtyping, and use that during the
occurs-check.

Second, the "fudge regions if ok" code was expecting no new type
variables to be created. It must be updated to create new type variables
outside of the probe. This is relatively straight-forward under the new
scheme, since type variables are now independent from one another, and
any relations are moderated by pending subtype obliations and so forth.
This part would be tricky to backport though.

cc rust-lang#18653
cc rust-lang#40951
@nikomatsakis
Copy link
Contributor Author

This is fixed by #40570

lnicola pushed a commit to lnicola/rust that referenced this issue Dec 11, 2024
Hash completion items to properly match them during /resolve
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system
Projects
None yet
Development

No branches or pull requests

2 participants