-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for CSS module composes: and fix CSS precedence issues (#…
…3771) Adds support for the CSS module `composes:` rule. This also fixes a large issue with our chunk ordering and CSS precedence, where the BFS order of our chunks did not match the expected topological ordering.
- Loading branch information
Showing
39 changed files
with
1,125 additions
and
316 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use std::{collections::HashSet, future::Future}; | ||
|
||
use anyhow::Result; | ||
|
||
/// A trait that allows a graph traversal to get the children of a node. | ||
pub trait GetChildren<T, A = ()> { | ||
type Children: IntoIterator<Item = T>; | ||
type Future: Future<Output = Result<Self::Children>>; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future>; | ||
} | ||
|
||
// The different `Impl*` here are necessary in order to avoid the `Conflicting | ||
// implementations of trait` error when implementing `GetChildren` on different | ||
// kinds of `FnMut`. | ||
// See https://users.rust-lang.org/t/conflicting-implementation-when-implementing-traits-for-fn/53359/3 | ||
|
||
pub struct ImplRef; | ||
|
||
impl<T, C, F, CI> GetChildren<T, ImplRef> for C | ||
where | ||
C: FnMut(&T) -> F, | ||
F: Future<Output = Result<CI>>, | ||
CI: IntoIterator<Item = T>, | ||
{ | ||
type Children = CI; | ||
type Future = F; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future> { | ||
Some((self)(item)) | ||
} | ||
} | ||
|
||
pub struct ImplRefOption; | ||
|
||
impl<T, C, F, CI> GetChildren<T, ImplRefOption> for C | ||
where | ||
C: FnMut(&T) -> Option<F>, | ||
F: Future<Output = Result<CI>>, | ||
CI: IntoIterator<Item = T>, | ||
{ | ||
type Children = CI; | ||
type Future = F; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future> { | ||
(self)(item) | ||
} | ||
} | ||
|
||
pub struct ImplValue; | ||
|
||
impl<T, C, F, CI> GetChildren<T, ImplValue> for C | ||
where | ||
T: Copy, | ||
C: FnMut(T) -> F, | ||
F: Future<Output = Result<CI>>, | ||
CI: IntoIterator<Item = T>, | ||
{ | ||
type Children = CI; | ||
type Future = F; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future> { | ||
Some((self)(*item)) | ||
} | ||
} | ||
|
||
pub struct ImplValueOption; | ||
|
||
impl<T, C, F, CI> GetChildren<T, ImplValueOption> for C | ||
where | ||
T: Copy, | ||
C: FnMut(T) -> Option<F>, | ||
F: Future<Output = Result<CI>>, | ||
CI: IntoIterator<Item = T>, | ||
{ | ||
type Children = CI; | ||
type Future = F; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future> { | ||
(self)(*item) | ||
} | ||
} | ||
|
||
/// A [`GetChildren`] implementation that skips nodes that have already been | ||
/// visited. This is necessary to avoid repeated work when traversing non-tree | ||
/// graphs (i.e. where a child can have more than one parent). | ||
#[derive(Debug)] | ||
pub struct SkipDuplicates<T, C, A> { | ||
get_children: C, | ||
visited: HashSet<T>, | ||
_phantom: std::marker::PhantomData<A>, | ||
} | ||
|
||
impl<T, C, A> SkipDuplicates<T, C, A> { | ||
/// Create a new [`SkipDuplicates`] that wraps the given [`GetChildren`]. | ||
pub fn new(get_children: C) -> Self { | ||
Self { | ||
get_children, | ||
visited: HashSet::new(), | ||
_phantom: std::marker::PhantomData, | ||
} | ||
} | ||
} | ||
|
||
impl<T, C, A> GetChildren<T> for SkipDuplicates<T, C, A> | ||
where | ||
T: Eq + std::hash::Hash + Clone, | ||
C: GetChildren<T, A>, | ||
{ | ||
type Children = C::Children; | ||
type Future = C::Future; | ||
|
||
fn get_children(&mut self, item: &T) -> Option<Self::Future> { | ||
if !self.visited.contains(item) { | ||
self.visited.insert(item.clone()); | ||
self.get_children.get_children(item) | ||
} else { | ||
None | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/// A graph store is a data structure that will be built up during a graph | ||
/// traversal. It is used to store the results of the traversal. | ||
pub trait GraphStore<T>: Default { | ||
type Handle: Clone; | ||
|
||
// TODO(alexkirsz) An `entry(parent_handle) -> Entry` API would be more | ||
// efficient, as right now we're getting the same key multiple times. | ||
/// Inserts a node into the graph store, and returns a handle to it. | ||
fn insert(&mut self, parent_handle: Option<Self::Handle>, node: T) -> (Self::Handle, &T); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
use std::{future::Future, pin::Pin, task::ready}; | ||
|
||
use anyhow::Result; | ||
use futures::{stream::FuturesUnordered, Stream}; | ||
use pin_project_lite::pin_project; | ||
|
||
use super::{graph_store::GraphStore, GetChildren}; | ||
|
||
/// [`GraphTraversal`] is a utility type that can be used to traverse a graph of | ||
/// nodes, where each node can have a variable number of children. The traversal | ||
/// is done in parallel, and the order of the nodes in the traversal result is | ||
/// determined by the [`GraphStore`] parameter. | ||
pub struct GraphTraversal<S> { | ||
_store: std::marker::PhantomData<S>, | ||
} | ||
|
||
impl<S> GraphTraversal<S> { | ||
/// Visits the graph starting from the given `roots`, and returns a future | ||
/// that will resolve to the traversal result. | ||
pub fn visit<T, I, C, A>(roots: I, mut get_children: C) -> GraphTraversalFuture<T, S, C, A> | ||
where | ||
S: GraphStore<T>, | ||
I: IntoIterator<Item = T>, | ||
C: GetChildren<T, A>, | ||
{ | ||
let mut store = S::default(); | ||
let futures = FuturesUnordered::new(); | ||
for item in roots { | ||
let (parent_handle, item) = store.insert(None, item); | ||
if let Some(future) = get_children.get_children(item) { | ||
futures.push(WithHandle::new(future, parent_handle)); | ||
} | ||
} | ||
GraphTraversalFuture { | ||
store, | ||
futures, | ||
get_children, | ||
} | ||
} | ||
} | ||
|
||
/// A future that resolves to a [`GraphStore`] containing the result of a graph | ||
/// traversal. | ||
pub struct GraphTraversalFuture<T, S, C, A> | ||
where | ||
S: GraphStore<T>, | ||
C: GetChildren<T, A>, | ||
{ | ||
store: S, | ||
futures: FuturesUnordered<WithHandle<C::Future, S::Handle>>, | ||
get_children: C, | ||
} | ||
|
||
impl<T, S, C, A> Future for GraphTraversalFuture<T, S, C, A> | ||
where | ||
S: GraphStore<T>, | ||
C: GetChildren<T, A>, | ||
{ | ||
type Output = Result<S>; | ||
|
||
fn poll( | ||
self: std::pin::Pin<&mut Self>, | ||
cx: &mut std::task::Context<'_>, | ||
) -> std::task::Poll<Self::Output> { | ||
let this = unsafe { self.get_unchecked_mut() }; | ||
loop { | ||
let futures = unsafe { Pin::new_unchecked(&mut this.futures) }; | ||
if let Some((parent_handle, result)) = ready!(futures.poll_next(cx)) { | ||
match result { | ||
Ok(children) => { | ||
for item in children { | ||
let (child_handle, item) = | ||
this.store.insert(Some(parent_handle.clone()), item); | ||
|
||
if let Some(future) = this.get_children.get_children(item) { | ||
this.futures.push(WithHandle::new(future, child_handle)); | ||
} | ||
} | ||
} | ||
Err(err) => return std::task::Poll::Ready(Err(err)), | ||
} | ||
} else { | ||
return std::task::Poll::Ready(Ok(std::mem::take(&mut this.store))); | ||
} | ||
} | ||
} | ||
} | ||
|
||
pin_project! { | ||
struct WithHandle<T, P> | ||
where | ||
T: Future, | ||
{ | ||
#[pin] | ||
future: T, | ||
handle: Option<P>, | ||
} | ||
} | ||
|
||
impl<T, H> WithHandle<T, H> | ||
where | ||
T: Future, | ||
{ | ||
pub fn new(future: T, handle: H) -> Self { | ||
Self { | ||
future, | ||
handle: Some(handle), | ||
} | ||
} | ||
} | ||
|
||
impl<T, H> Future for WithHandle<T, H> | ||
where | ||
T: Future, | ||
{ | ||
type Output = (H, T::Output); | ||
|
||
fn poll( | ||
self: std::pin::Pin<&mut Self>, | ||
cx: &mut std::task::Context<'_>, | ||
) -> std::task::Poll<Self::Output> { | ||
let this = self.project(); | ||
match this.future.poll(cx) { | ||
std::task::Poll::Ready(result) => std::task::Poll::Ready(( | ||
this.handle.take().expect("polled after completion"), | ||
result, | ||
)), | ||
std::task::Poll::Pending => std::task::Poll::Pending, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
mod get_children; | ||
mod graph_store; | ||
mod graph_traversal; | ||
mod non_deterministic; | ||
mod reverse_topological; | ||
|
||
pub use get_children::{GetChildren, SkipDuplicates}; | ||
pub use graph_traversal::GraphTraversal; | ||
pub use non_deterministic::NonDeterministic; | ||
pub use reverse_topological::ReverseTopological; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use super::graph_store::GraphStore; | ||
|
||
/// A graph traversal that does not guarantee any particular order, and may not | ||
/// return the same order every time it is run. | ||
pub struct NonDeterministic<T> { | ||
output: Vec<T>, | ||
} | ||
|
||
impl<T> Default for NonDeterministic<T> { | ||
fn default() -> Self { | ||
Self { output: Vec::new() } | ||
} | ||
} | ||
|
||
impl<T> GraphStore<T> for NonDeterministic<T> { | ||
type Handle = (); | ||
|
||
fn insert(&mut self, _parent_handle: Option<Self::Handle>, node: T) -> (Self::Handle, &T) { | ||
self.output.push(node); | ||
((), self.output.last().unwrap()) | ||
} | ||
} | ||
|
||
impl<T> IntoIterator for NonDeterministic<T> { | ||
type Item = T; | ||
type IntoIter = <Vec<T> as IntoIterator>::IntoIter; | ||
|
||
fn into_iter(self) -> Self::IntoIter { | ||
self.output.into_iter() | ||
} | ||
} |
Oops, something went wrong.