about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/clean/mod.rs122
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css4
-rw-r--r--src/librustdoc/passes/html_tags.rs55
-rw-r--r--src/test/rustdoc-ui/invalid-html-self-closing-tag.rs70
-rw-r--r--src/test/rustdoc-ui/invalid-html-self-closing-tag.stderr80
-rw-r--r--src/test/rustdoc/async-trait.rs16
-rw-r--r--src/test/rustdoc/auxiliary/async-trait-dep.rs9
-rw-r--r--src/test/ui/async-await/async-fn-nonsend.stderr2
8 files changed, 300 insertions, 58 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index d86a2682641..a3a1abc3cf0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -415,6 +415,16 @@ fn clean_projection<'tcx>(
     cx: &mut DocContext<'tcx>,
     def_id: Option<DefId>,
 ) -> Type {
+    if cx.tcx.def_kind(ty.item_def_id) == DefKind::ImplTraitPlaceholder {
+        let bounds = cx
+            .tcx
+            .explicit_item_bounds(ty.item_def_id)
+            .iter()
+            .map(|(bound, _)| EarlyBinder(*bound).subst(cx.tcx, ty.substs))
+            .collect::<Vec<_>>();
+        return clean_middle_opaque_bounds(cx, bounds);
+    }
+
     let trait_ = clean_trait_ref_with_bindings(cx, ty.trait_ref(cx.tcx), ThinVec::new());
     let self_type = clean_middle_ty(ty.self_ty(), cx, None);
     let self_def_id = if let Some(def_id) = def_id {
@@ -1720,59 +1730,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
                 .iter()
                 .map(|(bound, _)| EarlyBinder(*bound).subst(cx.tcx, substs))
                 .collect::<Vec<_>>();
-            let mut regions = vec![];
-            let mut has_sized = false;
-            let mut bounds = bounds
-                .iter()
-                .filter_map(|bound| {
-                    let bound_predicate = bound.kind();
-                    let trait_ref = match bound_predicate.skip_binder() {
-                        ty::PredicateKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref),
-                        ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => {
-                            if let Some(r) = clean_middle_region(reg) {
-                                regions.push(GenericBound::Outlives(r));
-                            }
-                            return None;
-                        }
-                        _ => return None,
-                    };
-
-                    if let Some(sized) = cx.tcx.lang_items().sized_trait() {
-                        if trait_ref.def_id() == sized {
-                            has_sized = true;
-                            return None;
-                        }
-                    }
-
-                    let bindings: ThinVec<_> = bounds
-                        .iter()
-                        .filter_map(|bound| {
-                            if let ty::PredicateKind::Projection(proj) = bound.kind().skip_binder()
-                            {
-                                if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() {
-                                    Some(TypeBinding {
-                                        assoc: projection_to_path_segment(proj.projection_ty, cx),
-                                        kind: TypeBindingKind::Equality {
-                                            term: clean_middle_term(proj.term, cx),
-                                        },
-                                    })
-                                } else {
-                                    None
-                                }
-                            } else {
-                                None
-                            }
-                        })
-                        .collect();
-
-                    Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, bindings))
-                })
-                .collect::<Vec<_>>();
-            bounds.extend(regions);
-            if !has_sized && !bounds.is_empty() {
-                bounds.insert(0, GenericBound::maybe_sized(cx));
-            }
-            ImplTrait(bounds)
+            clean_middle_opaque_bounds(cx, bounds)
         }
 
         ty::Closure(..) => panic!("Closure"),
@@ -1785,6 +1743,64 @@ pub(crate) fn clean_middle_ty<'tcx>(
     }
 }
 
+fn clean_middle_opaque_bounds<'tcx>(
+    cx: &mut DocContext<'tcx>,
+    bounds: Vec<ty::Predicate<'tcx>>,
+) -> Type {
+    let mut regions = vec![];
+    let mut has_sized = false;
+    let mut bounds = bounds
+        .iter()
+        .filter_map(|bound| {
+            let bound_predicate = bound.kind();
+            let trait_ref = match bound_predicate.skip_binder() {
+                ty::PredicateKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref),
+                ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => {
+                    if let Some(r) = clean_middle_region(reg) {
+                        regions.push(GenericBound::Outlives(r));
+                    }
+                    return None;
+                }
+                _ => return None,
+            };
+
+            if let Some(sized) = cx.tcx.lang_items().sized_trait() {
+                if trait_ref.def_id() == sized {
+                    has_sized = true;
+                    return None;
+                }
+            }
+
+            let bindings: ThinVec<_> = bounds
+                .iter()
+                .filter_map(|bound| {
+                    if let ty::PredicateKind::Projection(proj) = bound.kind().skip_binder() {
+                        if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() {
+                            Some(TypeBinding {
+                                assoc: projection_to_path_segment(proj.projection_ty, cx),
+                                kind: TypeBindingKind::Equality {
+                                    term: clean_middle_term(proj.term, cx),
+                                },
+                            })
+                        } else {
+                            None
+                        }
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, bindings))
+        })
+        .collect::<Vec<_>>();
+    bounds.extend(regions);
+    if !has_sized && !bounds.is_empty() {
+        bounds.insert(0, GenericBound::maybe_sized(cx));
+    }
+    ImplTrait(bounds)
+}
+
 pub(crate) fn clean_field<'tcx>(field: &hir::FieldDef<'tcx>, cx: &mut DocContext<'tcx>) -> Item {
     let def_id = cx.tcx.hir().local_def_id(field.hir_id).to_def_id();
     clean_field_with_def_id(def_id, field.ident.name, clean_ty(field.ty, cx), cx)
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 5a1a6c0f0f9..934334e0c88 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -857,9 +857,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
 	   causes rounded corners and no border on iOS Safari. */
 	-webkit-appearance: none;
 	/* Override Normalize.css: we have margins and do
-	 not want to overflow - the `moz` attribute is necessary
-	 until Firefox 29, too early to drop at this point */
-	-moz-box-sizing: border-box !important;
+	 not want to overflow */
 	box-sizing: border-box !important;
 	outline: none;
 	border: 1px solid var(--border-color);
diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index 67fc71665cc..a89ed7c7ed4 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -184,7 +184,60 @@ fn extract_html_tag(
                     }
                     drop_tag(tags, tag_name, r, f);
                 } else {
-                    tags.push((tag_name, r));
+                    let mut is_self_closing = false;
+                    let mut quote_pos = None;
+                    if c != '>' {
+                        let mut quote = None;
+                        let mut after_eq = false;
+                        for (i, c) in text[pos..].char_indices() {
+                            if !c.is_whitespace() {
+                                if let Some(q) = quote {
+                                    if c == q {
+                                        quote = None;
+                                        quote_pos = None;
+                                        after_eq = false;
+                                    }
+                                } else if c == '>' {
+                                    break;
+                                } else if c == '/' && !after_eq {
+                                    is_self_closing = true;
+                                } else {
+                                    if is_self_closing {
+                                        is_self_closing = false;
+                                    }
+                                    if (c == '"' || c == '\'') && after_eq {
+                                        quote = Some(c);
+                                        quote_pos = Some(pos + i);
+                                    } else if c == '=' {
+                                        after_eq = true;
+                                    }
+                                }
+                            } else if quote.is_none() {
+                                after_eq = false;
+                            }
+                        }
+                    }
+                    if let Some(quote_pos) = quote_pos {
+                        let qr = Range { start: quote_pos, end: quote_pos };
+                        f(
+                            &format!("unclosed quoted HTML attribute on tag `{}`", tag_name),
+                            &qr,
+                            false,
+                        );
+                    }
+                    if is_self_closing {
+                        // https://html.spec.whatwg.org/#parse-error-non-void-html-element-start-tag-with-trailing-solidus
+                        let valid = ALLOWED_UNCLOSED.contains(&&tag_name[..])
+                            || tags.iter().take(pos + 1).any(|(at, _)| {
+                                let at = at.to_lowercase();
+                                at == "svg" || at == "math"
+                            });
+                        if !valid {
+                            f(&format!("invalid self-closing HTML tag `{}`", tag_name), &r, false);
+                        }
+                    } else {
+                        tags.push((tag_name, r));
+                    }
                 }
             }
             break;
diff --git a/src/test/rustdoc-ui/invalid-html-self-closing-tag.rs b/src/test/rustdoc-ui/invalid-html-self-closing-tag.rs
new file mode 100644
index 00000000000..d973a53cbc7
--- /dev/null
+++ b/src/test/rustdoc-ui/invalid-html-self-closing-tag.rs
@@ -0,0 +1,70 @@
+#![deny(rustdoc::invalid_html_tags)]
+
+/// <p/>
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct A;
+
+/// <p style/>
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct B;
+
+/// <p style=""/>
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct C;
+
+/// <p style="x"/>
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct D;
+
+/// <p style="x/></p>
+//~^ ERROR unclosed quoted HTML attribute
+pub struct E;
+
+/// <p style='x/></p>
+//~^ ERROR unclosed quoted HTML attribute
+pub struct F;
+
+/// <p style="x/"></p>
+pub struct G;
+
+/// <p style="x/"/>
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct H;
+
+/// <p / >
+//~^ ERROR invalid self-closing HTML tag `p`
+pub struct I;
+
+/// <br/>
+pub struct J;
+
+/// <a href=/></a>
+pub struct K;
+
+/// <a href=//></a>
+pub struct L;
+
+/// <a href="/"/>
+//~^ ERROR invalid self-closing HTML tag `a`
+pub struct M;
+
+/// <a href=x />
+//~^ ERROR invalid self-closing HTML tag `a`
+pub struct N;
+
+/// <a href= />
+//~^ ERROR invalid self-closing HTML tag `a`
+pub struct O;
+
+/// <a href=x/></a>
+pub struct P;
+
+/// <svg><rect width=1 height=1 /></svg>
+pub struct Q;
+
+/// <svg><rect width=1 height=/></svg>
+//~^ ERROR unclosed HTML tag `rect`
+pub struct R;
+
+/// <svg / q>
+pub struct S;
diff --git a/src/test/rustdoc-ui/invalid-html-self-closing-tag.stderr b/src/test/rustdoc-ui/invalid-html-self-closing-tag.stderr
new file mode 100644
index 00000000000..e45edfb43ff
--- /dev/null
+++ b/src/test/rustdoc-ui/invalid-html-self-closing-tag.stderr
@@ -0,0 +1,80 @@
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:3:5
+   |
+LL | /// <p/>
+   |     ^^
+   |
+note: the lint level is defined here
+  --> $DIR/invalid-html-self-closing-tag.rs:1:9
+   |
+LL | #![deny(rustdoc::invalid_html_tags)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:7:5
+   |
+LL | /// <p style/>
+   |     ^^
+
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:11:5
+   |
+LL | /// <p style=""/>
+   |     ^^
+
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:15:5
+   |
+LL | /// <p style="x"/>
+   |     ^^
+
+error: unclosed quoted HTML attribute on tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:19:14
+   |
+LL | /// <p style="x/></p>
+   |              ^
+
+error: unclosed quoted HTML attribute on tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:23:14
+   |
+LL | /// <p style='x/></p>
+   |              ^
+
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:30:5
+   |
+LL | /// <p style="x/"/>
+   |     ^^
+
+error: invalid self-closing HTML tag `p`
+  --> $DIR/invalid-html-self-closing-tag.rs:34:5
+   |
+LL | /// <p / >
+   |     ^^
+
+error: invalid self-closing HTML tag `a`
+  --> $DIR/invalid-html-self-closing-tag.rs:47:5
+   |
+LL | /// <a href="/"/>
+   |     ^^
+
+error: invalid self-closing HTML tag `a`
+  --> $DIR/invalid-html-self-closing-tag.rs:51:5
+   |
+LL | /// <a href=x />
+   |     ^^
+
+error: invalid self-closing HTML tag `a`
+  --> $DIR/invalid-html-self-closing-tag.rs:55:5
+   |
+LL | /// <a href= />
+   |     ^^
+
+error: unclosed HTML tag `rect`
+  --> $DIR/invalid-html-self-closing-tag.rs:65:10
+   |
+LL | /// <svg><rect width=1 height=/></svg>
+   |          ^^^^^
+
+error: aborting due to 12 previous errors
+
diff --git a/src/test/rustdoc/async-trait.rs b/src/test/rustdoc/async-trait.rs
new file mode 100644
index 00000000000..a473e467473
--- /dev/null
+++ b/src/test/rustdoc/async-trait.rs
@@ -0,0 +1,16 @@
+// aux-build:async-trait-dep.rs
+// edition:2021
+
+#![feature(async_fn_in_trait)]
+#![allow(incomplete_features)]
+
+extern crate async_trait_dep;
+
+pub struct Oink {}
+
+// @has 'async_trait/struct.Oink.html' '//h4[@class="code-header"]' "async fn woof()"
+impl async_trait_dep::Meow for Oink {
+    async fn woof() {
+        todo!()
+    }
+}
diff --git a/src/test/rustdoc/auxiliary/async-trait-dep.rs b/src/test/rustdoc/auxiliary/async-trait-dep.rs
new file mode 100644
index 00000000000..10a55dd0260
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/async-trait-dep.rs
@@ -0,0 +1,9 @@
+// edition:2021
+
+#![feature(async_fn_in_trait)]
+#![allow(incomplete_features)]
+
+pub trait Meow {
+    /// Who's a good dog?
+    async fn woof();
+}
diff --git a/src/test/ui/async-await/async-fn-nonsend.stderr b/src/test/ui/async-await/async-fn-nonsend.stderr
index 40ad46b4862..a7b872fe444 100644
--- a/src/test/ui/async-await/async-fn-nonsend.stderr
+++ b/src/test/ui/async-await/async-fn-nonsend.stderr
@@ -27,7 +27,7 @@ error: future cannot be sent between threads safely
 LL |     assert_send(non_sync_with_method_call());
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `non_sync_with_method_call` is not `Send`
    |
-   = help: the trait `Send` is not implemented for `dyn std::fmt::Write`
+   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `dyn std::fmt::Write`
 note: future is not `Send` as this value is used across an await
   --> $DIR/async-fn-nonsend.rs:46:14
    |