From 9ca80a29ecb7298566ee78c0f3a6ff1fd18434fe Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 10 Jan 2021 22:17:51 +0900 Subject: [PATCH 1/7] Fix typo in source-based-code-coverage.md preceeding -> preceding --- .../src/compiler-flags/source-based-code-coverage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md b/src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md index 98bcadd12ee24..8aca005214724 100644 --- a/src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md +++ b/src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md @@ -123,7 +123,7 @@ The `rustup` option is guaranteed to install a compatible version of the LLVM to ```shell $ rustup component add llvm-tools-preview $ cargo install cargo-binutils -$ cargo profdata -- --help # note the additional "--" preceeding the tool-specific arguments +$ cargo profdata -- --help # note the additional "--" preceding the tool-specific arguments ``` ## Creating coverage reports From 0acaa5af6bd11f425245743b59530e40b8e0d193 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 10 Jan 2021 10:00:17 -0500 Subject: [PATCH 2/7] Fix intra-doc links to `Self` and `crate` --- .../passes/collect_intra_doc_links.rs | 22 ++++++++++++++----- .../rustdoc/intra-doc-crate/auxiliary/self.rs | 3 +++ src/test/rustdoc/intra-doc-crate/self.rs | 3 +++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 11ee59b2401c8..c84726312f5db 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1024,12 +1024,18 @@ impl LinkCollector<'_, '_> { let resolved_self; // replace `Self` with suitable item's parent name - if path_str.starts_with("Self::") { + let is_lone_self = path_str == "Self"; + let is_lone_crate = path_str == "crate"; + if path_str.starts_with("Self::") || is_lone_self { if let Some(ref name) = self_name { - resolved_self = format!("{}::{}", name, &path_str[6..]); - path_str = &resolved_self; + if is_lone_self { + path_str = name; + } else { + resolved_self = format!("{}::{}", name, &path_str[6..]); + path_str = &resolved_self; + } } - } else if path_str.starts_with("crate::") { + } else if path_str.starts_with("crate::") || is_lone_crate { use rustc_span::def_id::CRATE_DEF_INDEX; // HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented. @@ -1038,8 +1044,12 @@ impl LinkCollector<'_, '_> { // HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous // (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root. // FIXME(#78696): This doesn't always work. - resolved_self = format!("self::{}", &path_str["crate::".len()..]); - path_str = &resolved_self; + if is_lone_crate { + path_str = "self"; + } else { + resolved_self = format!("self::{}", &path_str["crate::".len()..]); + path_str = &resolved_self; + } module_id = DefId { krate, index: CRATE_DEF_INDEX }; } diff --git a/src/test/rustdoc/intra-doc-crate/auxiliary/self.rs b/src/test/rustdoc/intra-doc-crate/auxiliary/self.rs index cdfe842f3ccbb..54902f12eb18c 100644 --- a/src/test/rustdoc/intra-doc-crate/auxiliary/self.rs +++ b/src/test/rustdoc/intra-doc-crate/auxiliary/self.rs @@ -1,4 +1,7 @@ #![crate_name = "cross_crate_self"] + +/// Link to [Self] +/// Link to [crate] pub struct S; impl S { diff --git a/src/test/rustdoc/intra-doc-crate/self.rs b/src/test/rustdoc/intra-doc-crate/self.rs index 62aef8e85afc9..4db63b12b6bb7 100644 --- a/src/test/rustdoc/intra-doc-crate/self.rs +++ b/src/test/rustdoc/intra-doc-crate/self.rs @@ -1,6 +1,9 @@ // aux-build:self.rs +// build-aux-docs extern crate cross_crate_self; // @has self/struct.S.html '//a[@href="../self/struct.S.html#method.f"]' "Self::f" +// @has self/struct.S.html '//a[@href="../self/struct.S.html"]' "Self" +// @has self/struct.S.html '//a[@href="../cross_crate_self/index.html"]' "crate" pub use cross_crate_self::S; From a52341d784b3b7a6acacae4c2f461de735651fee Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 10 Jan 2021 10:00:41 -0500 Subject: [PATCH 3/7] Small cleanups --- .../passes/collect_intra_doc_links.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index c84726312f5db..9252ed2e831e3 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -504,15 +504,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { match res { // FIXME(#76467): make this fallthrough to lookup the associated // item a separate function. - Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => { - assert_eq!(ns, ValueNS); - } - Res::Def(DefKind::AssocTy, _) => { - assert_eq!(ns, TypeNS); - } - Res::Def(DefKind::Variant, _) => { - return handle_variant(cx, res, extra_fragment); - } + Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => assert_eq!(ns, ValueNS), + Res::Def(DefKind::AssocTy, _) => assert_eq!(ns, TypeNS), + Res::Def(DefKind::Variant, _) => return handle_variant(cx, res, extra_fragment), // Not a trait item; just return what we found. Res::Primitive(ty) => { if extra_fragment.is_some() { @@ -522,12 +516,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { } return Ok((res, Some(ty.as_str().to_owned()))); } - Res::Def(DefKind::Mod, _) => { - return Ok((res, extra_fragment.clone())); - } - _ => { - return Ok((res, extra_fragment.clone())); - } + _ => return Ok((res, extra_fragment.clone())), } } From 06fd212d6a52e8820f39c5dac3c80dbd773df643 Mon Sep 17 00:00:00 2001 From: Nym Seddon Date: Sun, 10 Jan 2021 14:31:02 +0000 Subject: [PATCH 4/7] Add ABI argument to `find_mir_or_eval_fn` Add ABI argument for called function in `find_mir_or_eval_fn` and `call_extra_fn`. Useful for comparing with expected ABI in interpreters. Related to [miri/1631](https://github.com/rust-lang/miri/issues/1631) --- compiler/rustc_mir/src/const_eval/machine.rs | 2 ++ compiler/rustc_mir/src/interpret/machine.rs | 4 ++++ compiler/rustc_mir/src/interpret/terminator.rs | 11 ++++++----- compiler/rustc_mir/src/transform/const_prop.rs | 2 ++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir/src/const_eval/machine.rs b/compiler/rustc_mir/src/const_eval/machine.rs index 72912dd76ff52..02a9ec4df16d5 100644 --- a/compiler/rustc_mir/src/const_eval/machine.rs +++ b/compiler/rustc_mir/src/const_eval/machine.rs @@ -13,6 +13,7 @@ use rustc_middle::mir::AssertMessage; use rustc_session::Limit; use rustc_span::symbol::{sym, Symbol}; use rustc_target::abi::{Align, Size}; +use rustc_target::spec::abi::Abi; use crate::interpret::{ self, compile_time_machine, AllocId, Allocation, Frame, ImmTy, InterpCx, InterpResult, Memory, @@ -203,6 +204,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, fn find_mir_or_eval_fn( ecx: &mut InterpCx<'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, + _abi: Abi, args: &[OpTy<'tcx>], _ret: Option<(PlaceTy<'tcx>, mir::BasicBlock)>, _unwind: Option, // unwinding is not supported in consts diff --git a/compiler/rustc_mir/src/interpret/machine.rs b/compiler/rustc_mir/src/interpret/machine.rs index f50cc6c16ea16..a1a825b3268ae 100644 --- a/compiler/rustc_mir/src/interpret/machine.rs +++ b/compiler/rustc_mir/src/interpret/machine.rs @@ -10,6 +10,7 @@ use rustc_middle::mir; use rustc_middle::ty::{self, Ty}; use rustc_span::def_id::DefId; use rustc_target::abi::Size; +use rustc_target::spec::abi::Abi; use super::{ AllocId, Allocation, AllocationExtra, CheckInAllocMsg, Frame, ImmTy, InterpCx, InterpResult, @@ -144,6 +145,7 @@ pub trait Machine<'mir, 'tcx>: Sized { fn find_mir_or_eval_fn( ecx: &mut InterpCx<'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, + abi: Abi, args: &[OpTy<'tcx, Self::PointerTag>], ret: Option<(PlaceTy<'tcx, Self::PointerTag>, mir::BasicBlock)>, unwind: Option, @@ -154,6 +156,7 @@ pub trait Machine<'mir, 'tcx>: Sized { fn call_extra_fn( ecx: &mut InterpCx<'mir, 'tcx, Self>, fn_val: Self::ExtraFnVal, + abi: Abi, args: &[OpTy<'tcx, Self::PointerTag>], ret: Option<(PlaceTy<'tcx, Self::PointerTag>, mir::BasicBlock)>, unwind: Option, @@ -405,6 +408,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) { fn call_extra_fn( _ecx: &mut InterpCx<$mir, $tcx, Self>, fn_val: !, + _abi: Abi, _args: &[OpTy<$tcx>], _ret: Option<(PlaceTy<$tcx>, mir::BasicBlock)>, _unwind: Option, diff --git a/compiler/rustc_mir/src/interpret/terminator.rs b/compiler/rustc_mir/src/interpret/terminator.rs index a2931325a2863..575667f9a9525 100644 --- a/compiler/rustc_mir/src/interpret/terminator.rs +++ b/compiler/rustc_mir/src/interpret/terminator.rs @@ -219,7 +219,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let instance = match fn_val { FnVal::Instance(instance) => instance, FnVal::Other(extra) => { - return M::call_extra_fn(self, extra, args, ret, unwind); + return M::call_extra_fn(self, extra, caller_abi, args, ret, unwind); } }; @@ -264,10 +264,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { | ty::InstanceDef::CloneShim(..) | ty::InstanceDef::Item(_) => { // We need MIR for this fn - let body = match M::find_mir_or_eval_fn(self, instance, args, ret, unwind)? { - Some(body) => body, - None => return Ok(()), - }; + let body = + match M::find_mir_or_eval_fn(self, instance, caller_abi, args, ret, unwind)? { + Some(body) => body, + None => return Ok(()), + }; self.push_stack_frame( instance, diff --git a/compiler/rustc_mir/src/transform/const_prop.rs b/compiler/rustc_mir/src/transform/const_prop.rs index 1d949e020ed5c..2d6d0adf3bccd 100644 --- a/compiler/rustc_mir/src/transform/const_prop.rs +++ b/compiler/rustc_mir/src/transform/const_prop.rs @@ -25,6 +25,7 @@ use rustc_middle::ty::{ use rustc_session::lint; use rustc_span::{def_id::DefId, Span}; use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout}; +use rustc_target::spec::abi::Abi; use rustc_trait_selection::traits; use crate::const_eval::ConstEvalErr; @@ -187,6 +188,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> fn find_mir_or_eval_fn( _ecx: &mut InterpCx<'mir, 'tcx, Self>, _instance: ty::Instance<'tcx>, + _abi: Abi, _args: &[OpTy<'tcx>], _ret: Option<(PlaceTy<'tcx>, BasicBlock)>, _unwind: Option, From 6488aecb7405949f28b36bed534d2f03892644bd Mon Sep 17 00:00:00 2001 From: Camelid Date: Thu, 7 Jan 2021 21:21:21 -0800 Subject: [PATCH 5/7] Use standard formatting for "rust-call" ABI message Nearly all error messages start with a lowercase letter and don't use articles - instead they refer to the plural case. --- compiler/rustc_typeck/src/check/check.rs | 2 +- src/test/ui/abi/issues/issue-22565-rust-call.rs | 8 ++++---- src/test/ui/abi/issues/issue-22565-rust-call.stderr | 8 ++++---- src/test/ui/overloaded-calls-nontuple.rs | 4 ++-- src/test/ui/overloaded-calls-nontuple.stderr | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs index 6154414ff602a..9bb4c9b3719e3 100644 --- a/compiler/rustc_typeck/src/check/check.rs +++ b/compiler/rustc_typeck/src/check/check.rs @@ -113,7 +113,7 @@ pub(super) fn check_fn<'a, 'tcx>( }; if let Some(header) = item { - tcx.sess.span_err(header.span, "A function with the \"rust-call\" ABI must take a single non-self argument that is a tuple") + tcx.sess.span_err(header.span, "functions with the \"rust-call\" ABI must take a single non-self argument that is a tuple") } }; diff --git a/src/test/ui/abi/issues/issue-22565-rust-call.rs b/src/test/ui/abi/issues/issue-22565-rust-call.rs index 383eaab454ec4..a08e0bfb5e5da 100644 --- a/src/test/ui/abi/issues/issue-22565-rust-call.rs +++ b/src/test/ui/abi/issues/issue-22565-rust-call.rs @@ -1,25 +1,25 @@ #![feature(unboxed_closures)] extern "rust-call" fn b(_i: i32) {} -//~^ ERROR A function with the "rust-call" ABI must take a single non-self argument that is a tuple +//~^ ERROR functions with the "rust-call" ABI must take a single non-self argument that is a tuple trait Tr { extern "rust-call" fn a(); extern "rust-call" fn b() {} - //~^ ERROR A function with the "rust-call" ABI must take a single non-self argument + //~^ ERROR functions with the "rust-call" ABI must take a single non-self argument } struct Foo; impl Foo { extern "rust-call" fn bar() {} - //~^ ERROR A function with the "rust-call" ABI must take a single non-self argument + //~^ ERROR functions with the "rust-call" ABI must take a single non-self argument } impl Tr for Foo { extern "rust-call" fn a() {} - //~^ ERROR A function with the "rust-call" ABI must take a single non-self argument + //~^ ERROR functions with the "rust-call" ABI must take a single non-self argument } fn main () { diff --git a/src/test/ui/abi/issues/issue-22565-rust-call.stderr b/src/test/ui/abi/issues/issue-22565-rust-call.stderr index f7c3d1de793dc..3eee10bc5e951 100644 --- a/src/test/ui/abi/issues/issue-22565-rust-call.stderr +++ b/src/test/ui/abi/issues/issue-22565-rust-call.stderr @@ -1,22 +1,22 @@ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/issue-22565-rust-call.rs:3:1 | LL | extern "rust-call" fn b(_i: i32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/issue-22565-rust-call.rs:9:5 | LL | extern "rust-call" fn b() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/issue-22565-rust-call.rs:16:5 | LL | extern "rust-call" fn bar() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/issue-22565-rust-call.rs:21:5 | LL | extern "rust-call" fn a() {} diff --git a/src/test/ui/overloaded-calls-nontuple.rs b/src/test/ui/overloaded-calls-nontuple.rs index 76b114c55925b..07d44ff82b133 100644 --- a/src/test/ui/overloaded-calls-nontuple.rs +++ b/src/test/ui/overloaded-calls-nontuple.rs @@ -11,13 +11,13 @@ impl FnMut for S { extern "rust-call" fn call_mut(&mut self, z: isize) -> isize { self.x + self.y + z } - //~^^^ ERROR A function with the "rust-call" ABI must take a single non-self argument + //~^^^ ERROR functions with the "rust-call" ABI must take a single non-self argument } impl FnOnce for S { type Output = isize; extern "rust-call" fn call_once(mut self, z: isize) -> isize { self.call_mut(z) } - //~^ ERROR A function with the "rust-call" ABI must take a single non-self argument + //~^ ERROR functions with the "rust-call" ABI must take a single non-self argument } fn main() { diff --git a/src/test/ui/overloaded-calls-nontuple.stderr b/src/test/ui/overloaded-calls-nontuple.stderr index bdadb95db2947..8f299bc9434f3 100644 --- a/src/test/ui/overloaded-calls-nontuple.stderr +++ b/src/test/ui/overloaded-calls-nontuple.stderr @@ -1,10 +1,10 @@ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/overloaded-calls-nontuple.rs:11:5 | LL | extern "rust-call" fn call_mut(&mut self, z: isize) -> isize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: A function with the "rust-call" ABI must take a single non-self argument that is a tuple +error: functions with the "rust-call" ABI must take a single non-self argument that is a tuple --> $DIR/overloaded-calls-nontuple.rs:19:5 | LL | extern "rust-call" fn call_once(mut self, z: isize) -> isize { self.call_mut(z) } From 7af29abbc1c2531d4172a9c69f250007c2ff1037 Mon Sep 17 00:00:00 2001 From: Camelid Date: Sun, 10 Jan 2021 13:16:06 -0800 Subject: [PATCH 6/7] log-color: Detect TTY based on stderr, not stdout Logging goes to stderr, not stdout, so we should base our automated detection on stderr instead of stdout. Thanks to Ralf Jung for noticing and reporting the bug! --- compiler/rustc_driver/src/lib.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index d57ab2433ad1b..c2a0d8ef7ea11 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -566,6 +566,25 @@ fn stdout_isatty() -> bool { } } +// FIXME remove these and use winapi 0.3 instead +#[cfg(unix)] +fn stderr_isatty() -> bool { + unsafe { libc::isatty(libc::STDERR_FILENO) != 0 } +} + +#[cfg(windows)] +fn stderr_isatty() -> bool { + use winapi::um::consoleapi::GetConsoleMode; + use winapi::um::processenv::GetStdHandle; + use winapi::um::winbase::STD_ERROR_HANDLE; + + unsafe { + let handle = GetStdHandle(STD_ERROR_HANDLE); + let mut out = 0; + GetConsoleMode(handle, &mut out) != 0 + } +} + fn handle_explain(registry: Registry, code: &str, output: ErrorOutputType) { let normalised = if code.starts_with('E') { code.to_string() } else { format!("E{0:0>4}", code) }; @@ -1290,7 +1309,7 @@ pub fn init_env_logger(env: &str) { Ok(value) => match value.as_ref() { "always" => true, "never" => false, - "auto" => stdout_isatty(), + "auto" => stderr_isatty(), _ => early_error( ErrorOutputType::default(), &format!( @@ -1299,7 +1318,7 @@ pub fn init_env_logger(env: &str) { ), ), }, - Err(std::env::VarError::NotPresent) => stdout_isatty(), + Err(std::env::VarError::NotPresent) => stderr_isatty(), Err(std::env::VarError::NotUnicode(_value)) => early_error( ErrorOutputType::default(), "non-Unicode log color value: expected one of always, never, or auto", From e98f11b27df25e8497fc8a61e2290a2e536171fc Mon Sep 17 00:00:00 2001 From: Camelid Date: Sun, 10 Jan 2021 15:40:20 -0800 Subject: [PATCH 7/7] rustdoc: Remove `*` intra-doc alias for `pointer` It's not valid Rust code and it can easily be confused with a wildcard glob pattern or something else. People can always use `pointer` instead, so it's just removing an alias. It hasn't hit stable yet (I think it's still on nightly), so it's okay to remove it. (We can always add it back later if we change our mind too.) --- src/librustdoc/passes/collect_intra_doc_links.rs | 2 +- src/test/rustdoc/intra-doc/non-path-primitives.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 11ee59b2401c8..fd034548c17d2 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2092,7 +2092,7 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option { "array" => Array, "tuple" => Tuple, "unit" => Unit, - "pointer" | "*" | "*const" | "*mut" => RawPointer, + "pointer" | "*const" | "*mut" => RawPointer, "reference" | "&" | "&mut" => Reference, "fn" => Fn, "never" | "!" => Never, diff --git a/src/test/rustdoc/intra-doc/non-path-primitives.rs b/src/test/rustdoc/intra-doc/non-path-primitives.rs index ad4f6ddd9de69..a409744e409b0 100644 --- a/src/test/rustdoc/intra-doc/non-path-primitives.rs +++ b/src/test/rustdoc/intra-doc/non-path-primitives.rs @@ -11,11 +11,9 @@ // @has - '//a[@href="https://doc.rust-lang.org/nightly/std/primitive.pointer.html#method.is_null"]' 'pointer::is_null' // @has - '//a[@href="https://doc.rust-lang.org/nightly/std/primitive.pointer.html#method.is_null"]' '*const::is_null' // @has - '//a[@href="https://doc.rust-lang.org/nightly/std/primitive.pointer.html#method.is_null"]' '*mut::is_null' -// @has - '//a[@href="https://doc.rust-lang.org/nightly/std/primitive.pointer.html#method.is_null"]' '*::is_null' //! [pointer::is_null] //! [*const::is_null] //! [*mut::is_null] -//! [*::is_null] // @has - '//a[@href="https://doc.rust-lang.org/nightly/std/primitive.unit.html"]' 'unit' //! [unit]