-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
#48538 Specialization: Intersection Impls #49624
Conversation
Your PR failed on Travis. Through arcane magic we have determined that the following fragments from the build log may contain information about the problem. Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
@@ -42,6 +42,7 @@ pub struct OverlapError { | |||
pub trait_desc: String, | |||
pub self_desc: Option<String>, | |||
pub intercrate_ambiguity_causes: Vec<IntercrateAmbiguityCause>, | |||
pub used_to_be_allowed: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used_to_be_allowed
is the name of a variable currently used in librustc/traits/specialize/mod.rs.
This variable is setted based on the result of the insertion and is linked to the IntercrateMode
used to check if two impls overalp.
Since, with my changes, any OverlapError
s is no more emitted at insertion time I keep track of this variable when creating the OverlapError
struct and i use it later when checking if two impls overlap.
@@ -291,8 +335,52 @@ impl<'a, 'gcx, 'tcx> Graph { | |||
|
|||
/// The parent of a given impl, which is the def id of the trait when the | |||
/// impl is a "specialization root". | |||
pub fn parent(&self, child: DefId) -> DefId { | |||
*self.parent.get(&child).unwrap() | |||
pub fn parent(&self, child: DefId) -> Vec<DefId> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we rename this to parents
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the comment should be altered. Something like:
Returns the def-id of the parent impl(s) for a given impl -- these are the impls which this impl specializes, meaning that it matches a subset of the types that they match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, better to phrase more precisely, I think:
Returns the def-id of the parent impl(s) for a given impl. An impl A has a parent impl B if A matches a strict subset of the types that B matches.
self.parent.get(&child).unwrap().clone() | ||
} | ||
|
||
pub fn build_graph(&self) -> (DataStructuresGraph<DefId, String>, DefIdMap<NodeIndex>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment would be great. What are these two things that are being returned, anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, worth documenting that in the graph, we have edges from children to parents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the specialization graph doesn't have the computations (Eg. postdominators) needed to check overlaps between impls this method build a "DataStructureGraph
" out of a specialization Graph
. The data returned are the "DataStructureGraph
" and a DefIdMap
that gives the NodeIndexes
of all the impls in the Graph
.
Here I originally thought to replace the specizlization Graph
directly with a DataStructureGraph
but I wasn't sure about that. Do you think i should replace it considering also what you wrote later?
it feels like it'd be nice to extract out this graph into something we can unit-test, that is more independent of the compiler -- eg., living in rustc_data_structures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be nice to replace it, yes, though maybe that should be a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e., it'd be nice to do that refactoring first without changing semantics
sg_graph: &DataStructuresGraph<DefId, String>, | ||
nodes_idx: &DefIdMap<NodeIndex>, | ||
impl1: DefId, | ||
impl2: DefId) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we format this method the rustfmt way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've run the rustfmt tool on the entire file. It seems that there were other methods not formatted.
let idx = sg_graph.add_node(node); | ||
nodes_idx.insert(node, idx); | ||
idx | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels like it'd be nice to extract out this graph into something we can unit-test, that is more independent of the compiler -- eg., living in rustc_data_structures
let impl2_idx = *nodes_idx.get(&impl2).unwrap(); | ||
|
||
let impl1_incoming_nodes = sg_graph.nodes_in_postorder(INCOMING, impl1_idx); | ||
let impl2_incoming_nodes = sg_graph.nodes_in_postorder(INCOMING, impl2_idx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Man, I must be missing something. I don't get why this works. It appears from the code above that the edges go from child to parent, and we are therefore walking from the children to the parents here...maybe I don't understand something? All of this tells me that we need a comment here!
But also, I think that we have to be pretty careful about this check. It's not enough for there to be an impl that specializes both of them: that impl has to cover the full intersection, right?
For example, consider this:
trait A { }
trait B { }
trait C { }
trait MyTrait { }
impl<T: A> MyTrait for T { }
impl<T: B> MyTrait for T { }
// This would be OK:
// impl<T: A + B> MyTrait for T { }
// But what about this:
impl<T: A + B + C> MyTrait for T { }
Does your code accept that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add your example as a test and let you know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikomatsakis My code accept that since A+B+C specializes both A and B but it shouldn't accept it, Right? Because it extends beyond the intersection of A and B. So an additional check is required in order to:
- obtain the intersection between the impls
- check if the impl that specializes both parents fits the intersection
Ping from triage @giannicic! We haven't heard from you on this in a while, will you have time to address the comments in the future? |
I think it's not so much that the impl should not be accepted as that the program on the whole should not -- that is, the intersection is partly covered, but not completely. I'm actually not entirely sure how to fix that. I guess we basically want to phrase the query:
In Chalk it'd certainly be possible to phrase such a query. I have to think a bit about how to phrase it in the current system, and also on the interactions around negative reasoning. |
@nikomatsakis |
Ping from triage, @giannicic ! Will you have time soon to continue progress on this PR? |
@shepmaster |
Ping from triage @giannicic! What's your progress on this? |
@pietroalbini |
@giannicic I've been wondering how things are going. Perhaps you can pop onto the discord instance and we can schedule a time to chat a bit. |
@nikomatsakis |
@giannicic there is no a great urgency; however, I wonder if it would be better to explore this on the chalk side first. It is (among other things) better equipped to answer questions like "does this exist". |
Sure @nikomatsakis. |
dc5f643
to
4726344
Compare
check if the intersection impl is complete
4726344
to
5067f54
Compare
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
@nikomatsakis |
Ping from triage @nikomatsakis! This PR needs your review. |
Ping from triage @nikomatsakis / @rust-lang/compiler, this PR needs your review. |
Ping from triage! This PR needs a review, can @nikomatsakis or someone else from @rust-lang/compiler review this? |
@giannicic that code is not part of this PR, right? that makes it harder for me to comment on. |
There are certainly some flaws in the code. For example, this part: let param_env1 = tcx.param_env(impl1);
let param_env2 = tcx.param_env(impl2);
let mut combined_param_envs_vec =
param_env1
.caller_bounds
.iter()
.chain(param_env2.caller_bounds.iter())
.map(|p| *p)
.collect::<Vec<_>>(); Those two I think that the gist of what you are trying to do is to setup a chalk query where:
I guess the premise is — if we can prove the clauses from impl C assuming only the clauses from A + B — than there must be "no gap", i.e., no cases uncovered. I think this is .. roughly true but we'll have to do some more sophisticated work. But actually I suspect we want to do this anyway. We sort of want to phrase the query like
Basically "if A and B both apply, then C always applies". That seems roughly right but the "both impls apply" bit is tricky. It requires being able to handle things like (Also, this condition is perhaps stricter than we want? You could imagine wanting multiple intersection impls that mutually cover the space.) |
Ping from triage @giannicic! It's been a while since we heard from you, will you have time to work on this again? |
My inclination here is probably to close this PR -- but I'd like to capture some of the insights we encountered into a chalk issue to let us discuss the overall design a bit more. |
☔ The latest upstream changes (presumably #51492) made this pull request unmergeable. Please resolve the merge conflicts. |
Ping from triage, @nikomatsakis / @giannicic: If I understand correctly this PR can't be merged in its current state and hasn't seen any updates in a while, so I'm closing it. Feel free to re-open in the future. |
This commit implements the Intersection Impls feature described in this blog post.
It'a preparatory work for the implementation of the "always applicable impls" feature.
I've some questions/doubts about the implementation:
r? @nikomatsakis