From 37570e82baf12951f8bb408884e31640b0d29dae Mon Sep 17 00:00:00 2001 From: Nathan West Date: Thu, 28 May 2020 22:07:13 -0400 Subject: [PATCH 01/62] Substantial refactor to the design of LineWriter This commit redesigns LineWriter to work more directly on the internals of BufWriter. This interface change is to enable a future Pull Request in which Stdout can be switched between Line and Block buffered mode. --- src/libstd/io/buffered.rs | 352 ++++++++++++++++++++++++++++---------- 1 file changed, 259 insertions(+), 93 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 046b1a6888024..fff4d0df7e769 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -449,6 +449,12 @@ impl Seek for BufReader { #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { inner: Option, + // FIXME: Replace this with a VecDeque. Because VecDeque is a Ring buffer, + // this would enable BufWriter to operate without any interior copies. + // It was also allow a much simpler implementation of flush_buf. The main + // blocker here is that VecDeque doesn't currently have the same + // slice-specific specializations (extend_from_slice, `Extend` + // specializations) buf: Vec, // #30888: If the inner writer panics in a call to write, we don't want to // write the buffered data a second time in BufWriter's destructor. This @@ -519,6 +525,13 @@ impl BufWriter { BufWriter { inner: Some(inner), buf: Vec::with_capacity(capacity), panicked: false } } + /// Send data in our local buffer into the inner writer, looping as + /// necessary until either it's all been sent or an error occurs. + /// + /// Because all the data in the buffer has been reported to our owner as + /// "successfully written" (by returning nonzero success values from + /// `write`), any 0-length writes from `inner` must be reported as i/o + /// errors from this method. fn flush_buf(&mut self) -> io::Result<()> { let mut written = 0; let len = self.buf.len(); @@ -548,6 +561,17 @@ impl BufWriter { ret } + /// Buffer some data without flushing it, regardless of the size of the + /// data. Writes as much as possible without exceeding capacity. Returns + /// the number of bytes written. + #[inline] + fn write_to_buffer(&mut self, buf: &[u8]) -> usize { + let available = self.buf.capacity() - self.buf.len(); + let amt_to_buffer = available.min(buf.len()); + self.buf.extend_from_slice(&buf[..amt_to_buffer]); + amt_to_buffer + } + /// Gets a reference to the underlying writer. /// /// # Examples @@ -665,7 +689,22 @@ impl Write for BufWriter { self.panicked = false; r } else { - self.buf.write(buf) + Ok(self.write_to_buffer(buf)) + } + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + if self.buf.len() + buf.len() > self.buf.capacity() { + self.flush_buf()?; + } + if buf.len() >= self.buf.capacity() { + self.panicked = true; + let r = self.get_mut().write_all(buf); + self.panicked = false; + r + } else { + self.write_to_buffer(buf); + Ok(()) } } @@ -818,6 +857,200 @@ impl fmt::Display for IntoInnerError { } } +/// Private helper struct for implementing the line-buffered writing logic. +/// This shim temporarily wraps a BufWriter, and uses its internals to +/// implement a line-buffered writer (specifically by using the internal +/// methods like write_to_buffer and flush_buffer). In this way, a more +/// efficient abstraction can be created than one that only had access to +/// `write` and `flush`, without needlessly duplicating a lot of the +/// implementation details of BufWriter. This also allows existing +/// `BufWriters` to be temporarily given line-buffering logic; this is what +/// enables Stdout to be alternately in line-buffered or block-buffered mode. +#[derive(Debug)] +pub(super) struct LineWriterShim<'a, W: Write> { + inner: &'a mut BufWriter, +} + +impl<'a, W: Write> LineWriterShim<'a, W> { + pub fn new(inner: &'a mut BufWriter) -> Self { + Self { inner } + } +} + +impl<'a, W: Write> Write for LineWriterShim<'a, W> { + /// Write some data into this BufReader with line buffering. This means + /// that, if any newlines are present in the data, the data up to the last + /// newline is sent directly to the underlying writer, and data after it + /// is buffered. Returns the number of bytes written. + /// + /// This function operates on a "best effort basis"; in keeping with the + /// convention of `Write::write`, it makes at most one attempt to write + /// new data to the underlying writer. If that write only reports a partial + /// success, the remaining data will be buffered. + /// + /// Because this function attempts to send completed lines to the underlying + /// writer, it will also flush the existing buffer if it contains any + /// newlines, even if the incoming data does not contain any newlines. + fn write(&mut self, buf: &[u8]) -> io::Result { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write + let newline_idx = match memchr::memrchr(b'\n', buf) { + None => { + // Check for prior partial line writes that need to be retried + if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + self.inner.flush_buf()?; + } + return self.inner.write(buf); + } + Some(i) => i, + }; + + // Flush existing content to prepare for our write + self.inner.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let lines = &buf[..newline_idx + 1]; + + // Write `lines` directly to the inner writer. In keeping with the + // `write` convention, make at most one attempt to add new (unbuffered) + // data. Because this write doesn't touch the BufWriter state directly, + // and the buffer is known to be empty, we don't need to worry about + // self.panicked here. + let flushed = self.inner.get_mut().write(lines)?; + + // Now that the write has succeeded, buffer the rest (or as much of + // the rest as possible). If there were any unwritten newlines, we + // only buffer out to the last unwritten newline; this helps prevent + // flushing partial lines on subsequent calls to write_buffered_lines. + let tail = &buf[flushed..]; + let buffered = match memchr::memrchr(b'\n', tail) { + None => self.inner.write_to_buffer(tail), + Some(i) => self.inner.write_to_buffer(&tail[..i + 1]), + }; + Ok(flushed + buffered) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } + + /// Write some vectored data into this BufReader with line buffering. This + /// means that, if any newlines are present in the data, the data up to + /// and including the buffer containing the last newline is sent directly + /// to the inner writer, and the data after it is buffered. Returns the + /// number of bytes written. + /// + /// This function operates on a "best effort basis"; in keeping with the + /// convention of `Write::write`, it makes at most one attempt to write + /// new data to the underlying writer. + /// + /// Because this function attempts to send completed lines to the underlying + /// writer, it will also flush the existing buffer if it contains any + /// newlines. + /// + /// Because sorting through an array of `IoSlice` can be a bit convoluted, + /// This method differs from write in the following ways: + /// + /// - It attempts to write all the buffers up to and including the one + /// containing the last newline. This means that it may attempt to + /// write a partial line. + /// - If the write only reports partial success, it does not attempt to + /// find the precise location of the written bytes and buffer the rest. + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // Find the buffer containing the last newline + let last_newline_buf_idx = bufs + .iter() + .enumerate() + .rev() + .find_map(|(i, buf)| memchr::memchr(b'\n', buf).map(|_| i)); + + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write + let last_newline_buf_idx = match last_newline_buf_idx { + // No newlines; just do a normal buffered write + None => { + // Check for prior partial line writes that need to be retried + if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + self.inner.flush_buf()?; + } + return self.inner.write_vectored(bufs); + } + Some(i) => i, + }; + + // Flush existing content to prepare for our write + self.inner.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let (lines, tail) = bufs.split_at(last_newline_buf_idx + 1); + + // Write `lines` directly to the inner writer. In keeping with the + // `write` convention, make at most one attempt to add new (unbuffered) + // data. Because this write doesn't touch the BufWriter state directly, + // and the buffer is known to be empty, we don't need to worry about + // self.panicked here. + let flushed = self.inner.write_vectored(lines)?; + + // Don't try to reconstruct the exact amount written; just bail + // in the event of a partial write + let lines_len = lines.iter().map(|buf| buf.len()).sum(); + if flushed < lines_len { + return Ok(flushed); + } + + // Now that the write has succeeded, buffer the rest (or as much of the + // rest as possible) + let buffered: usize = + tail.iter().map(|buf| self.inner.write_to_buffer(buf)).take_while(|&n| n > 0).sum(); + + Ok(flushed + buffered) + } + + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } + + /// Write some data into this BufReader with line buffering. This means + /// that, if any newlines are present in the data, the data up to the last + /// newline is sent directly to the underlying writer, and data after it + /// is buffered. + /// + /// Because this function attempts to send completed lines to the underlying + /// writer, it will also flush the existing buffer if it contains any + /// newlines, even if the incoming data does not contain any newlines. + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write + let newline_idx = match memchr::memrchr(b'\n', buf) { + None => { + // Check for prior partial line writes that need to be retried + if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + self.inner.flush_buf()?; + } + return self.inner.write_all(buf); + } + Some(i) => i, + }; + + // Flush existing content to prepare for our write + self.inner.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let (lines, tail) = buf.split_at(newline_idx + 1); + + // Write `lines` directly to the inner writer, bypassing the buffer. + self.inner.get_mut().write_all(lines)?; + + // Now that the write has succeeded, buffer the rest with BufWriter::write_all. + // This will buffer as much as possible, but continue flushing as + // necessary if our tail is huge. + self.inner.write_all(tail) + } +} + /// Wraps a writer and buffers output to it, flushing whenever a newline /// (`0x0a`, `'\n'`) is detected. /// @@ -885,7 +1118,6 @@ impl fmt::Display for IntoInnerError { #[stable(feature = "rust1", since = "1.0.0")] pub struct LineWriter { inner: BufWriter, - need_flush: bool, } impl LineWriter { @@ -926,7 +1158,7 @@ impl LineWriter { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: W) -> LineWriter { - LineWriter { inner: BufWriter::with_capacity(capacity, inner), need_flush: false } + LineWriter { inner: BufWriter::with_capacity(capacity, inner) } } /// Gets a reference to the underlying writer. @@ -1000,110 +1232,40 @@ impl LineWriter { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn into_inner(self) -> Result>> { - self.inner.into_inner().map_err(|IntoInnerError(buf, e)| { - IntoInnerError(LineWriter { inner: buf, need_flush: false }, e) - }) + self.inner + .into_inner() + .map_err(|IntoInnerError(buf, e)| IntoInnerError(LineWriter { inner: buf }, e)) } } #[stable(feature = "rust1", since = "1.0.0")] impl Write for LineWriter { fn write(&mut self, buf: &[u8]) -> io::Result { - if self.need_flush { - self.flush()?; - } - - // Find the last newline character in the buffer provided. If found then - // we're going to write all the data up to that point and then flush, - // otherwise we just write the whole block to the underlying writer. - let i = match memchr::memrchr(b'\n', buf) { - Some(i) => i, - None => return self.inner.write(buf), - }; - - // Ok, we're going to write a partial amount of the data given first - // followed by flushing the newline. After we've successfully written - // some data then we *must* report that we wrote that data, so future - // errors are ignored. We set our internal `need_flush` flag, though, in - // case flushing fails and we need to try it first next time. - let n = self.inner.write(&buf[..=i])?; - self.need_flush = true; - if self.flush().is_err() || n != i + 1 { - return Ok(n); - } + LineWriterShim::new(&mut self.inner).write(buf) + } - // At this point we successfully wrote `i + 1` bytes and flushed it out, - // meaning that the entire line is now flushed out on the screen. While - // we can attempt to finish writing the rest of the data provided. - // Remember though that we ignore errors here as we've successfully - // written data, so we need to report that. - match self.inner.write(&buf[i + 1..]) { - Ok(i) => Ok(n + i), - Err(_) => Ok(n), - } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() } - // Vectored writes are very similar to the writes above, but adjusted for - // the list of buffers that we have to write. fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - if self.need_flush { - self.flush()?; - } + LineWriterShim::new(&mut self.inner).write_vectored(bufs) + } - // Find the last newline, and failing that write the whole buffer - let last_newline = bufs.iter().enumerate().rev().find_map(|(i, buf)| { - let pos = memchr::memrchr(b'\n', buf)?; - Some((i, pos)) - }); - let (i, j) = match last_newline { - Some(pair) => pair, - None => return self.inner.write_vectored(bufs), - }; - let (prefix, suffix) = bufs.split_at(i); - let (buf, suffix) = suffix.split_at(1); - let buf = &buf[0]; - - // Write everything up to the last newline, flushing afterwards. Note - // that only if we finished our entire `write_vectored` do we try the - // subsequent - // `write` - let mut n = 0; - let prefix_amt = prefix.iter().map(|i| i.len()).sum(); - if prefix_amt > 0 { - n += self.inner.write_vectored(prefix)?; - self.need_flush = true; - } - if n == prefix_amt { - match self.inner.write(&buf[..=j]) { - Ok(m) => n += m, - Err(e) if n == 0 => return Err(e), - Err(_) => return Ok(n), - } - self.need_flush = true; - } - if self.flush().is_err() || n != j + 1 + prefix_amt { - return Ok(n); - } + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } - // ... and now write out everything remaining - match self.inner.write(&buf[j + 1..]) { - Ok(i) => n += i, - Err(_) => return Ok(n), - } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + LineWriterShim::new(&mut self.inner).write_all(buf) + } - if suffix.iter().map(|s| s.len()).sum::() == 0 { - return Ok(n); - } - match self.inner.write_vectored(suffix) { - Ok(i) => Ok(n + i), - Err(_) => Ok(n), - } + fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> { + LineWriterShim::new(&mut self.inner).write_all_vectored(bufs) } - fn flush(&mut self) -> io::Result<()> { - self.inner.flush()?; - self.need_flush = false; - Ok(()) + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> { + LineWriterShim::new(&mut self.inner).write_fmt(fmt) } } @@ -1137,7 +1299,11 @@ mod tests { impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } + if self.lengths.is_empty() { + Ok(0) + } else { + Ok(self.lengths.remove(0)) + } } } From 0f3815886a0d7aaefd222b4592c3ef2ccfd0ebc2 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 31 May 2020 01:29:48 -0400 Subject: [PATCH 02/62] Updated comments; only pre-flush newline terminated buffers --- src/libstd/io/buffered.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index fff4d0df7e769..0ae5db0df1778 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -896,8 +896,10 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // one line), just do a regular buffered write let newline_idx = match memchr::memrchr(b'\n', buf) { None => { - // Check for prior partial line writes that need to be retried - if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + // Check for prior partial line writes that need to be retried. + // Only retry if the buffer contains a completed line, to + // avoid flushing partial lines. + if let Some(b'\n') = self.inner.buffer().last().copied() { self.inner.flush_buf()?; } return self.inner.write(buf); @@ -916,13 +918,13 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // `write` convention, make at most one attempt to add new (unbuffered) // data. Because this write doesn't touch the BufWriter state directly, // and the buffer is known to be empty, we don't need to worry about - // self.panicked here. + // self.inner.panicked here. let flushed = self.inner.get_mut().write(lines)?; // Now that the write has succeeded, buffer the rest (or as much of // the rest as possible). If there were any unwritten newlines, we // only buffer out to the last unwritten newline; this helps prevent - // flushing partial lines on subsequent calls to write_buffered_lines. + // flushing partial lines on subsequent calls to LineWriterShim::write. let tail = &buf[flushed..]; let buffered = match memchr::memrchr(b'\n', tail) { None => self.inner.write_to_buffer(tail), @@ -970,8 +972,10 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { let last_newline_buf_idx = match last_newline_buf_idx { // No newlines; just do a normal buffered write None => { - // Check for prior partial line writes that need to be retried - if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + // Check for prior partial line writes that need to be retried. + // Only retry if the buffer contains a completed line, to + // avoid flushing partial lines. + if let Some(b'\n') = self.inner.buffer().last().copied() { self.inner.flush_buf()?; } return self.inner.write_vectored(bufs); @@ -1025,8 +1029,10 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // one line), just do a regular buffered write let newline_idx = match memchr::memrchr(b'\n', buf) { None => { - // Check for prior partial line writes that need to be retried - if memchr::memchr(b'\n', &self.inner.buffer()).is_some() { + // Check for prior partial line writes that need to be retried. + // Only retry if the buffer contains a completed line, to + // avoid flushing partial lines. + if let Some(b'\n') = self.inner.buffer().last().copied() { self.inner.flush_buf()?; } return self.inner.write_all(buf); From 4a1597f7a77b17e42217d02750af2ae8012430af Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 31 May 2020 02:37:36 -0400 Subject: [PATCH 03/62] Expressionify `LineWriterShim::write` --- src/libstd/io/buffered.rs | 62 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 0ae5db0df1778..6098416df19b2 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -892,9 +892,9 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write(&mut self, buf: &[u8]) -> io::Result { - // If there are no new newlines (that is, if this write is less than - // one line), just do a regular buffered write - let newline_idx = match memchr::memrchr(b'\n', buf) { + match memchr::memrchr(b'\n', buf) { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write None => { // Check for prior partial line writes that need to be retried. // Only retry if the buffer contains a completed line, to @@ -902,35 +902,37 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { if let Some(b'\n') = self.inner.buffer().last().copied() { self.inner.flush_buf()?; } - return self.inner.write(buf); + self.inner.write(buf) + } + // Otherwise, arrange for the lines to be written directly to the + // inner writer. + Some(newline_idx) => { + // Flush existing content to prepare for our write + self.inner.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let lines = &buf[..newline_idx + 1]; + + // Write `lines` directly to the inner writer. In keeping with the + // `write` convention, make at most one attempt to add new (unbuffered) + // data. Because this write doesn't touch the BufWriter state directly, + // and the buffer is known to be empty, we don't need to worry about + // self.inner.panicked here. + let flushed = self.inner.get_mut().write(lines)?; + + // Now that the write has succeeded, buffer the rest (or as much of + // the rest as possible). If there were any unwritten newlines, we + // only buffer out to the last unwritten newline; this helps prevent + // flushing partial lines on subsequent calls to LineWriterShim::write. + let tail = &buf[flushed..]; + let buffered = match memchr::memrchr(b'\n', tail) { + None => self.inner.write_to_buffer(tail), + Some(i) => self.inner.write_to_buffer(&tail[..i + 1]), + }; + Ok(flushed + buffered) } - Some(i) => i, - }; - - // Flush existing content to prepare for our write - self.inner.flush_buf()?; - - // This is what we're going to try to write directly to the inner - // writer. The rest will be buffered, if nothing goes wrong. - let lines = &buf[..newline_idx + 1]; - - // Write `lines` directly to the inner writer. In keeping with the - // `write` convention, make at most one attempt to add new (unbuffered) - // data. Because this write doesn't touch the BufWriter state directly, - // and the buffer is known to be empty, we don't need to worry about - // self.inner.panicked here. - let flushed = self.inner.get_mut().write(lines)?; - - // Now that the write has succeeded, buffer the rest (or as much of - // the rest as possible). If there were any unwritten newlines, we - // only buffer out to the last unwritten newline; this helps prevent - // flushing partial lines on subsequent calls to LineWriterShim::write. - let tail = &buf[flushed..]; - let buffered = match memchr::memrchr(b'\n', tail) { - None => self.inner.write_to_buffer(tail), - Some(i) => self.inner.write_to_buffer(&tail[..i + 1]), }; - Ok(flushed + buffered) } fn flush(&mut self) -> io::Result<()> { From 5edad37b04d4daac62b01946142589e0f69aa22c Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 31 May 2020 03:21:51 -0400 Subject: [PATCH 04/62] Expressionify write_all --- src/libstd/io/buffered.rs | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 6098416df19b2..de2a32345f1c9 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -932,7 +932,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { }; Ok(flushed + buffered) } - }; + } } fn flush(&mut self) -> io::Result<()> { @@ -1027,9 +1027,9 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - // If there are no new newlines (that is, if this write is less than - // one line), just do a regular buffered write - let newline_idx = match memchr::memrchr(b'\n', buf) { + match memchr::memrchr(b'\n', buf) { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write None => { // Check for prior partial line writes that need to be retried. // Only retry if the buffer contains a completed line, to @@ -1037,25 +1037,27 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { if let Some(b'\n') = self.inner.buffer().last().copied() { self.inner.flush_buf()?; } - return self.inner.write_all(buf); + self.inner.write_all(buf) } - Some(i) => i, - }; - - // Flush existing content to prepare for our write - self.inner.flush_buf()?; + // Otherwise, arrange for the lines to be written directly to the + // inner writer. + Some(newline_idx) => { + // Flush existing content to prepare for our write + self.inner.flush_buf()?; - // This is what we're going to try to write directly to the inner - // writer. The rest will be buffered, if nothing goes wrong. - let (lines, tail) = buf.split_at(newline_idx + 1); + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let (lines, tail) = buf.split_at(newline_idx + 1); - // Write `lines` directly to the inner writer, bypassing the buffer. - self.inner.get_mut().write_all(lines)?; + // Write `lines` directly to the inner writer, bypassing the buffer. + self.inner.get_mut().write_all(lines)?; - // Now that the write has succeeded, buffer the rest with BufWriter::write_all. - // This will buffer as much as possible, but continue flushing as - // necessary if our tail is huge. - self.inner.write_all(tail) + // Now that the write has succeeded, buffer the rest with BufWriter::write_all. + // This will buffer as much as possible, but continue flushing as + // necessary if our tail is huge. + self.inner.write_all(tail) + } + } } } From 1bf8ba3546aace5a16852d5f58fd9206934cc829 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 31 May 2020 03:28:07 -0400 Subject: [PATCH 05/62] x.py fmt --- src/libstd/io/buffered.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index de2a32345f1c9..35434979b85ad 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1309,11 +1309,7 @@ mod tests { impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { - Ok(0) - } else { - Ok(self.lengths.remove(0)) - } + if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } } } From e0dfdc683def9573f28d7f2f077790bc3c52132b Mon Sep 17 00:00:00 2001 From: Nathan West Date: Mon, 1 Jun 2020 00:26:48 -0400 Subject: [PATCH 06/62] Added check for `is_write_vectored` --- src/libstd/io/buffered.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 35434979b85ad..4ae2a5eaf95a9 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -961,7 +961,19 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// write a partial line. /// - If the write only reports partial success, it does not attempt to /// find the precise location of the written bytes and buffer the rest. + /// + /// If the underlying vector doesn't support vectored writing, we instead + /// simply write the first non-empty buffer with `write`. This way, we + /// get the benefits of more granular partial-line handling without losing + /// anything in efficiency fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + if !self.is_write_vectored() { + return match bufs.iter().find(|buf| !buf.is_empty()) { + Some(buf) => self.write(buf), + None => Ok(0), + } + } + // Find the buffer containing the last newline let last_newline_buf_idx = bufs .iter() From 2c3024b368cb2810e390f980c3f9820cc07389e8 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Mon, 1 Jun 2020 00:36:31 -0400 Subject: [PATCH 07/62] Add comment describing erroneous_flush_retried --- src/libstd/io/buffered.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 4ae2a5eaf95a9..04c1cbf5a17d3 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1766,6 +1766,15 @@ mod tests { } } + /// Previously the `LineWriter` could successfully write some bytes but + /// then fail to report that it has done so. Additionally, an erroneous + /// flush after a successful write was permanently ignored. + /// + /// Test that a line writer correctly reports the number of written bytes, + /// and that it attempts to flush buffered lines from previous writes + /// before processing new data + /// + /// Regression test for #37807 #[test] fn erroneous_flush_retried() { let a = AcceptOneThenFail { written: false, flushed: false }; From e89e2e42f9269fe401b1eef0288ff12c7941544f Mon Sep 17 00:00:00 2001 From: Nathan West Date: Mon, 1 Jun 2020 01:04:33 -0400 Subject: [PATCH 08/62] Added test stubs --- src/libstd/io/buffered.rs | 109 ++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 04c1cbf5a17d3..09b24d810c526 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -971,7 +971,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { return match bufs.iter().find(|buf| !buf.is_empty()) { Some(buf) => self.write(buf), None => Ok(0), - } + }; } // Find the buffer containing the last newline @@ -1321,7 +1321,11 @@ mod tests { impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } + if self.lengths.is_empty() { + Ok(0) + } else { + Ok(self.lengths.remove(0)) + } } } @@ -1742,27 +1746,48 @@ mod tests { b.iter(|| BufWriter::new(io::sink())); } - struct AcceptOneThenFail { - written: bool, + #[derive(Default, Clone)] + struct ProgrammableSink { + // Writes append to this slice + buffer: Vec, + + // Flushes set this flag flushed: bool, + + // If true, writes & flushes will always be an error + return_error: bool, + + // If set, only up to this number of bytes will be written in a single + // call to `write` + accept_prefix: Option, + + // If set, counts down with each write, and writes return an error + // when it hits 0 + max_writes: Option, } - impl Write for AcceptOneThenFail { + impl Write for ProgrammableSink { fn write(&mut self, data: &[u8]) -> io::Result { - if !self.written { - assert_eq!(data, b"a\nb\n"); - self.written = true; - Ok(data.len()) + if self.return_error { + Err(io::Error::new(io::ErrorKind::Other, "test")) } else { - Err(io::Error::new(io::ErrorKind::NotFound, "test")) + let len = match self.accept_prefix { + None => data.len(), + Some(prefix) => prefix.min(prefix), + }; + let data = &data[..len]; + self.buffer.extend_from_slice(data); + Ok(len) } } fn flush(&mut self) -> io::Result<()> { - assert!(self.written); - assert!(!self.flushed); - self.flushed = true; - Err(io::Error::new(io::ErrorKind::Other, "test")) + if self.return_error { + Err(io::Error::new(io::ErrorKind::Other, "test")) + } else { + self.flushed = true; + Ok(()) + } } } @@ -1777,15 +1802,7 @@ mod tests { /// Regression test for #37807 #[test] fn erroneous_flush_retried() { - let a = AcceptOneThenFail { written: false, flushed: false }; - - let mut l = LineWriter::new(a); - assert_eq!(l.write(b"a\nb\na").unwrap(), 4); - assert!(l.get_ref().written); - assert!(l.get_ref().flushed); - l.get_mut().flushed = false; - - assert_eq!(l.write(b"a").unwrap_err().kind(), io::ErrorKind::Other) + todo!() } #[test] @@ -1895,4 +1912,50 @@ mod tests { io::Error::new(io::ErrorKind::Other, "x") } } + + /// Test that, given this input: + /// + /// Line 1\n + /// Line 2\n + /// Line 3\n + /// Line 4 + /// + /// And given a result that only writes to midway through Line 2 + /// + /// That only up to the end of Line 3 is buffered + /// + /// This behavior is desirable because it prevents flushing partial lines + #[test] + fn test_partial_write_buffers_line() { + todo!() + } + + /// Test that, given this input: + /// + /// Line 1\n + /// Line 2\n + /// Line 3 + /// + /// And given that the full write of lines 1 and 2 was successful + /// That data up to Line 3 is buffered + #[test] + fn test_partial_line_buffered_after_line_write() { + todo!() + } + + /// Test that, given a partial line that exceeds the length of + /// LineBuffer's buffer (that is, without a trailing newline), that that + /// line is written to the inner writer + #[test] + fn test_long_line_flushed() { + todo!() + } + + /// Test that, given a very long partial line *after* successfully + /// flushing a complete line, that that line is buffered unconditionally, + /// and no additional writes take place + #[test] + fn test_long_tail_not_flushed() { + todo!() + } } From f0a08073e47cc4a832cea66a7ef6bd9506061a72 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Mon, 1 Jun 2020 17:36:55 -0400 Subject: [PATCH 09/62] Various testing & implementation updates: - Added a bunch of new unit tests - Removed test_line_buffer_fail_flush - Updated erroneous_flush_retried - Added helper methods to LineWriterShim for code clarity, to distinguish "self.buffer" (the BufWriter) from self.inner (the thing wrapped by the BufWriter) - Un-expressionized write & write_all - Added clause to bail early on Ok(0) --- src/libstd/io/buffered.rs | 344 +++++++++++++++++++++++++------------- 1 file changed, 232 insertions(+), 112 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 09b24d810c526..730ae8a0ee471 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -868,12 +868,30 @@ impl fmt::Display for IntoInnerError { /// enables Stdout to be alternately in line-buffered or block-buffered mode. #[derive(Debug)] pub(super) struct LineWriterShim<'a, W: Write> { - inner: &'a mut BufWriter, + buffer: &'a mut BufWriter, } impl<'a, W: Write> LineWriterShim<'a, W> { - pub fn new(inner: &'a mut BufWriter) -> Self { - Self { inner } + pub fn new(buffer: &'a mut BufWriter) -> Self { + Self { buffer } + } + + /// Get a reference to the inner writer (that is, the writer wrapped by + /// the BufWriter) + fn inner(&self) -> &W { + self.buffer.get_ref() + } + + /// Get a mutable reference to the inner writer (that is, the writer + /// wrapped by the BufWriter). Be careful with this writer, as writes to + /// it will bypass the buffer. + fn inner_mut(&mut self) -> &mut W { + self.buffer.get_mut() + } + + /// Get the content currently buffered in self.buffer + fn buffered(&self) -> &[u8] { + self.buffer.buffer() } } @@ -892,51 +910,58 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write(&mut self, buf: &[u8]) -> io::Result { - match memchr::memrchr(b'\n', buf) { + let newline_idx = match memchr::memrchr(b'\n', buf) { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write None => { // Check for prior partial line writes that need to be retried. // Only retry if the buffer contains a completed line, to // avoid flushing partial lines. - if let Some(b'\n') = self.inner.buffer().last().copied() { - self.inner.flush_buf()?; + if let Some(b'\n') = self.buffered().last().copied() { + self.buffer.flush_buf()?; } - self.inner.write(buf) + return self.buffer.write(buf); } // Otherwise, arrange for the lines to be written directly to the // inner writer. - Some(newline_idx) => { - // Flush existing content to prepare for our write - self.inner.flush_buf()?; - - // This is what we're going to try to write directly to the inner - // writer. The rest will be buffered, if nothing goes wrong. - let lines = &buf[..newline_idx + 1]; - - // Write `lines` directly to the inner writer. In keeping with the - // `write` convention, make at most one attempt to add new (unbuffered) - // data. Because this write doesn't touch the BufWriter state directly, - // and the buffer is known to be empty, we don't need to worry about - // self.inner.panicked here. - let flushed = self.inner.get_mut().write(lines)?; - - // Now that the write has succeeded, buffer the rest (or as much of - // the rest as possible). If there were any unwritten newlines, we - // only buffer out to the last unwritten newline; this helps prevent - // flushing partial lines on subsequent calls to LineWriterShim::write. - let tail = &buf[flushed..]; - let buffered = match memchr::memrchr(b'\n', tail) { - None => self.inner.write_to_buffer(tail), - Some(i) => self.inner.write_to_buffer(&tail[..i + 1]), - }; - Ok(flushed + buffered) - } + Some(newline_idx) => newline_idx, + }; + + // Flush existing content to prepare for our write + self.buffer.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let lines = &buf[..newline_idx + 1]; + + // Write `lines` directly to the inner writer. In keeping with the + // `write` convention, make at most one attempt to add new (unbuffered) + // data. Because this write doesn't touch the BufWriter state directly, + // and the buffer is known to be empty, we don't need to worry about + // self.buffer.panicked here. + let flushed = self.inner_mut().write(lines)?; + + // If buffer returns Ok(0), propagate that to the caller without + // doing additional buffering; otherwise we're just guaranteeing + // an "ErrorKind::WriteZero" later. + if flushed == 0 { + return Ok(0); } + + // Now that the write has succeeded, buffer the rest (or as much of + // the rest as possible). If there were any unwritten newlines, we + // only buffer out to the last unwritten newline; this helps prevent + // flushing partial lines on subsequent calls to LineWriterShim::write. + let tail = &buf[flushed..]; + let buffered = match memchr::memrchr(b'\n', tail) { + None => self.buffer.write_to_buffer(tail), + Some(i) => self.buffer.write_to_buffer(&tail[..i + 1]), + }; + Ok(flushed + buffered) } fn flush(&mut self) -> io::Result<()> { - self.inner.flush() + self.buffer.flush() } /// Write some vectored data into this BufReader with line buffering. This @@ -967,6 +992,8 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// get the benefits of more granular partial-line handling without losing /// anything in efficiency fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + // If there's no specialized behavior for write_vectored, just use + // write. This has the benefit of more granular partial-line handling. if !self.is_write_vectored() { return match bufs.iter().find(|buf| !buf.is_empty()) { Some(buf) => self.write(buf), @@ -989,16 +1016,16 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // Check for prior partial line writes that need to be retried. // Only retry if the buffer contains a completed line, to // avoid flushing partial lines. - if let Some(b'\n') = self.inner.buffer().last().copied() { - self.inner.flush_buf()?; + if let Some(b'\n') = self.buffered().last().copied() { + self.buffer.flush_buf()?; } - return self.inner.write_vectored(bufs); + return self.buffer.write_vectored(bufs); } Some(i) => i, }; // Flush existing content to prepare for our write - self.inner.flush_buf()?; + self.buffer.flush_buf()?; // This is what we're going to try to write directly to the inner // writer. The rest will be buffered, if nothing goes wrong. @@ -1009,7 +1036,14 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // data. Because this write doesn't touch the BufWriter state directly, // and the buffer is known to be empty, we don't need to worry about // self.panicked here. - let flushed = self.inner.write_vectored(lines)?; + let flushed = self.inner_mut().write_vectored(lines)?; + + // If inner returns Ok(0), propagate that to the caller without + // doing additional buffering; otherwise we're just guaranteeing + // an "ErrorKind::WriteZero" later. + if flushed == 0 { + return Ok(0); + } // Don't try to reconstruct the exact amount written; just bail // in the event of a partial write @@ -1021,13 +1055,15 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // Now that the write has succeeded, buffer the rest (or as much of the // rest as possible) let buffered: usize = - tail.iter().map(|buf| self.inner.write_to_buffer(buf)).take_while(|&n| n > 0).sum(); + tail.iter().map(|buf| self.buffer.write_to_buffer(buf)).take_while(|&n| n > 0).sum(); Ok(flushed + buffered) } fn is_write_vectored(&self) -> bool { - self.inner.is_write_vectored() + // It's hard to imagine these diverging, but it's worth checking + // just in case, because we call `write_vectored` on both. + self.buffer.is_write_vectored() && self.inner().is_write_vectored() } /// Write some data into this BufReader with line buffering. This means @@ -1039,37 +1075,37 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - match memchr::memrchr(b'\n', buf) { + let newline_idx = match memchr::memrchr(b'\n', buf) { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write None => { // Check for prior partial line writes that need to be retried. // Only retry if the buffer contains a completed line, to // avoid flushing partial lines. - if let Some(b'\n') = self.inner.buffer().last().copied() { - self.inner.flush_buf()?; + if let Some(b'\n') = self.buffered().last().copied() { + self.buffer.flush_buf()?; } - self.inner.write_all(buf) + return self.buffer.write_all(buf); } // Otherwise, arrange for the lines to be written directly to the // inner writer. - Some(newline_idx) => { - // Flush existing content to prepare for our write - self.inner.flush_buf()?; + Some(newline_idx) => newline_idx, + }; - // This is what we're going to try to write directly to the inner - // writer. The rest will be buffered, if nothing goes wrong. - let (lines, tail) = buf.split_at(newline_idx + 1); + // Flush existing content to prepare for our write + self.buffer.flush_buf()?; - // Write `lines` directly to the inner writer, bypassing the buffer. - self.inner.get_mut().write_all(lines)?; + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let (lines, tail) = buf.split_at(newline_idx + 1); - // Now that the write has succeeded, buffer the rest with BufWriter::write_all. - // This will buffer as much as possible, but continue flushing as - // necessary if our tail is huge. - self.inner.write_all(tail) - } - } + // Write `lines` directly to the inner writer, bypassing the buffer. + self.inner_mut().write_all(lines)?; + + // Now that the write has succeeded, buffer the rest with + // BufWriter::write_all. This will buffer as much as possible, but + // continue flushing as necessary if our tail is huge. + self.buffer.write_all(tail) } } @@ -1310,7 +1346,7 @@ where #[cfg(test)] mod tests { use crate::io::prelude::*; - use crate::io::{self, BufReader, BufWriter, IoSlice, LineWriter, SeekFrom}; + use crate::io::{self, BufReader, BufWriter, ErrorKind, IoSlice, LineWriter, SeekFrom}; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::thread; @@ -1598,34 +1634,6 @@ mod tests { assert_eq!(v, []); } - #[test] - fn test_line_buffer_fail_flush() { - // Issue #32085 - struct FailFlushWriter<'a>(&'a mut Vec); - - impl Write for FailFlushWriter<'_> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Err(io::Error::new(io::ErrorKind::Other, "flush failed")) - } - } - - let mut buf = Vec::new(); - { - let mut writer = LineWriter::new(FailFlushWriter(&mut buf)); - let to_write = b"abc\ndef"; - if let Ok(written) = writer.write(to_write) { - assert!(written < to_write.len(), "didn't flush on new line"); - // PASS - return; - } - } - assert!(buf.is_empty(), "write returned an error but wrote data"); - } - #[test] fn test_line_buffer() { let mut writer = LineWriter::new(Vec::new()); @@ -1746,44 +1754,62 @@ mod tests { b.iter(|| BufWriter::new(io::sink())); } + /// A simple `Write` target, designed to be wrapped by `LineWriter` / + /// `BufWriter` / etc, that can have its `write` & `flush` behavior + /// configured #[derive(Default, Clone)] struct ProgrammableSink { // Writes append to this slice - buffer: Vec, + pub buffer: Vec, - // Flushes set this flag - flushed: bool, + // Flush sets this flag + pub flushed: bool, // If true, writes & flushes will always be an error - return_error: bool, + pub always_error: bool, // If set, only up to this number of bytes will be written in a single // call to `write` - accept_prefix: Option, + pub accept_prefix: Option, // If set, counts down with each write, and writes return an error // when it hits 0 - max_writes: Option, + pub max_writes: Option, + + // If set, attempting to write when max_writes == Some(0) will be an + // error; otherwise, it will return Ok(0). + pub error_after_max_writes: bool, } impl Write for ProgrammableSink { fn write(&mut self, data: &[u8]) -> io::Result { - if self.return_error { - Err(io::Error::new(io::ErrorKind::Other, "test")) - } else { - let len = match self.accept_prefix { - None => data.len(), - Some(prefix) => prefix.min(prefix), - }; - let data = &data[..len]; - self.buffer.extend_from_slice(data); - Ok(len) + if self.always_error { + return Err(io::Error::new(io::ErrorKind::Other, "test - write always_error")); + } + + match self.max_writes { + Some(0) if self.error_after_max_writes => { + return Err(io::Error::new(io::ErrorKind::Other, "test - max_writes")) + } + Some(0) => return Ok(0), + Some(ref mut count) => *count -= 1, + None => {} } + + let len = match self.accept_prefix { + None => data.len(), + Some(prefix) => data.len().min(prefix), + }; + + let data = &data[..len]; + self.buffer.extend_from_slice(data); + + Ok(len) } fn flush(&mut self) -> io::Result<()> { - if self.return_error { - Err(io::Error::new(io::ErrorKind::Other, "test")) + if self.always_error { + Err(io::Error::new(io::ErrorKind::Other, "test - flush always_error")) } else { self.flushed = true; Ok(()) @@ -1802,7 +1828,27 @@ mod tests { /// Regression test for #37807 #[test] fn erroneous_flush_retried() { - todo!() + let writer = ProgrammableSink { + // Only write up to 4 bytes at a time + accept_prefix: Some(4), + + // Accept the first two writes, then error the others + max_writes: Some(2), + error_after_max_writes: true, + + ..Default::default() + }; + + // This should write the first 4 bytes. The rest will be buffered, out + // to the last newline. + let mut writer = LineWriter::new(writer); + assert_eq!(writer.write(b"a\nb\nc\nd\ne").unwrap(), 8); + + // This write should attempt to flush "c\nd\n", then buffer "e". No + // errors should happen here because no further writes should be + // attempted against `writer`. + assert_eq!(writer.write(b"e").unwrap(), 1); + assert_eq!(&writer.get_ref().buffer, b"a\nb\nc\nd\n"); } #[test] @@ -1927,7 +1973,14 @@ mod tests { /// This behavior is desirable because it prevents flushing partial lines #[test] fn test_partial_write_buffers_line() { - todo!() + let writer = ProgrammableSink { accept_prefix: Some(13), ..Default::default() }; + let mut writer = LineWriter::new(writer); + + assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3\nLine4").unwrap(), 21); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2"); + + assert_eq!(writer.write(b"Line 4").unwrap(), 6); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); } /// Test that, given this input: @@ -1940,7 +1993,14 @@ mod tests { /// That data up to Line 3 is buffered #[test] fn test_partial_line_buffered_after_line_write() { - todo!() + let writer = ProgrammableSink::default(); + let mut writer = LineWriter::new(writer); + + assert_eq!(writer.write(b"Line 1\nLine 2\nLine 3").unwrap(), 20); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\n"); + + assert!(writer.flush().is_ok()); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3"); } /// Test that, given a partial line that exceeds the length of @@ -1948,14 +2008,74 @@ mod tests { /// line is written to the inner writer #[test] fn test_long_line_flushed() { - todo!() + let writer = ProgrammableSink::default(); + let mut writer = LineWriter::with_capacity(5, writer); + + assert_eq!(writer.write(b"0123456789").unwrap(), 10); + assert_eq!(&writer.get_ref().buffer, b"0123456789"); } /// Test that, given a very long partial line *after* successfully /// flushing a complete line, that that line is buffered unconditionally, - /// and no additional writes take place + /// and no additional writes take place. This assures the property that + /// `write` should make at-most-one attempt to write new data. #[test] fn test_long_tail_not_flushed() { - todo!() + let writer = ProgrammableSink::default(); + let mut writer = LineWriter::with_capacity(5, writer); + + // Assert that Line 1\n is flushed, and 01234 is buffered + assert_eq!(writer.write(b"Line 1\n0123456789").unwrap(), 12); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); + + // Because the buffer is full, this subsequent write will flush it + assert_eq!(writer.write(b"5").unwrap(), 1); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n01234"); + } + + /// Test that, if an attempt to pre-flush buffered data returns Ok(0), + /// this is propagated as an error. + #[test] + fn test_line_buffer_write0_error() { + let writer = ProgrammableSink { + // Accept one write, then return Ok(0) on subsequent ones + max_writes: Some(1), + + ..Default::default() + }; + let mut writer = LineWriter::new(writer); + + // This should write "Line 1\n" and buffer "Partial" + assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); + + // This will attempt to flush "partial", which will return Ok(0), which + // needs to be an error, because we've already informed the client + // that we accepted the write. + let err = writer.write(b" Line End\n").unwrap_err(); + assert_eq!(err.kind(), ErrorKind::WriteZero); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); + } + + /// Test that, if a write returns Ok(0) after a successful pre-flush, this + /// is propogated as Ok(0) + #[test] + fn test_line_buffer_write0_normal() { + let writer = ProgrammableSink { + // Accept two writes, then return Ok(0) on subsequent ones + max_writes: Some(2), + + ..Default::default() + }; + let mut writer = LineWriter::new(writer); + + // This should write "Line 1\n" and buffer "Partial" + assert_eq!(writer.write(b"Line 1\nPartial").unwrap(), 14); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); + + // This will flush partial, which will succeed, but then return Ok(0) + // when flushing " Line End\n" + assert_eq!(writer.write(b" Line End\n").unwrap(), 0); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nPartial"); } } From b6296e88f0176255e67672a70c9761729e20f33f Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 00:43:06 -0400 Subject: [PATCH 10/62] Tons of testing updates, other minor changes - Cleaned up BufWriter::seek - Updated line_vectored test - Updated line_vectored_partial_and_errors test - Added several new tests --- src/libstd/io/buffered.rs | 130 +++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 24 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 730ae8a0ee471..ced84b777ce77 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -751,7 +751,8 @@ impl Seek for BufWriter { /// /// Seeking always writes out the internal buffer before seeking. fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.flush_buf().and_then(|_| self.get_mut().seek(pos)) + self.flush_buf()?; + self.get_mut().seek(pos) } } @@ -1862,12 +1863,13 @@ mod tests { IoSlice::new(b"a"), ]) .unwrap(), - 2, + 1, ); assert_eq!(a.get_ref(), b"\n"); assert_eq!( a.write_vectored(&[ + IoSlice::new(b"a"), IoSlice::new(&[]), IoSlice::new(b"b"), IoSlice::new(&[]), @@ -1876,7 +1878,7 @@ mod tests { IoSlice::new(b"c"), ]) .unwrap(), - 3, + 4, ); assert_eq!(a.get_ref(), b"\n"); a.flush().unwrap(); @@ -1893,17 +1895,21 @@ mod tests { 0, ); assert_eq!(a.write_vectored(&[IoSlice::new(b"a\nb"),]).unwrap(), 3); - assert_eq!(a.get_ref(), b"\nabaca\n"); + assert_eq!(a.get_ref(), b"\nabaca\nb"); } #[test] fn line_vectored_partial_and_errors() { + use crate::collections::VecDeque; + enum Call { Write { inputs: Vec<&'static [u8]>, output: io::Result }, Flush { output: io::Result<()> }, } + + #[derive(Default)] struct Writer { - calls: Vec, + calls: VecDeque, } impl Write for Writer { @@ -1912,19 +1918,23 @@ mod tests { } fn write_vectored(&mut self, buf: &[IoSlice<'_>]) -> io::Result { - match self.calls.pop().unwrap() { + match self.calls.pop_front().expect("unexpected call to write") { Call::Write { inputs, output } => { assert_eq!(inputs, buf.iter().map(|b| &**b).collect::>()); output } - _ => panic!("unexpected call to write"), + Call::Flush { .. } => panic!("unexpected call to write; expected a flush"), } } + fn is_write_vectored(&self) -> bool { + true + } + fn flush(&mut self) -> io::Result<()> { - match self.calls.pop().unwrap() { + match self.calls.pop_front().expect("Unexpected call to flush") { Call::Flush { output } => output, - _ => panic!("unexpected call to flush"), + Call::Write { .. } => panic!("unexpected call to flush; expected a write"), } } } @@ -1938,20 +1948,22 @@ mod tests { } // partial writes keep going - let mut a = LineWriter::new(Writer { calls: Vec::new() }); + let mut a = LineWriter::new(Writer::default()); a.write_vectored(&[IoSlice::new(&[]), IoSlice::new(b"abc")]).unwrap(); - a.get_mut().calls.push(Call::Flush { output: Ok(()) }); - a.get_mut().calls.push(Call::Write { inputs: vec![b"bcx\n"], output: Ok(4) }); - a.get_mut().calls.push(Call::Write { inputs: vec![b"abcx\n"], output: Ok(1) }); + + a.get_mut().calls.push_back(Call::Write { inputs: vec![b"abc"], output: Ok(1) }); + a.get_mut().calls.push_back(Call::Write { inputs: vec![b"bc"], output: Ok(2) }); + a.get_mut().calls.push_back(Call::Write { inputs: vec![b"x", b"\n"], output: Ok(2) }); + a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\n")]).unwrap(); - a.get_mut().calls.push(Call::Flush { output: Ok(()) }); + + a.get_mut().calls.push_back(Call::Flush { output: Ok(()) }); a.flush().unwrap(); // erroneous writes stop and don't write more - a.get_mut().calls.push(Call::Write { inputs: vec![b"x\n"], output: Err(err()) }); - assert_eq!(a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\na")]).unwrap(), 2); - a.get_mut().calls.push(Call::Flush { output: Ok(()) }); - a.get_mut().calls.push(Call::Write { inputs: vec![b"x\n"], output: Ok(2) }); + a.get_mut().calls.push_back(Call::Write { inputs: vec![b"x", b"\na"], output: Err(err()) }); + a.get_mut().calls.push_back(Call::Flush { output: Ok(()) }); + assert!(a.write_vectored(&[IoSlice::new(b"x"), IoSlice::new(b"\na")]).is_err()); a.flush().unwrap(); fn err() -> io::Error { @@ -1959,6 +1971,41 @@ mod tests { } } + /// Test that, in cases where vectored writing is not enabled, the + /// LineWriter uses the normal `write` call, which more-corectly handles + /// partial lines + #[test] + fn line_vectored_ignored() { + let writer = ProgrammableSink::default(); + let mut writer = LineWriter::new(writer); + + let content = [ + IoSlice::new(b"Line 1\nLine"), + IoSlice::new(b" 2\nLine 3\nL"), + IoSlice::new(b"ine 4"), + IoSlice::new(b"\nLine 5\n"), + ]; + + let count = writer.write_vectored(&content).unwrap(); + assert_eq!(count, 11); + assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); + + let count = writer.write_vectored(&content[1..]).unwrap(); + assert_eq!(count, 11); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); + + let count = writer.write_vectored(&content[2..]).unwrap(); + assert_eq!(count, 5); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); + + let count = writer.write_vectored(&content[3..]).unwrap(); + assert_eq!(count, 8); + assert_eq!( + writer.get_ref().buffer.as_slice(), + b"Line 1\nLine 2\nLine 3\nLine 4\n Line 5".as_ref() + ); + } + /// Test that, given this input: /// /// Line 1\n @@ -1972,7 +2019,7 @@ mod tests { /// /// This behavior is desirable because it prevents flushing partial lines #[test] - fn test_partial_write_buffers_line() { + fn partial_write_buffers_line() { let writer = ProgrammableSink { accept_prefix: Some(13), ..Default::default() }; let mut writer = LineWriter::new(writer); @@ -1992,7 +2039,7 @@ mod tests { /// And given that the full write of lines 1 and 2 was successful /// That data up to Line 3 is buffered #[test] - fn test_partial_line_buffered_after_line_write() { + fn partial_line_buffered_after_line_write() { let writer = ProgrammableSink::default(); let mut writer = LineWriter::new(writer); @@ -2007,7 +2054,7 @@ mod tests { /// LineBuffer's buffer (that is, without a trailing newline), that that /// line is written to the inner writer #[test] - fn test_long_line_flushed() { + fn long_line_flushed() { let writer = ProgrammableSink::default(); let mut writer = LineWriter::with_capacity(5, writer); @@ -2020,7 +2067,7 @@ mod tests { /// and no additional writes take place. This assures the property that /// `write` should make at-most-one attempt to write new data. #[test] - fn test_long_tail_not_flushed() { + fn line_long_tail_not_flushed() { let writer = ProgrammableSink::default(); let mut writer = LineWriter::with_capacity(5, writer); @@ -2036,7 +2083,7 @@ mod tests { /// Test that, if an attempt to pre-flush buffered data returns Ok(0), /// this is propagated as an error. #[test] - fn test_line_buffer_write0_error() { + fn line_buffer_write0_error() { let writer = ProgrammableSink { // Accept one write, then return Ok(0) on subsequent ones max_writes: Some(1), @@ -2060,7 +2107,7 @@ mod tests { /// Test that, if a write returns Ok(0) after a successful pre-flush, this /// is propogated as Ok(0) #[test] - fn test_line_buffer_write0_normal() { + fn line_buffer_write0_normal() { let writer = ProgrammableSink { // Accept two writes, then return Ok(0) on subsequent ones max_writes: Some(2), @@ -2078,4 +2125,39 @@ mod tests { assert_eq!(writer.write(b" Line End\n").unwrap(), 0); assert_eq!(&writer.get_ref().buffer, b"Line 1\nPartial"); } + + /// LineWriter has a custom `write_all`; make sure it works correctly + #[test] + fn line_write_all() { + let writer = ProgrammableSink { + // Only write 5 bytes at a time + accept_prefix: Some(5), + ..Default::default() + }; + let mut writer = LineWriter::new(writer); + + writer.write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial").unwrap(); + assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\nLine 4\n"); + writer.write_all(b" Line 5\n").unwrap(); + assert_eq!( + writer.get_ref().buffer.as_slice(), + b"Line 1\nLine 2\nLine 3\nLine 4\nPartial Line 5\n".as_ref(), + ); + } + + #[test] + fn line_write_all_error() { + let writer = ProgrammableSink { + // Only accept up to 3 writes of up to 5 bytes each + accept_prefix: Some(5), + max_writes: Some(3), + ..Default::default() + }; + + let mut writer = LineWriter::new(writer); + let res = writer.write_all(b"Line 1\nLine 2\nLine 3\nLine 4\nPartial"); + assert!(res.is_err()); + // An error from write_all leaves everything in an indeterminate state, + // so there's nothing else to test here + } } From e022d3452de74b5596810f0aa26193e262de725b Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 00:44:16 -0400 Subject: [PATCH 11/62] Fixed typo in test --- src/libstd/io/buffered.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index ced84b777ce77..abfc2fc37c4f9 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -2002,7 +2002,7 @@ mod tests { assert_eq!(count, 8); assert_eq!( writer.get_ref().buffer.as_slice(), - b"Line 1\nLine 2\nLine 3\nLine 4\n Line 5".as_ref() + b"Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n".as_ref() ); } From e4328ae54573aa42051246b675bf5280ea58eddf Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:01:30 -0400 Subject: [PATCH 12/62] Code review updates: all minor style fixes - Renamed write_to_buffer to write_to_buf, for consistency - Fixed references to flush_buf - Optimized `write` to use one less `memchr` call --- src/libstd/io/buffered.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index abfc2fc37c4f9..7b7c8c6e3177a 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -565,7 +565,7 @@ impl BufWriter { /// data. Writes as much as possible without exceeding capacity. Returns /// the number of bytes written. #[inline] - fn write_to_buffer(&mut self, buf: &[u8]) -> usize { + fn write_to_buf(&mut self, buf: &[u8]) -> usize { let available = self.buf.capacity() - self.buf.len(); let amt_to_buffer = available.min(buf.len()); self.buf.extend_from_slice(&buf[..amt_to_buffer]); @@ -689,7 +689,7 @@ impl Write for BufWriter { self.panicked = false; r } else { - Ok(self.write_to_buffer(buf)) + Ok(self.write_to_buf(buf)) } } @@ -703,7 +703,7 @@ impl Write for BufWriter { self.panicked = false; r } else { - self.write_to_buffer(buf); + self.write_to_buf(buf); Ok(()) } } @@ -861,7 +861,7 @@ impl fmt::Display for IntoInnerError { /// Private helper struct for implementing the line-buffered writing logic. /// This shim temporarily wraps a BufWriter, and uses its internals to /// implement a line-buffered writer (specifically by using the internal -/// methods like write_to_buffer and flush_buffer). In this way, a more +/// methods like write_to_buf and flush_buf). In this way, a more /// efficient abstraction can be created than one that only had access to /// `write` and `flush`, without needlessly duplicating a lot of the /// implementation details of BufWriter. This also allows existing @@ -925,7 +925,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { } // Otherwise, arrange for the lines to be written directly to the // inner writer. - Some(newline_idx) => newline_idx, + Some(newline_idx) => newline_idx + 1, }; // Flush existing content to prepare for our write @@ -933,7 +933,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // This is what we're going to try to write directly to the inner // writer. The rest will be buffered, if nothing goes wrong. - let lines = &buf[..newline_idx + 1]; + let lines = &buf[..newline_idx]; // Write `lines` directly to the inner writer. In keeping with the // `write` convention, make at most one attempt to add new (unbuffered) @@ -953,11 +953,10 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // the rest as possible). If there were any unwritten newlines, we // only buffer out to the last unwritten newline; this helps prevent // flushing partial lines on subsequent calls to LineWriterShim::write. - let tail = &buf[flushed..]; - let buffered = match memchr::memrchr(b'\n', tail) { - None => self.buffer.write_to_buffer(tail), - Some(i) => self.buffer.write_to_buffer(&tail[..i + 1]), - }; + let tail = + if flushed < newline_idx { &buf[flushed..newline_idx] } else { &buf[newline_idx..] }; + + let buffered = self.buffer.write_to_buf(tail); Ok(flushed + buffered) } @@ -1056,7 +1055,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // Now that the write has succeeded, buffer the rest (or as much of the // rest as possible) let buffered: usize = - tail.iter().map(|buf| self.buffer.write_to_buffer(buf)).take_while(|&n| n > 0).sum(); + tail.iter().map(|buf| self.buffer.write_to_buf(buf)).take_while(|&n| n > 0).sum(); Ok(flushed + buffered) } @@ -1076,6 +1075,9 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + // The default `write_all` calls `write` in a loop; we can do better + // by simply calling self.inner().write_all directly. This avoids + // round trips to `self.buffer` in the event of partial writes. let newline_idx = match memchr::memrchr(b'\n', buf) { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write From f7650fe3f955863e810ef7816edbe78f88393d87 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:07:34 -0400 Subject: [PATCH 13/62] Add comment --- src/libstd/io/buffered.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 7b7c8c6e3177a..b9839c318b75a 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1358,6 +1358,9 @@ mod tests { lengths: Vec, } + // FIXME: rustfmt and tidy disagree about the correct formatting of this + // function. This leads to issues for users with editors configured to + // rustfmt-on-save. impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { if self.lengths.is_empty() { From 7a6a12bdf42804e2e2ef6437bed6a45a1ede51b2 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:09:55 -0400 Subject: [PATCH 14/62] Tidy fixes --- src/libstd/io/buffered.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index b9839c318b75a..560c8f2369846 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1363,11 +1363,7 @@ mod tests { // rustfmt-on-save. impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { - Ok(0) - } else { - Ok(self.lengths.remove(0)) - } + if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } } } @@ -1795,7 +1791,7 @@ mod tests { match self.max_writes { Some(0) if self.error_after_max_writes => { - return Err(io::Error::new(io::ErrorKind::Other, "test - max_writes")) + return Err(io::Error::new(io::ErrorKind::Other, "test - max_writes")); } Some(0) => return Ok(0), Some(ref mut count) => *count -= 1, From 338a2c02e4b9cc5b1fae698ab019724340b26967 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:28:31 -0400 Subject: [PATCH 15/62] Reimplement flush_buf with a Guard. Longer, but cleaner. --- src/libstd/io/buffered.rs | 67 +++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 560c8f2369846..b7e848b04d721 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -448,6 +448,9 @@ impl Seek for BufReader { /// [`flush`]: #method.flush #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { + // FIXME: Can this just be W, instead of Option? I don't see any code + // paths that lead to this being None, or that ever check if it IS none, + // even in drop implementations. inner: Option, // FIXME: Replace this with a VecDeque. Because VecDeque is a Ring buffer, // this would enable BufWriter to operate without any interior copies. @@ -533,32 +536,62 @@ impl BufWriter { /// `write`), any 0-length writes from `inner` must be reported as i/o /// errors from this method. fn flush_buf(&mut self) -> io::Result<()> { - let mut written = 0; - let len = self.buf.len(); - let mut ret = Ok(()); - while written < len { + /// Helper struct to ensure the buffer is updated after all the writes + /// are complete + struct BufGuard<'a> { + buffer: &'a mut Vec, + written: usize, + } + + impl<'a> BufGuard<'a> { + fn new(buffer: &'a mut Vec) -> Self { + Self { buffer, written: 0 } + } + + /// The unwritten part of the buffer + fn remaining(&self) -> &[u8] { + &self.buffer[self.written..] + } + + /// Flag some bytes as removed from the front of the buffer + fn consume(&mut self, amt: usize) { + self.written += amt; + } + + /// true if all of the bytes have been written + fn done(&self) -> bool { + self.written >= self.buffer.len() + } + } + + impl Drop for BufGuard<'_> { + fn drop(&mut self) { + if self.written > 0 { + self.buffer.drain(..self.written); + } + } + } + + let mut guard = BufGuard::new(&mut self.buf); + let inner = self.inner.as_mut().unwrap(); + while !guard.done() { self.panicked = true; - let r = self.inner.as_mut().unwrap().write(&self.buf[written..]); + let r = inner.write(guard.remaining()); self.panicked = false; match r { Ok(0) => { - ret = - Err(Error::new(ErrorKind::WriteZero, "failed to write the buffered data")); - break; + return Err(Error::new( + ErrorKind::WriteZero, + "failed to write the buffered data", + )) } - Ok(n) => written += n, + Ok(n) => guard.consume(n), Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => { - ret = Err(e); - break; - } + Err(e) => return Err(e), } } - if written > 0 { - self.buf.drain(..written); - } - ret + Ok(()) } /// Buffer some data without flushing it, regardless of the size of the From c869638cba8e9ab8392016b11675706c4e514b6e Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:30:40 -0400 Subject: [PATCH 16/62] Added comment about BufWrite::write_all --- src/libstd/io/buffered.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index b7e848b04d721..f9bde7897e997 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -727,6 +727,10 @@ impl Write for BufWriter { } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + // Normally, `write_all` just calls `write` in a loop. We can do better + // by calling `self.get_mut().write_all()` directly, which avoids + // round trips through the buffer in the event of a series of partial + // writes. if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } From 61f591e1738845911048ff43d956bb9d39987910 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:36:40 -0400 Subject: [PATCH 17/62] Improved line_vectored_ignored. Added stylistic semicolon. --- src/libstd/io/buffered.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index f9bde7897e997..02615bffe3bd1 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -584,7 +584,7 @@ impl BufWriter { return Err(Error::new( ErrorKind::WriteZero, "failed to write the buffered data", - )) + )); } Ok(n) => guard.consume(n), Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} @@ -2018,8 +2018,11 @@ mod tests { let mut writer = LineWriter::new(writer); let content = [ + IoSlice::new(&[]), IoSlice::new(b"Line 1\nLine"), IoSlice::new(b" 2\nLine 3\nL"), + IoSlice::new(&[]), + IoSlice::new(&[]), IoSlice::new(b"ine 4"), IoSlice::new(b"\nLine 5\n"), ]; @@ -2028,15 +2031,15 @@ mod tests { assert_eq!(count, 11); assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); - let count = writer.write_vectored(&content[1..]).unwrap(); + let count = writer.write_vectored(&content[2..]).unwrap(); assert_eq!(count, 11); assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); - let count = writer.write_vectored(&content[2..]).unwrap(); + let count = writer.write_vectored(&content[5..]).unwrap(); assert_eq!(count, 5); assert_eq!(&writer.get_ref().buffer, b"Line 1\nLine 2\nLine 3\n"); - let count = writer.write_vectored(&content[3..]).unwrap(); + let count = writer.write_vectored(&content[6..]).unwrap(); assert_eq!(count, 8); assert_eq!( writer.get_ref().buffer.as_slice(), From 2d22c7741816aa391f619f31b14b1ca01cc31b61 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 01:43:10 -0400 Subject: [PATCH 18/62] Fixed bug in write_vectored & empty buffers --- src/libstd/io/buffered.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 02615bffe3bd1..5189b39aaae68 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1091,8 +1091,12 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // Now that the write has succeeded, buffer the rest (or as much of the // rest as possible) - let buffered: usize = - tail.iter().map(|buf| self.buffer.write_to_buf(buf)).take_while(|&n| n > 0).sum(); + let buffered: usize = tail + .iter() + .filter(|buf| !buf.is_empty()) + .map(|buf| self.buffer.write_to_buf(buf)) + .take_while(|&n| n > 0) + .sum(); Ok(flushed + buffered) } From 2c23b9066f761e07f1c4db6ca8d8f0bf48e4e296 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 11:56:02 -0400 Subject: [PATCH 19/62] Comment updates --- src/libstd/io/buffered.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 5189b39aaae68..51098f6ff6af5 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -730,7 +730,7 @@ impl Write for BufWriter { // Normally, `write_all` just calls `write` in a loop. We can do better // by calling `self.get_mut().write_all()` directly, which avoids // round trips through the buffer in the event of a series of partial - // writes. + // writes in some circumstances. if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } @@ -1116,9 +1116,6 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// writer, it will also flush the existing buffer if it contains any /// newlines, even if the incoming data does not contain any newlines. fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - // The default `write_all` calls `write` in a loop; we can do better - // by simply calling self.inner().write_all directly. This avoids - // round trips to `self.buffer` in the event of partial writes. let newline_idx = match memchr::memrchr(b'\n', buf) { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write From e999ca5ac3a21183fdc8cc99350752d0f1ef0bdd Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 12:00:12 -0400 Subject: [PATCH 20/62] Remove inline from write_to_buf --- src/libstd/io/buffered.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 51098f6ff6af5..a66fa6ea36a28 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -597,7 +597,6 @@ impl BufWriter { /// Buffer some data without flushing it, regardless of the size of the /// data. Writes as much as possible without exceeding capacity. Returns /// the number of bytes written. - #[inline] fn write_to_buf(&mut self, buf: &[u8]) -> usize { let available = self.buf.capacity() - self.buf.len(); let amt_to_buffer = available.min(buf.len()); From 70ba320052669c06e2285d16b4a36af83f9c83da Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 12:40:05 -0400 Subject: [PATCH 21/62] More minor changes - Fixed test after write_vectored bugfix - Some comments --- src/libstd/io/buffered.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index a66fa6ea36a28..c599bd8b55127 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -715,6 +715,7 @@ impl Write for BufWriter { if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } + // FIXME: Why no len > capacity? Why not buffer len == capacity? if buf.len() >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write(buf); @@ -733,6 +734,7 @@ impl Write for BufWriter { if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } + // FIXME: Why no len > capacity? Why not buffer len == capacity? if buf.len() >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write_all(buf); @@ -749,6 +751,7 @@ impl Write for BufWriter { if self.buf.len() + total_len > self.buf.capacity() { self.flush_buf()?; } + // FIXME: Why no len > capacity? Why not buffer len == capacity? if total_len >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write_vectored(bufs); @@ -1901,13 +1904,12 @@ mod tests { IoSlice::new(b"a"), ]) .unwrap(), - 1, + 2, ); assert_eq!(a.get_ref(), b"\n"); assert_eq!( a.write_vectored(&[ - IoSlice::new(b"a"), IoSlice::new(&[]), IoSlice::new(b"b"), IoSlice::new(&[]), @@ -1916,7 +1918,7 @@ mod tests { IoSlice::new(b"c"), ]) .unwrap(), - 4, + 3, ); assert_eq!(a.get_ref(), b"\n"); a.flush().unwrap(); From 5b1a40c18168a8049eb9f3af3178ec1846e64730 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 12:58:49 -0400 Subject: [PATCH 22/62] BufWriter::write* methods now use fewer runtime checks --- src/libstd/io/buffered.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index c599bd8b55127..1998cdde8d08e 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -722,7 +722,8 @@ impl Write for BufWriter { self.panicked = false; r } else { - Ok(self.write_to_buf(buf)) + self.buf.extend_from_slice(buf); + Ok(buf.len()) } } @@ -741,7 +742,7 @@ impl Write for BufWriter { self.panicked = false; r } else { - self.write_to_buf(buf); + self.buf.extend_from_slice(buf); Ok(()) } } @@ -758,7 +759,8 @@ impl Write for BufWriter { self.panicked = false; r } else { - self.buf.write_vectored(bufs) + bufs.iter().for_each(|b| self.buf.extend_from_slice(b)); + Ok(total_len) } } @@ -1403,7 +1405,11 @@ mod tests { // rustfmt-on-save. impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } + if self.lengths.is_empty() { + Ok(0) + } else { + Ok(self.lengths.remove(0)) + } } } From 8df5ae0fffc9de2884e0c916bdcd74cb69949b1c Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 14:36:03 -0400 Subject: [PATCH 23/62] x.py fix AGAIN --- src/libstd/io/buffered.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 1998cdde8d08e..61ad5d0c274ff 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1405,11 +1405,7 @@ mod tests { // rustfmt-on-save. impl Read for ShortReader { fn read(&mut self, _: &mut [u8]) -> io::Result { - if self.lengths.is_empty() { - Ok(0) - } else { - Ok(self.lengths.remove(0)) - } + if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) } } } From 60ab99f9bdcb33ea025aca6a94e34f2ae5b5b75e Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 15:57:20 -0400 Subject: [PATCH 24/62] Fixed corner case related to partial-line buffering - Fixed partial-line buffering issue - Added tests Thanks @the8472 for catching! --- src/libstd/io/buffered.rs | 93 +++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 61ad5d0c274ff..d094f19b5a5ff 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -994,8 +994,26 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // the rest as possible). If there were any unwritten newlines, we // only buffer out to the last unwritten newline; this helps prevent // flushing partial lines on subsequent calls to LineWriterShim::write. - let tail = - if flushed < newline_idx { &buf[flushed..newline_idx] } else { &buf[newline_idx..] }; + + // Handle the cases in order of most-common to least-common, under + // the presumption that most writes succeed in totality, and that most + // writes are smaller than the buffer. + // - Is this a partial line (ie, no newlines left in the unwritten tail) + // - If not, does the data out to the last unwritten newline fit in + // the buffer? + // - If not, scan for the last newline that *does* fit in the buffer + let tail = if flushed >= newline_idx { + &buf[flushed..] + } else if newline_idx - flushed <= self.buffer.capacity() { + &buf[flushed..newline_idx] + } else { + let scan_area = &buf[flushed..]; + let scan_area = &scan_area[..self.buffer.capacity()]; + match memchr::memrchr(b'\n', scan_area) { + Some(newline_idx) => &scan_area[..newline_idx + 1], + None => scan_area, + } + }; let buffered = self.buffer.write_to_buf(tail); Ok(flushed + buffered) @@ -1809,8 +1827,11 @@ mod tests { // Flush sets this flag pub flushed: bool, - // If true, writes & flushes will always be an error - pub always_error: bool, + // If true, writes will always be an error + pub always_write_error: bool, + + // If true, flushes will always be an error + pub always_flush_error: bool, // If set, only up to this number of bytes will be written in a single // call to `write` @@ -1827,8 +1848,8 @@ mod tests { impl Write for ProgrammableSink { fn write(&mut self, data: &[u8]) -> io::Result { - if self.always_error { - return Err(io::Error::new(io::ErrorKind::Other, "test - write always_error")); + if self.always_write_error { + return Err(io::Error::new(io::ErrorKind::Other, "test - always_write_error")); } match self.max_writes { @@ -1852,8 +1873,8 @@ mod tests { } fn flush(&mut self) -> io::Result<()> { - if self.always_error { - Err(io::Error::new(io::ErrorKind::Other, "test - flush always_error")) + if self.always_flush_error { + Err(io::Error::new(io::ErrorKind::Other, "test - always_flush_error")) } else { self.flushed = true; Ok(()) @@ -2205,4 +2226,60 @@ mod tests { // An error from write_all leaves everything in an indeterminate state, // so there's nothing else to test here } + + /// Under certain circumstances, the old implementation of LineWriter + /// would try to buffer "to the last newline" but be forced to buffer + /// less than that, leading to inappropriate partial line writes. + /// Regression test for that issue. + #[test] + fn partial_multiline_buffering() { + let writer = ProgrammableSink { + // Write only up to 5 bytes at a time + accept_prefix: Some(5), + ..Default::default() + }; + + let mut writer = LineWriter::with_capacity(10, writer); + + let content = b"AAAAABBBBB\nCCCCDDDDDD\nEEE"; + + // When content is written, LineWriter will try to write blocks A, B, + // C, and D. Only block A will succeed. Under the old behavior, LineWriter + // would then try to buffer B, C and D, but because its capacity is 10, + // it will only be able to buffer B and C. We don't want it to buffer + // partial lines if it can avoid it, so the correct behavior is to + // only buffer block B (with its newline). + assert_eq!(writer.write(content).unwrap(), 11); + assert_eq!(writer.get_ref().buffer, *b"AAAAA"); + + writer.flush().unwrap(); + assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB\n"); + } + + /// Same as test_partial_multiline_buffering, but in the event NO full lines + /// fit in the buffer, just buffer as much as possible + #[test] + fn partial_multiline_buffering_without_full_line() { + let writer = ProgrammableSink { + // Write only up to 5 bytes at a time + accept_prefix: Some(5), + ..Default::default() + }; + + let mut writer = LineWriter::with_capacity(5, writer); + + let content = b"AAAAABBBBBBBBBB\nCCCCC\nDDDDD"; + + // When content is written, LineWriter will try to write blocks A, B, + // and C. Only block A will succeed. Under the old behavior, LineWriter + // would then try to buffer B and C, but because its capacity is 5, + // it will only be able to buffer part of B. Because it's not possible + // for it to buffer any complete lines, it should buffer as much of B as + // possible + assert_eq!(writer.write(content).unwrap(), 10); + assert_eq!(writer.get_ref().buffer, *b"AAAAA"); + + writer.flush().unwrap(); + assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB"); + } } From 38017a31e7728c359487817008c913a8e461a857 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 16:17:42 -0400 Subject: [PATCH 25/62] Update comments with relevant issue numbers --- src/libstd/io/buffered.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index d094f19b5a5ff..1048c9d078d86 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -450,7 +450,7 @@ impl Seek for BufReader { pub struct BufWriter { // FIXME: Can this just be W, instead of Option? I don't see any code // paths that lead to this being None, or that ever check if it IS none, - // even in drop implementations. + // even in drop implementations. #72925. inner: Option, // FIXME: Replace this with a VecDeque. Because VecDeque is a Ring buffer, // this would enable BufWriter to operate without any interior copies. @@ -715,7 +715,7 @@ impl Write for BufWriter { if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } - // FIXME: Why no len > capacity? Why not buffer len == capacity? + // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 if buf.len() >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write(buf); @@ -735,7 +735,7 @@ impl Write for BufWriter { if self.buf.len() + buf.len() > self.buf.capacity() { self.flush_buf()?; } - // FIXME: Why no len > capacity? Why not buffer len == capacity? + // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 if buf.len() >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write_all(buf); @@ -752,7 +752,7 @@ impl Write for BufWriter { if self.buf.len() + total_len > self.buf.capacity() { self.flush_buf()?; } - // FIXME: Why no len > capacity? Why not buffer len == capacity? + // FIXME: Why no len > capacity? Why not buffer len == capacity? #72919 if total_len >= self.buf.capacity() { self.panicked = true; let r = self.get_mut().write_vectored(bufs); From 59710fb716d2810c6eada13275039274b6279065 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 2 Jun 2020 18:16:02 -0400 Subject: [PATCH 26/62] Clarified comment in `partial_multiline_buffering` test --- src/libstd/io/buffered.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 1048c9d078d86..c8c9ee4a4b47c 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -2246,9 +2246,9 @@ mod tests { // When content is written, LineWriter will try to write blocks A, B, // C, and D. Only block A will succeed. Under the old behavior, LineWriter // would then try to buffer B, C and D, but because its capacity is 10, - // it will only be able to buffer B and C. We don't want it to buffer - // partial lines if it can avoid it, so the correct behavior is to - // only buffer block B (with its newline). + // it will only be able to buffer B and C. We don't want to buffer + // partial lines concurrent with whole lines, so the correct behavior + // is to buffer only block B (out to the newline) assert_eq!(writer.write(content).unwrap(), 11); assert_eq!(writer.get_ref().buffer, *b"AAAAA"); From 2e21af2859854668448ac96f335986a19011e571 Mon Sep 17 00:00:00 2001 From: Alexis Bourget Date: Mon, 29 Jun 2020 19:18:40 +0200 Subject: [PATCH 27/62] Document the union keyword --- src/libstd/keyword_docs.rs | 67 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/src/libstd/keyword_docs.rs b/src/libstd/keyword_docs.rs index d972cf6db18cf..cbfaac8c4b317 100644 --- a/src/libstd/keyword_docs.rs +++ b/src/libstd/keyword_docs.rs @@ -1624,8 +1624,71 @@ mod dyn_keyword {} // /// The [Rust equivalent of a C-style union][union]. /// -/// The documentation for this keyword is [not yet complete]. Pull requests welcome! +/// A `union` looks like a [`struct`] in terms of declaration, but all of its +/// fields exist simultaneously, superimposed over one another. For instance, +/// if we wanted some bits in memory that we sometimes interpret as a `u32` and +/// sometimes as an `f32`, we would write: +/// +/// ```rust +/// union IntOrFloat { +/// i: u32, +/// f: f32, +/// } +/// +/// let mut u = IntOrFloat { f: 1.0 }; +/// // Reading the fields of an union is always unsafe +/// assert_eq!(unsafe { u.i }, 1065353216); +/// // Updating through any of the field will modify all of them +/// u.i = 1073741824; +/// assert_eq!(unsafe { u.f }, 2.0); +/// ``` +/// +/// # Matching on unions +/// +/// It is possible to use pattern matching on `union`s. A single field name must +/// be used and it must match the name of one of the `union`'s field. +/// +/// ```rust +/// union IntOrFloat { +/// i: u32, +/// f: f32, +/// } +/// +/// let u = IntOrFloat { f: 1.0 }; +/// +/// unsafe { +/// match u { +/// IntOrFloat { i: 10 } => println!("Found exactly ten !"), +/// // The field name is used to deduce the type +/// IntOrFloat { f } => println!("Found f = {} !", f), +/// } +/// } +/// ``` +/// +/// # References to union fields +/// +/// All fields in a union are all at the same place in memory which means +/// borrowing one borrows all of them, for the same duration: +/// +/// ```rust,compile_fail,E0502 +/// union IntOrFloat { +/// i: u32, +/// f: f32, +/// } /// +/// let mut u = IntOrFloat { f: 1.0 }; +/// +/// let f = unsafe { &u.f }; +/// // This will not compile because the field has already been borrowed, even +/// // if only immutably +/// let i = unsafe { &mut u.i }; +/// +/// *i = 10; +/// println!("f = {} and i = {}", f, i); +/// ``` +/// +/// See the [Reference][union] for more informations on `union`s. +/// +/// [`struct`]: keyword.struct.html /// [union]: ../reference/items/unions.html -/// [not yet complete]: https://github.com/rust-lang/rust/issues/34601 mod union_keyword {} From 614f7738ba65ac37f2dc61afa8c68fc3768fdffc Mon Sep 17 00:00:00 2001 From: Poliorcetics Date: Tue, 30 Jun 2020 11:14:45 +0200 Subject: [PATCH 28/62] Clarify some parts by applying the suggestions from review Co-authored-by: Josh Triplett --- src/libstd/keyword_docs.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libstd/keyword_docs.rs b/src/libstd/keyword_docs.rs index cbfaac8c4b317..51b21ce572a3f 100644 --- a/src/libstd/keyword_docs.rs +++ b/src/libstd/keyword_docs.rs @@ -1625,9 +1625,9 @@ mod dyn_keyword {} /// The [Rust equivalent of a C-style union][union]. /// /// A `union` looks like a [`struct`] in terms of declaration, but all of its -/// fields exist simultaneously, superimposed over one another. For instance, +/// fields exist in the same memory, superimposed over one another. For instance, /// if we wanted some bits in memory that we sometimes interpret as a `u32` and -/// sometimes as an `f32`, we would write: +/// sometimes as an `f32`, we could write: /// /// ```rust /// union IntOrFloat { @@ -1647,6 +1647,7 @@ mod dyn_keyword {} /// /// It is possible to use pattern matching on `union`s. A single field name must /// be used and it must match the name of one of the `union`'s field. +/// Like reading from a `union`, pattern matching on a `union` requires `unsafe`. /// /// ```rust /// union IntOrFloat { @@ -1658,8 +1659,8 @@ mod dyn_keyword {} /// /// unsafe { /// match u { -/// IntOrFloat { i: 10 } => println!("Found exactly ten !"), -/// // The field name is used to deduce the type +/// IntOrFloat { i: 10 } => println!("Found exactly ten!"), +/// // Matching the field `f` provides an `f32`. /// IntOrFloat { f } => println!("Found f = {} !", f), /// } /// } @@ -1667,8 +1668,8 @@ mod dyn_keyword {} /// /// # References to union fields /// -/// All fields in a union are all at the same place in memory which means -/// borrowing one borrows all of them, for the same duration: +/// All fields in a `union` are all at the same place in memory which means +/// borrowing one borrows the entire `union`, for the same lifetime: /// /// ```rust,compile_fail,E0502 /// union IntOrFloat { From 310c97b6ba7e6b8d4e3e7f337db0cff97f45f5c0 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 4 Jul 2020 14:46:04 -0700 Subject: [PATCH 29/62] Fix caching issue when building tools. --- src/bootstrap/bin/rustc.rs | 4 ++++ src/bootstrap/builder.rs | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/bootstrap/bin/rustc.rs b/src/bootstrap/bin/rustc.rs index 3072a4a1ae7c0..fd36cd9bd8beb 100644 --- a/src/bootstrap/bin/rustc.rs +++ b/src/bootstrap/bin/rustc.rs @@ -76,6 +76,10 @@ fn main() { cmd.env("RUST_BACKTRACE", "1"); } + if let Ok(lint_flags) = env::var("RUSTC_LINT_FLAGS") { + cmd.args(lint_flags.split_whitespace()); + } + if target.is_some() { // The stage0 compiler has a special sysroot distinct from what we // actually downloaded, so we just always pass the `--sysroot` option, diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 3cbecbbaa06cb..557fb1aa550a5 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -1130,22 +1130,32 @@ impl<'a> Builder<'a> { cargo.env("RUSTC_VERBOSE", self.verbosity.to_string()); if source_type == SourceType::InTree { + let mut lint_flags = Vec::new(); // When extending this list, add the new lints to the RUSTFLAGS of the // build_bootstrap function of src/bootstrap/bootstrap.py as well as // some code doesn't go through this `rustc` wrapper. - rustflags.arg("-Wrust_2018_idioms"); - rustflags.arg("-Wunused_lifetimes"); + lint_flags.push("-Wrust_2018_idioms"); + lint_flags.push("-Wunused_lifetimes"); if self.config.deny_warnings { - rustflags.arg("-Dwarnings"); + lint_flags.push("-Dwarnings"); } // FIXME(#58633) hide "unused attribute" errors in incremental // builds of the standard library, as the underlying checks are // not yet properly integrated with incremental recompilation. if mode == Mode::Std && compiler.stage == 0 && self.config.incremental { - rustflags.arg("-Aunused-attributes"); + lint_flags.push("-Aunused-attributes"); } + // This does not use RUSTFLAGS due to caching issues with Cargo. + // Clippy is treated as an "in tree" tool, but shares the same + // cache as other "submodule" tools. With these options set in + // RUSTFLAGS, that causes *every* shared dependency to be rebuilt. + // By injecting this into the rustc wrapper, this circumvents + // Cargo's fingerprint detection. This is fine because lint flags + // are always ignored in dependencies. Eventually this should be + // fixed via better support from Cargo. + cargo.env("RUSTC_LINT_FLAGS", lint_flags.join(" ")); } if let Mode::Rustc | Mode::Codegen = mode { From c478b5473d6623622d318d058477f5f09e2eeb52 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 3 Jul 2020 11:58:05 +0200 Subject: [PATCH 30/62] add as_ptr method to raw slices --- src/libcore/ptr/const_ptr.rs | 21 +++++++++++++++++++++ src/libcore/ptr/mut_ptr.rs | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/libcore/ptr/const_ptr.rs b/src/libcore/ptr/const_ptr.rs index d1d7a71523822..f926befb4b323 100644 --- a/src/libcore/ptr/const_ptr.rs +++ b/src/libcore/ptr/const_ptr.rs @@ -826,6 +826,27 @@ impl *const [T] { // Only `std` can make this guarantee. unsafe { Repr { rust: self }.raw }.len } + + /// Returns a raw pointer to the slice's buffer. + /// + /// This is equivalent to casting `self` to `*const T`, but more type-safe. + /// + /// # Examples + /// + /// ```rust + /// #![feature(slice_ptr_ptr)] + /// + /// use std::ptr; + /// + /// let slice: *const [i8] = ptr::slice_from_raw_parts(ptr::null(), 3); + /// assert_eq!(slice.as_ptr(), 0 as *const i8); + /// ``` + #[inline] + #[unstable(feature = "slice_ptr_ptr", issue = "none")] + #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + pub const fn as_ptr(self) -> *const T { + self as *const T + } } // Equality for pointers diff --git a/src/libcore/ptr/mut_ptr.rs b/src/libcore/ptr/mut_ptr.rs index 7d4b6339b511f..7fc4805c9211b 100644 --- a/src/libcore/ptr/mut_ptr.rs +++ b/src/libcore/ptr/mut_ptr.rs @@ -1028,6 +1028,27 @@ impl *mut [T] { // Only `std` can make this guarantee. unsafe { Repr { rust_mut: self }.raw }.len } + + /// Returns a raw pointer to the slice's buffer. + /// + /// This is equivalent to casting `self` to `*mut T`, but more type-safe. + /// + /// # Examples + /// + /// ```rust + /// #![feature(slice_ptr_ptr)] + /// + /// use std::ptr; + /// + /// let slice: *mut [i8] = ptr::slice_from_raw_parts_mut(ptr::null_mut(), 3); + /// assert_eq!(slice.as_ptr(), 0 as *mut i8); + /// ``` + #[inline] + #[unstable(feature = "slice_ptr_ptr", issue = "none")] + #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + pub const fn as_ptr(self) -> *mut T { + self as *mut T + } } // Equality for pointers From 3b1d5e6d792fb47c9a95c4ea210ce88174f18b13 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 3 Jul 2020 12:04:36 +0200 Subject: [PATCH 31/62] call the mut version as_mut_ptr and also add an as_ptr-like method to NonNull slices --- src/libcore/lib.rs | 1 + src/libcore/ptr/mut_ptr.rs | 4 ++-- src/libcore/ptr/non_null.rs | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs index 50c56434fa9a1..66a1bea0c44b5 100644 --- a/src/libcore/lib.rs +++ b/src/libcore/lib.rs @@ -93,6 +93,7 @@ #![feature(const_slice_ptr_len)] #![feature(const_type_name)] #![feature(const_likely)] +#![feature(const_slice_ptr_ptr)] #![feature(custom_inner_attributes)] #![feature(decl_macro)] #![feature(doc_cfg)] diff --git a/src/libcore/ptr/mut_ptr.rs b/src/libcore/ptr/mut_ptr.rs index 7fc4805c9211b..70a81f85da8f4 100644 --- a/src/libcore/ptr/mut_ptr.rs +++ b/src/libcore/ptr/mut_ptr.rs @@ -1041,12 +1041,12 @@ impl *mut [T] { /// use std::ptr; /// /// let slice: *mut [i8] = ptr::slice_from_raw_parts_mut(ptr::null_mut(), 3); - /// assert_eq!(slice.as_ptr(), 0 as *mut i8); + /// assert_eq!(slice.as_mut_ptr(), 0 as *mut i8); /// ``` #[inline] #[unstable(feature = "slice_ptr_ptr", issue = "none")] #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] - pub const fn as_ptr(self) -> *mut T { + pub const fn as_mut_ptr(self) -> *mut T { self as *mut T } } diff --git a/src/libcore/ptr/non_null.rs b/src/libcore/ptr/non_null.rs index c2d31bfb6a4ee..3461d224c46b9 100644 --- a/src/libcore/ptr/non_null.rs +++ b/src/libcore/ptr/non_null.rs @@ -204,6 +204,26 @@ impl NonNull<[T]> { pub const fn len(self) -> usize { self.as_ptr().len() } + + /// Returns a non-null pointer to the slice's buffer. + /// + /// # Examples + /// + /// ```rust + /// #![feature(slice_ptr_ptr, nonnull_slice_from_raw_parts)] + /// + /// use std::ptr::NonNull; + /// + /// let slice: NonNull<[i8]> = NonNull::slice_from_raw_parts(NonNull::dangling(), 3); + /// assert_eq!(slice.as_non_null_ptr(), NonNull::new(1 as *mut i8).unwrap()); + /// ``` + #[inline] + #[unstable(feature = "slice_ptr_ptr", issue = "none")] + #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + pub const fn as_non_null_ptr(self) -> NonNull { + // SAFETY: We know `self` is non-null. + unsafe { NonNull::new_unchecked(self.as_ptr().as_mut_ptr()) } + } } #[stable(feature = "nonnull", since = "1.25.0")] From 90580c7b0e51050c892613664db79493df7a2af5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 5 Jul 2020 18:02:00 +0200 Subject: [PATCH 32/62] make unchecked slice indexing helper methods use raw pointers --- src/libcore/slice/mod.rs | 111 ++++++++++++++-------------- src/libcore/str/mod.rs | 152 +++++++++++++++++++-------------------- 2 files changed, 134 insertions(+), 129 deletions(-) diff --git a/src/libcore/slice/mod.rs b/src/libcore/slice/mod.rs index e7a2d7adedea0..88deeac00ff14 100644 --- a/src/libcore/slice/mod.rs +++ b/src/libcore/slice/mod.rs @@ -310,8 +310,10 @@ impl [T] { where I: SliceIndex, { - // SAFETY: the caller must uphold the safety requirements for `get_unchecked`. - unsafe { index.get_unchecked(self) } + // SAFETY: the caller must uphold most of the safety requirements for `get_unchecked`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &*index.get_unchecked(self) } } /// Returns a mutable reference to an element or subslice, without doing @@ -342,8 +344,10 @@ impl [T] { where I: SliceIndex, { - // SAFETY: the caller must uphold the safety requirements for `get_unchecked_mut`. - unsafe { index.get_unchecked_mut(self) } + // SAFETY: the caller must uphold the safety requirements for `get_unchecked_mut`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &mut *index.get_unchecked_mut(self) } } /// Returns a raw pointer to the slice's buffer. @@ -2910,6 +2914,9 @@ mod private_slice_index { } /// A helper trait used for indexing operations. +/// +/// Implementations of this trait have to promise that if the argument +/// to `get_(mut_)unchecked` is a safe reference, then so is the result. #[stable(feature = "slice_get_slice", since = "1.28.0")] #[rustc_on_unimplemented( on(T = "str", label = "string indices are ranges of `usize`",), @@ -2921,7 +2928,7 @@ see chapter in The Book : private_slice_index::Sealed { +pub unsafe trait SliceIndex: private_slice_index::Sealed { /// The output type returned by methods. #[stable(feature = "slice_get_slice", since = "1.28.0")] type Output: ?Sized; @@ -2938,21 +2945,21 @@ pub trait SliceIndex: private_slice_index::Sealed { /// Returns a shared reference to the output at this location, without /// performing any bounds checking. - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. + /// Calling this method with an out-of-bounds index or a dangling `slice` pointer + /// is *[undefined behavior]* even if the resulting reference is not used. /// /// [undefined behavior]: ../../reference/behavior-considered-undefined.html #[unstable(feature = "slice_index_methods", issue = "none")] - unsafe fn get_unchecked(self, slice: &T) -> &Self::Output; + unsafe fn get_unchecked(self, slice: *const T) -> *const Self::Output; /// Returns a mutable reference to the output at this location, without /// performing any bounds checking. - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. + /// Calling this method with an out-of-bounds index or a dangling `slice` pointer + /// is *[undefined behavior]* even if the resulting reference is not used. /// /// [undefined behavior]: ../../reference/behavior-considered-undefined.html #[unstable(feature = "slice_index_methods", issue = "none")] - unsafe fn get_unchecked_mut(self, slice: &mut T) -> &mut Self::Output; + unsafe fn get_unchecked_mut(self, slice: *mut T) -> *mut Self::Output; /// Returns a shared reference to the output at this location, panicking /// if out of bounds. @@ -2968,33 +2975,32 @@ pub trait SliceIndex: private_slice_index::Sealed { } #[stable(feature = "slice_get_slice_impls", since = "1.15.0")] -impl SliceIndex<[T]> for usize { +unsafe impl SliceIndex<[T]> for usize { type Output = T; #[inline] fn get(self, slice: &[T]) -> Option<&T> { - if self < slice.len() { unsafe { Some(self.get_unchecked(slice)) } } else { None } + if self < slice.len() { unsafe { Some(&*self.get_unchecked(slice)) } } else { None } } #[inline] fn get_mut(self, slice: &mut [T]) -> Option<&mut T> { - if self < slice.len() { unsafe { Some(self.get_unchecked_mut(slice)) } } else { None } + if self < slice.len() { unsafe { Some(&mut *self.get_unchecked_mut(slice)) } } else { None } } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &T { - // SAFETY: `slice` cannot be longer than `isize::MAX` and - // the caller guarantees that `self` is in bounds of `slice` - // so `self` cannot overflow an `isize`, so the call to `add` is safe. - // The obtained pointer comes from a reference which is guaranteed - // to be valid. - unsafe { &*slice.as_ptr().add(self) } + unsafe fn get_unchecked(self, slice: *const [T]) -> *const T { + // SAFETY: the caller guarantees that `slice` is not dangling, so it + // cannot be longer than `isize::MAX`. They also guarantee that + // `self` is in bounds of `slice` so `self` cannot overflow an `isize`, + // so the call to `add` is safe. + unsafe { slice.as_ptr().add(self) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut T { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut T { // SAFETY: see comments for `get_unchecked` above. - unsafe { &mut *slice.as_mut_ptr().add(self) } + unsafe { slice.as_mut_ptr().add(self) } } #[inline] @@ -3011,7 +3017,7 @@ impl SliceIndex<[T]> for usize { } #[stable(feature = "slice_get_slice_impls", since = "1.15.0")] -impl SliceIndex<[T]> for ops::Range { +unsafe impl SliceIndex<[T]> for ops::Range { type Output = [T]; #[inline] @@ -3019,7 +3025,7 @@ impl SliceIndex<[T]> for ops::Range { if self.start > self.end || self.end > slice.len() { None } else { - unsafe { Some(self.get_unchecked(slice)) } + unsafe { Some(&*self.get_unchecked(slice)) } } } @@ -3028,24 +3034,25 @@ impl SliceIndex<[T]> for ops::Range { if self.start > self.end || self.end > slice.len() { None } else { - unsafe { Some(self.get_unchecked_mut(slice)) } + unsafe { Some(&mut *self.get_unchecked_mut(slice)) } } } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { - // SAFETY: `slice` cannot be longer than `isize::MAX` and - // the caller guarantees that `self` is in bounds of `slice` - // so `self` cannot overflow an `isize`, so the call to `add` is safe. - // Also, since the caller guarantees that `self` is in bounds of `slice`, - // `from_raw_parts` will give a subslice of `slice` which is always safe. - unsafe { from_raw_parts(slice.as_ptr().add(self.start), self.end - self.start) } + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { + // SAFETY: the caller guarantees that `slice` is not dangling, so it + // cannot be longer than `isize::MAX`. They also guarantee that + // `self` is in bounds of `slice` so `self` cannot overflow an `isize`, + // so the call to `add` is safe. + unsafe { ptr::slice_from_raw_parts(slice.as_ptr().add(self.start), self.end - self.start) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { // SAFETY: see comments for `get_unchecked` above. - unsafe { from_raw_parts_mut(slice.as_mut_ptr().add(self.start), self.end - self.start) } + unsafe { + ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().add(self.start), self.end - self.start) + } } #[inline] @@ -3055,7 +3062,7 @@ impl SliceIndex<[T]> for ops::Range { } else if self.end > slice.len() { slice_index_len_fail(self.end, slice.len()); } - unsafe { self.get_unchecked(slice) } + unsafe { &*self.get_unchecked(slice) } } #[inline] @@ -3065,12 +3072,12 @@ impl SliceIndex<[T]> for ops::Range { } else if self.end > slice.len() { slice_index_len_fail(self.end, slice.len()); } - unsafe { self.get_unchecked_mut(slice) } + unsafe { &mut *self.get_unchecked_mut(slice) } } } #[stable(feature = "slice_get_slice_impls", since = "1.15.0")] -impl SliceIndex<[T]> for ops::RangeTo { +unsafe impl SliceIndex<[T]> for ops::RangeTo { type Output = [T]; #[inline] @@ -3084,13 +3091,13 @@ impl SliceIndex<[T]> for ops::RangeTo { } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked`. unsafe { (0..self.end).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`. unsafe { (0..self.end).get_unchecked_mut(slice) } } @@ -3107,7 +3114,7 @@ impl SliceIndex<[T]> for ops::RangeTo { } #[stable(feature = "slice_get_slice_impls", since = "1.15.0")] -impl SliceIndex<[T]> for ops::RangeFrom { +unsafe impl SliceIndex<[T]> for ops::RangeFrom { type Output = [T]; #[inline] @@ -3121,13 +3128,13 @@ impl SliceIndex<[T]> for ops::RangeFrom { } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked`. unsafe { (self.start..slice.len()).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`. unsafe { (self.start..slice.len()).get_unchecked_mut(slice) } } @@ -3144,7 +3151,7 @@ impl SliceIndex<[T]> for ops::RangeFrom { } #[stable(feature = "slice_get_slice_impls", since = "1.15.0")] -impl SliceIndex<[T]> for ops::RangeFull { +unsafe impl SliceIndex<[T]> for ops::RangeFull { type Output = [T]; #[inline] @@ -3158,12 +3165,12 @@ impl SliceIndex<[T]> for ops::RangeFull { } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { slice } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { slice } @@ -3179,7 +3186,7 @@ impl SliceIndex<[T]> for ops::RangeFull { } #[stable(feature = "inclusive_range", since = "1.26.0")] -impl SliceIndex<[T]> for ops::RangeInclusive { +unsafe impl SliceIndex<[T]> for ops::RangeInclusive { type Output = [T]; #[inline] @@ -3197,13 +3204,13 @@ impl SliceIndex<[T]> for ops::RangeInclusive { } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked`. unsafe { (*self.start()..self.end() + 1).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`. unsafe { (*self.start()..self.end() + 1).get_unchecked_mut(slice) } } @@ -3226,7 +3233,7 @@ impl SliceIndex<[T]> for ops::RangeInclusive { } #[stable(feature = "inclusive_range", since = "1.26.0")] -impl SliceIndex<[T]> for ops::RangeToInclusive { +unsafe impl SliceIndex<[T]> for ops::RangeToInclusive { type Output = [T]; #[inline] @@ -3240,13 +3247,13 @@ impl SliceIndex<[T]> for ops::RangeToInclusive { } #[inline] - unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { + unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked`. unsafe { (0..=self.end).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { + unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { // SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`. unsafe { (0..=self.end).get_unchecked_mut(slice) } } diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index 0014501d2c4d0..5c4134b0749b9 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -1731,7 +1731,8 @@ Section: Trait implementations mod traits { use crate::cmp::Ordering; use crate::ops; - use crate::slice::{self, SliceIndex}; + use crate::ptr; + use crate::slice::SliceIndex; /// Implements ordering of strings. /// @@ -1822,7 +1823,7 @@ mod traits { /// /// Equivalent to `&self[0 .. len]` or `&mut self[0 .. len]`. #[stable(feature = "str_checked_slicing", since = "1.20.0")] - impl SliceIndex for ops::RangeFull { + unsafe impl SliceIndex for ops::RangeFull { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { @@ -1833,11 +1834,11 @@ mod traits { Some(slice) } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { slice } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { slice } #[inline] @@ -1886,7 +1887,7 @@ mod traits { /// // &s[3 .. 100]; /// ``` #[stable(feature = "str_checked_slicing", since = "1.20.0")] - impl SliceIndex for ops::Range { + unsafe impl SliceIndex for ops::Range { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { @@ -1894,8 +1895,10 @@ mod traits { && slice.is_char_boundary(self.start) && slice.is_char_boundary(self.end) { - // SAFETY: just checked that `start` and `end` are on a char boundary. - Some(unsafe { self.get_unchecked(slice) }) + // SAFETY: just checked that `start` and `end` are on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + // We also checked char boundaries, so this is valid UTF-8. + Some(unsafe { &*self.get_unchecked(slice) }) } else { None } @@ -1907,34 +1910,28 @@ mod traits { && slice.is_char_boundary(self.end) { // SAFETY: just checked that `start` and `end` are on a char boundary. - Some(unsafe { self.get_unchecked_mut(slice) }) + // We know the pointer is unique because we got it from `slice`. + Some(unsafe { &mut *self.get_unchecked_mut(slice) }) } else { None } } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { + let slice = slice as *const [u8]; // SAFETY: the caller guarantees that `self` is in bounds of `slice` // which satisfies all the conditions for `add`. let ptr = unsafe { slice.as_ptr().add(self.start) }; let len = self.end - self.start; - // SAFETY: as the caller guarantees that `self` is in bounds of `slice`, - // we can safely construct a subslice with `from_raw_parts` and use it - // since we return a shared thus immutable reference. - // The call to `from_utf8_unchecked` is safe since the data comes from - // a `str` which is guaranteed to be valid utf8, since the caller - // must guarantee that `self.start` and `self.end` are char boundaries. - unsafe { super::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) } + ptr::slice_from_raw_parts(ptr, len) as *const str } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { + let slice = slice as *mut [u8]; // SAFETY: see comments for `get_unchecked`. let ptr = unsafe { slice.as_mut_ptr().add(self.start) }; let len = self.end - self.start; - // SAFETY: mostly identical to the comments for `get_unchecked`, except that we - // can return a mutable reference since the caller passed a mutable reference - // and is thus guaranteed to have exclusive write access to `slice`. - unsafe { super::from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, len)) } + ptr::slice_from_raw_parts_mut(ptr, len) as *mut str } #[inline] fn index(self, slice: &str) -> &Self::Output { @@ -1949,8 +1946,9 @@ mod traits { && slice.is_char_boundary(self.start) && slice.is_char_boundary(self.end) { - // SAFETY: just checked that `start` and `end` are on a char boundary. - unsafe { self.get_unchecked_mut(slice) } + // SAFETY: just checked that `start` and `end` are on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + unsafe { &mut *self.get_unchecked_mut(slice) } } else { super::slice_error_fail(slice, self.start, self.end) } @@ -1973,13 +1971,14 @@ mod traits { /// Panics if `end` does not point to the starting byte offset of a /// character (as defined by `is_char_boundary`), or if `end > len`. #[stable(feature = "str_checked_slicing", since = "1.20.0")] - impl SliceIndex for ops::RangeTo { + unsafe impl SliceIndex for ops::RangeTo { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { if slice.is_char_boundary(self.end) { - // SAFETY: just checked that `end` is on a char boundary. - Some(unsafe { self.get_unchecked(slice) }) + // SAFETY: just checked that `end` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + Some(unsafe { &*self.get_unchecked(slice) }) } else { None } @@ -1987,30 +1986,24 @@ mod traits { #[inline] fn get_mut(self, slice: &mut str) -> Option<&mut Self::Output> { if slice.is_char_boundary(self.end) { - // SAFETY: just checked that `end` is on a char boundary. - Some(unsafe { self.get_unchecked_mut(slice) }) + // SAFETY: just checked that `end` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + Some(unsafe { &mut *self.get_unchecked_mut(slice) }) } else { None } } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { + let slice = slice as *const [u8]; let ptr = slice.as_ptr(); - // SAFETY: as the caller guarantees that `self` is in bounds of `slice`, - // we can safely construct a subslice with `from_raw_parts` and use it - // since we return a shared thus immutable reference. - // The call to `from_utf8_unchecked` is safe since the data comes from - // a `str` which is guaranteed to be valid utf8, since the caller - // must guarantee that `self.end` is a char boundary. - unsafe { super::from_utf8_unchecked(slice::from_raw_parts(ptr, self.end)) } + ptr::slice_from_raw_parts(ptr, self.end) as *const str } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { + let slice = slice as *mut [u8]; let ptr = slice.as_mut_ptr(); - // SAFETY: mostly identical to `get_unchecked`, except that we can safely - // return a mutable reference since the caller passed a mutable reference - // and is thus guaranteed to have exclusive write access to `slice`. - unsafe { super::from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, self.end)) } + ptr::slice_from_raw_parts_mut(ptr, self.end) as *mut str } #[inline] fn index(self, slice: &str) -> &Self::Output { @@ -2020,8 +2013,9 @@ mod traits { #[inline] fn index_mut(self, slice: &mut str) -> &mut Self::Output { if slice.is_char_boundary(self.end) { - // SAFETY: just checked that `end` is on a char boundary. - unsafe { self.get_unchecked_mut(slice) } + // SAFETY: just checked that `end` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + unsafe { &mut *self.get_unchecked_mut(slice) } } else { super::slice_error_fail(slice, 0, self.end) } @@ -2045,13 +2039,14 @@ mod traits { /// Panics if `begin` does not point to the starting byte offset of /// a character (as defined by `is_char_boundary`), or if `begin >= len`. #[stable(feature = "str_checked_slicing", since = "1.20.0")] - impl SliceIndex for ops::RangeFrom { + unsafe impl SliceIndex for ops::RangeFrom { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { if slice.is_char_boundary(self.start) { - // SAFETY: just checked that `start` is on a char boundary. - Some(unsafe { self.get_unchecked(slice) }) + // SAFETY: just checked that `start` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + Some(unsafe { &*self.get_unchecked(slice) }) } else { None } @@ -2059,35 +2054,29 @@ mod traits { #[inline] fn get_mut(self, slice: &mut str) -> Option<&mut Self::Output> { if slice.is_char_boundary(self.start) { - // SAFETY: just checked that `start` is on a char boundary. - Some(unsafe { self.get_unchecked_mut(slice) }) + // SAFETY: just checked that `start` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + Some(unsafe { &mut *self.get_unchecked_mut(slice) }) } else { None } } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { + let slice = slice as *const [u8]; // SAFETY: the caller guarantees that `self` is in bounds of `slice` // which satisfies all the conditions for `add`. let ptr = unsafe { slice.as_ptr().add(self.start) }; let len = slice.len() - self.start; - // SAFETY: as the caller guarantees that `self` is in bounds of `slice`, - // we can safely construct a subslice with `from_raw_parts` and use it - // since we return a shared thus immutable reference. - // The call to `from_utf8_unchecked` is safe since the data comes from - // a `str` which is guaranteed to be valid utf8, since the caller - // must guarantee that `self.start` is a char boundary. - unsafe { super::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) } + ptr::slice_from_raw_parts(ptr, len) as *const str } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { + let slice = slice as *mut [u8]; // SAFETY: identical to `get_unchecked`. let ptr = unsafe { slice.as_mut_ptr().add(self.start) }; let len = slice.len() - self.start; - // SAFETY: mostly identical to `get_unchecked`, except that we can safely - // return a mutable reference since the caller passed a mutable reference - // and is thus guaranteed to have exclusive write access to `slice`. - unsafe { super::from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, len)) } + ptr::slice_from_raw_parts_mut(ptr, len) as *mut str } #[inline] fn index(self, slice: &str) -> &Self::Output { @@ -2097,8 +2086,9 @@ mod traits { #[inline] fn index_mut(self, slice: &mut str) -> &mut Self::Output { if slice.is_char_boundary(self.start) { - // SAFETY: just checked that `start` is on a char boundary. - unsafe { self.get_unchecked_mut(slice) } + // SAFETY: just checked that `start` is on a char boundary, + // and we are passing in a safe reference, so the return value will also be one. + unsafe { &mut *self.get_unchecked_mut(slice) } } else { super::slice_error_fail(slice, self.start, slice.len()) } @@ -2122,7 +2112,7 @@ mod traits { /// to the ending byte offset of a character (`end + 1` is either a starting /// byte offset or equal to `len`), if `begin > end`, or if `end >= len`. #[stable(feature = "inclusive_range", since = "1.26.0")] - impl SliceIndex for ops::RangeInclusive { + unsafe impl SliceIndex for ops::RangeInclusive { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { @@ -2141,12 +2131,12 @@ mod traits { } } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { // SAFETY: the caller must uphold the safety contract for `get_unchecked`. unsafe { (*self.start()..self.end() + 1).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`. unsafe { (*self.start()..self.end() + 1).get_unchecked_mut(slice) } } @@ -2181,7 +2171,7 @@ mod traits { /// (`end + 1` is either a starting byte offset as defined by /// `is_char_boundary`, or equal to `len`), or if `end >= len`. #[stable(feature = "inclusive_range", since = "1.26.0")] - impl SliceIndex for ops::RangeToInclusive { + unsafe impl SliceIndex for ops::RangeToInclusive { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { @@ -2192,12 +2182,12 @@ mod traits { if self.end == usize::MAX { None } else { (..self.end + 1).get_mut(slice) } } #[inline] - unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { + unsafe fn get_unchecked(self, slice: *const str) -> *const Self::Output { // SAFETY: the caller must uphold the safety contract for `get_unchecked`. unsafe { (..self.end + 1).get_unchecked(slice) } } #[inline] - unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut Self::Output { // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`. unsafe { (..self.end + 1).get_unchecked_mut(slice) } } @@ -2560,8 +2550,10 @@ impl str { #[stable(feature = "str_checked_slicing", since = "1.20.0")] #[inline] pub unsafe fn get_unchecked>(&self, i: I) -> &I::Output { - // SAFETY: the caller must uphold the safety contract for `get_unchecked`. - unsafe { i.get_unchecked(self) } + // SAFETY: the caller must uphold the safety contract for `get_unchecked`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &*i.get_unchecked(self) } } /// Returns a mutable, unchecked subslice of `str`. @@ -2593,8 +2585,10 @@ impl str { #[stable(feature = "str_checked_slicing", since = "1.20.0")] #[inline] pub unsafe fn get_unchecked_mut>(&mut self, i: I) -> &mut I::Output { - // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`. - unsafe { i.get_unchecked_mut(self) } + // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &mut *i.get_unchecked_mut(self) } } /// Creates a string slice from another string slice, bypassing safety @@ -2644,8 +2638,10 @@ impl str { #[rustc_deprecated(since = "1.29.0", reason = "use `get_unchecked(begin..end)` instead")] #[inline] pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str { - // SAFETY: the caller must uphold the safety contract for `get_unchecked`. - unsafe { (begin..end).get_unchecked(self) } + // SAFETY: the caller must uphold the safety contract for `get_unchecked`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &*(begin..end).get_unchecked(self) } } /// Creates a string slice from another string slice, bypassing safety @@ -2676,8 +2672,10 @@ impl str { #[rustc_deprecated(since = "1.29.0", reason = "use `get_unchecked_mut(begin..end)` instead")] #[inline] pub unsafe fn slice_mut_unchecked(&mut self, begin: usize, end: usize) -> &mut str { - // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`. - unsafe { (begin..end).get_unchecked_mut(self) } + // SAFETY: the caller must uphold the safety contract for `get_unchecked_mut`; + // the slice is dereferencable because `self` is a safe reference. + // The returned pointer is safe because impls of `SliceIndex` have to guarantee that it is. + unsafe { &mut *(begin..end).get_unchecked_mut(self) } } /// Divide one string slice into two at an index. From 5f5c98bd8a27b5313d0b1a5830076139c562b6b6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 5 Jul 2020 18:39:04 +0200 Subject: [PATCH 33/62] add (unchecked) indexing methods to raw pointers and NonNull --- src/libcore/lib.rs | 2 +- src/libcore/ptr/const_ptr.rs | 37 ++++++++++++++++++++++++++++---- src/libcore/ptr/mut_ptr.rs | 38 ++++++++++++++++++++++++++++----- src/libcore/ptr/non_null.rs | 41 +++++++++++++++++++++++++++++++----- 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs index 66a1bea0c44b5..1f906650dfa56 100644 --- a/src/libcore/lib.rs +++ b/src/libcore/lib.rs @@ -93,7 +93,6 @@ #![feature(const_slice_ptr_len)] #![feature(const_type_name)] #![feature(const_likely)] -#![feature(const_slice_ptr_ptr)] #![feature(custom_inner_attributes)] #![feature(decl_macro)] #![feature(doc_cfg)] @@ -149,6 +148,7 @@ #![feature(associated_type_bounds)] #![feature(const_type_id)] #![feature(const_caller_location)] +#![feature(slice_ptr_get)] #![feature(no_niche)] // rust-lang/rust#68303 #![feature(unsafe_block_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/src/libcore/ptr/const_ptr.rs b/src/libcore/ptr/const_ptr.rs index f926befb4b323..22c741d4651a5 100644 --- a/src/libcore/ptr/const_ptr.rs +++ b/src/libcore/ptr/const_ptr.rs @@ -2,6 +2,7 @@ use super::*; use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::intrinsics; use crate::mem; +use crate::slice::SliceIndex; #[lang = "const_ptr"] impl *const T { @@ -834,19 +835,47 @@ impl *const [T] { /// # Examples /// /// ```rust - /// #![feature(slice_ptr_ptr)] - /// + /// #![feature(slice_ptr_get)] /// use std::ptr; /// /// let slice: *const [i8] = ptr::slice_from_raw_parts(ptr::null(), 3); /// assert_eq!(slice.as_ptr(), 0 as *const i8); /// ``` #[inline] - #[unstable(feature = "slice_ptr_ptr", issue = "none")] - #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] pub const fn as_ptr(self) -> *const T { self as *const T } + + /// Returns a raw pointer to an element or subslice, without doing bounds + /// checking. + /// + /// Calling this method with an out-of-bounds index or when `self` is not dereferencable + /// is *[undefined behavior]* even if the resulting pointer is not used. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// # Examples + /// + /// ``` + /// #![feature(slice_ptr_get)] + /// + /// let x = &[1, 2, 4] as *const [i32]; + /// + /// unsafe { + /// assert_eq!(x.get_unchecked(1), x.as_ptr().add(1)); + /// } + /// ``` + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[inline] + pub unsafe fn get_unchecked(self, index: I) -> *const I::Output + where + I: SliceIndex<[T]>, + { + // SAFETY: the caller ensures that `self` is dereferencable and `index` in-bounds. + unsafe { index.get_unchecked(self) } + } } // Equality for pointers diff --git a/src/libcore/ptr/mut_ptr.rs b/src/libcore/ptr/mut_ptr.rs index 70a81f85da8f4..56bade706942a 100644 --- a/src/libcore/ptr/mut_ptr.rs +++ b/src/libcore/ptr/mut_ptr.rs @@ -1,6 +1,7 @@ use super::*; use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::intrinsics; +use crate::slice::SliceIndex; #[lang = "mut_ptr"] impl *mut T { @@ -1014,7 +1015,6 @@ impl *mut [T] { /// /// ```rust /// #![feature(slice_ptr_len)] - /// /// use std::ptr; /// /// let slice: *mut [i8] = ptr::slice_from_raw_parts_mut(ptr::null_mut(), 3); @@ -1036,19 +1036,47 @@ impl *mut [T] { /// # Examples /// /// ```rust - /// #![feature(slice_ptr_ptr)] - /// + /// #![feature(slice_ptr_get)] /// use std::ptr; /// /// let slice: *mut [i8] = ptr::slice_from_raw_parts_mut(ptr::null_mut(), 3); /// assert_eq!(slice.as_mut_ptr(), 0 as *mut i8); /// ``` #[inline] - #[unstable(feature = "slice_ptr_ptr", issue = "none")] - #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] pub const fn as_mut_ptr(self) -> *mut T { self as *mut T } + + /// Returns a raw pointer to an element or subslice, without doing bounds + /// checking. + /// + /// Calling this method with an out-of-bounds index or when `self` is not dereferencable + /// is *[undefined behavior]* even if the resulting pointer is not used. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// # Examples + /// + /// ``` + /// #![feature(slice_ptr_get)] + /// + /// let x = &mut [1, 2, 4] as *mut [i32]; + /// + /// unsafe { + /// assert_eq!(x.get_unchecked_mut(1), x.as_mut_ptr().add(1)); + /// } + /// ``` + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[inline] + pub unsafe fn get_unchecked_mut(self, index: I) -> *mut I::Output + where + I: SliceIndex<[T]>, + { + // SAFETY: the caller ensures that `self` is dereferencable and `index` in-bounds. + unsafe { index.get_unchecked_mut(self) } + } } // Equality for pointers diff --git a/src/libcore/ptr/non_null.rs b/src/libcore/ptr/non_null.rs index 3461d224c46b9..f59da631b46ab 100644 --- a/src/libcore/ptr/non_null.rs +++ b/src/libcore/ptr/non_null.rs @@ -6,6 +6,7 @@ use crate::marker::Unsize; use crate::mem; use crate::ops::{CoerceUnsized, DispatchFromDyn}; use crate::ptr::Unique; +use crate::slice::SliceIndex; /// `*mut T` but non-zero and covariant. /// @@ -192,7 +193,6 @@ impl NonNull<[T]> { /// /// ```rust /// #![feature(slice_ptr_len, nonnull_slice_from_raw_parts)] - /// /// use std::ptr::NonNull; /// /// let slice: NonNull<[i8]> = NonNull::slice_from_raw_parts(NonNull::dangling(), 3); @@ -210,20 +210,51 @@ impl NonNull<[T]> { /// # Examples /// /// ```rust - /// #![feature(slice_ptr_ptr, nonnull_slice_from_raw_parts)] - /// + /// #![feature(slice_ptr_get, nonnull_slice_from_raw_parts)] /// use std::ptr::NonNull; /// /// let slice: NonNull<[i8]> = NonNull::slice_from_raw_parts(NonNull::dangling(), 3); /// assert_eq!(slice.as_non_null_ptr(), NonNull::new(1 as *mut i8).unwrap()); /// ``` #[inline] - #[unstable(feature = "slice_ptr_ptr", issue = "none")] - #[rustc_const_unstable(feature = "const_slice_ptr_ptr", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] pub const fn as_non_null_ptr(self) -> NonNull { // SAFETY: We know `self` is non-null. unsafe { NonNull::new_unchecked(self.as_ptr().as_mut_ptr()) } } + + /// Returns a raw pointer to an element or subslice, without doing bounds + /// checking. + /// + /// Calling this method with an out-of-bounds index or when `self` is not dereferencable + /// is *[undefined behavior]* even if the resulting pointer is not used. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// # Examples + /// + /// ``` + /// #![feature(slice_ptr_get, nonnull_slice_from_raw_parts)] + /// use std::ptr::NonNull; + /// + /// let x = &mut [1, 2, 4]; + /// let x = NonNull::slice_from_raw_parts(NonNull::new(x.as_mut_ptr()).unwrap(), x.len()); + /// + /// unsafe { + /// assert_eq!(x.get_unchecked_mut(1).as_ptr(), x.as_non_null_ptr().as_ptr().add(1)); + /// } + /// ``` + #[unstable(feature = "slice_ptr_get", issue = "none")] + #[inline] + pub unsafe fn get_unchecked_mut(self, index: I) -> NonNull + where + I: SliceIndex<[T]>, + { + // SAFETY: the caller ensures that `self` is dereferencable and `index` in-bounds. + // As a consequence, the resulting pointer cannot be NULL. + unsafe { NonNull::new_unchecked(self.as_ptr().get_unchecked_mut(index)) } + } } #[stable(feature = "nonnull", since = "1.25.0")] From 00980b6ccda5ca6d8039cd493a7aa7f487c47c0d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 7 Jul 2020 14:13:03 +0200 Subject: [PATCH 34/62] clean up E0718 explanation --- src/librustc_error_codes/error_codes/E0718.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/librustc_error_codes/error_codes/E0718.md b/src/librustc_error_codes/error_codes/E0718.md index e7ae51ca58835..bb401ef6e2d4c 100644 --- a/src/librustc_error_codes/error_codes/E0718.md +++ b/src/librustc_error_codes/error_codes/E0718.md @@ -1,7 +1,6 @@ -This error indicates that a `#[lang = ".."]` attribute was placed -on the wrong type of item. +A `#[lang = ".."]` attribute was placed on the wrong type of item. -Examples of erroneous code: +Erroneous code example: ```compile_fail,E0718 #![feature(lang_items)] From c8b16cdbd0f06c66467c082b9d3e190255fa295d Mon Sep 17 00:00:00 2001 From: Dennis Hamester Date: Wed, 8 Jul 2020 07:39:49 +0200 Subject: [PATCH 35/62] rustdoc: Allow linking from private items to private types Fixes #74134 After PR #72771 this would trigger an intra_doc_link_resolution_failure warning when rustdoc is invoked without --document-private-items. Links from private items to private types are however never actually generated in that case and thus shouldn't produce a warning. These links are in fact a very useful tool to document crate internals. Tests are added for all 4 combinations of public/private items and link targets. Test 1 is the case mentioned above and fails without this commit. Tests 2 - 4 passed before already but are added nonetheless to prevent regressions. --- src/librustdoc/passes/collect_intra_doc_links.rs | 1 + src/test/rustdoc/issue-74134-1.rs | 10 ++++++++++ src/test/rustdoc/issue-74134-2.rs | 11 +++++++++++ src/test/rustdoc/issue-74134-3.rs | 11 +++++++++++ src/test/rustdoc/issue-74134-4.rs | 11 +++++++++++ 5 files changed, 44 insertions(+) create mode 100644 src/test/rustdoc/issue-74134-1.rs create mode 100644 src/test/rustdoc/issue-74134-2.rs create mode 100644 src/test/rustdoc/issue-74134-3.rs create mode 100644 src/test/rustdoc/issue-74134-4.rs diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index f1d1bf439f171..86f94af0c6e31 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -796,6 +796,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> { let hir_id = self.cx.tcx.hir().as_local_hir_id(local); if !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_id) + && (item.visibility == Visibility::Public) && !self.cx.render_options.document_private { let item_name = item.name.as_deref().unwrap_or(""); diff --git a/src/test/rustdoc/issue-74134-1.rs b/src/test/rustdoc/issue-74134-1.rs new file mode 100644 index 0000000000000..72d38638a794c --- /dev/null +++ b/src/test/rustdoc/issue-74134-1.rs @@ -0,0 +1,10 @@ +#![deny(intra_doc_link_resolution_failure)] + +// Linking from a private item to a private type is fine without --document-private-items. + +struct Private; + +pub struct Public { + /// [`Private`] + private: Private, +} diff --git a/src/test/rustdoc/issue-74134-2.rs b/src/test/rustdoc/issue-74134-2.rs new file mode 100644 index 0000000000000..f665e360b4982 --- /dev/null +++ b/src/test/rustdoc/issue-74134-2.rs @@ -0,0 +1,11 @@ +// compile-flags: --document-private-items +#![deny(intra_doc_link_resolution_failure)] + +// Linking from a private item to a private type is fine with --document-private-items. + +struct Private; + +pub struct Public { + /// [`Private`] + private: Private, +} diff --git a/src/test/rustdoc/issue-74134-3.rs b/src/test/rustdoc/issue-74134-3.rs new file mode 100644 index 0000000000000..b2709ecdaddda --- /dev/null +++ b/src/test/rustdoc/issue-74134-3.rs @@ -0,0 +1,11 @@ +// should-fail +#![deny(intra_doc_link_resolution_failure)] + +// Linking from a public item to a private type fails without --document-private-items. + +struct Private; + +pub struct Public { + /// [`Private`] + pub public: u32, +} diff --git a/src/test/rustdoc/issue-74134-4.rs b/src/test/rustdoc/issue-74134-4.rs new file mode 100644 index 0000000000000..efff74f279721 --- /dev/null +++ b/src/test/rustdoc/issue-74134-4.rs @@ -0,0 +1,11 @@ +// compile-flags: --document-private-items +#![deny(intra_doc_link_resolution_failure)] + +// Linking from a public item to a private type is fine with --document-private-items. + +struct Private; + +pub struct Public { + /// [`Private`] + pub public: u32, +} From 23d7b3f6f1a345ad95f0812c85613627164b6c39 Mon Sep 17 00:00:00 2001 From: Erik Desjardins Date: Fri, 10 Jul 2020 19:02:10 -0400 Subject: [PATCH 36/62] Remove an unwrap in layout computation --- src/librustc_middle/ty/layout.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustc_middle/ty/layout.rs b/src/librustc_middle/ty/layout.rs index 39b8566e7a873..efdb5f27afb2a 100644 --- a/src/librustc_middle/ty/layout.rs +++ b/src/librustc_middle/ty/layout.rs @@ -774,12 +774,12 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { (present_variants.next(), present_variants.next()) }; let present_first = match present_first { - present_first @ Some(_) => present_first, + Some(present_first) => present_first, // Uninhabited because it has no variants, or only absent ones. None if def.is_enum() => return tcx.layout_raw(param_env.and(tcx.types.never)), // If it's a struct, still compute a layout so that we can still compute the // field offsets. - None => Some(VariantIdx::new(0)), + None => VariantIdx::new(0), }; let is_struct = !def.is_enum() || @@ -791,7 +791,7 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { // Struct, or univariant enum equivalent to a struct. // (Typechecking will reject discriminant-sizing attrs.) - let v = present_first.unwrap(); + let v = present_first; let kind = if def.is_enum() || variants[v].is_empty() { StructKind::AlwaysSized } else { From 689e360d8273ed13e433ce3e7ae1046e33894234 Mon Sep 17 00:00:00 2001 From: Dennis Hamester Date: Sat, 11 Jul 2020 09:26:26 +0200 Subject: [PATCH 37/62] test: rustdoc-ui: Add issue-74134, replacing test/rustdoc/issue-74134-* As per the discussion in PR #74147, the 4 individual tests are replaced by a single one. The test is expanded to cover all 4 public/private cases, each with and without --document-private-items. --- src/test/rustdoc-ui/issue-74134.public.stderr | 10 +++++++ src/test/rustdoc-ui/issue-74134.rs | 26 +++++++++++++++++++ src/test/rustdoc/issue-74134-1.rs | 10 ------- src/test/rustdoc/issue-74134-2.rs | 11 -------- src/test/rustdoc/issue-74134-3.rs | 11 -------- src/test/rustdoc/issue-74134-4.rs | 11 -------- 6 files changed, 36 insertions(+), 43 deletions(-) create mode 100644 src/test/rustdoc-ui/issue-74134.public.stderr create mode 100644 src/test/rustdoc-ui/issue-74134.rs delete mode 100644 src/test/rustdoc/issue-74134-1.rs delete mode 100644 src/test/rustdoc/issue-74134-2.rs delete mode 100644 src/test/rustdoc/issue-74134-3.rs delete mode 100644 src/test/rustdoc/issue-74134-4.rs diff --git a/src/test/rustdoc-ui/issue-74134.public.stderr b/src/test/rustdoc-ui/issue-74134.public.stderr new file mode 100644 index 0000000000000..03f95f19d326e --- /dev/null +++ b/src/test/rustdoc-ui/issue-74134.public.stderr @@ -0,0 +1,10 @@ +warning: `[PrivateType]` public documentation for `public_item` links to a private item + --> $DIR/issue-74134.rs:19:10 + | +LL | /// [`PrivateType`] + | ^^^^^^^^^^^^^ this item is private + | + = note: `#[warn(intra_doc_link_resolution_failure)]` on by default + +warning: 1 warning emitted + diff --git a/src/test/rustdoc-ui/issue-74134.rs b/src/test/rustdoc-ui/issue-74134.rs new file mode 100644 index 0000000000000..d0747817580da --- /dev/null +++ b/src/test/rustdoc-ui/issue-74134.rs @@ -0,0 +1,26 @@ +// revisions: public private +// [private]compile-flags: --document-private-items +// check-pass + +// There are 4 cases here: +// 1. public item -> public type: no warning +// 2. public item -> private type: warning, if --document-private-items is not passed +// 3. private item -> public type: no warning +// 4. private item -> private type: no warning +// All 4 cases are tested with and without --document-private-items. +// +// Case 4 without --document-private-items is the one described in issue #74134. + +struct PrivateType; +pub struct PublicType; + +pub struct Public { + /// [`PublicType`] + /// [`PrivateType`] + //[public]~^ WARNING `[PrivateType]` public documentation for `public_item` links to a private item + pub public_item: u32, + + /// [`PublicType`] + /// [`PrivateType`] + private_item: u32, +} diff --git a/src/test/rustdoc/issue-74134-1.rs b/src/test/rustdoc/issue-74134-1.rs deleted file mode 100644 index 72d38638a794c..0000000000000 --- a/src/test/rustdoc/issue-74134-1.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![deny(intra_doc_link_resolution_failure)] - -// Linking from a private item to a private type is fine without --document-private-items. - -struct Private; - -pub struct Public { - /// [`Private`] - private: Private, -} diff --git a/src/test/rustdoc/issue-74134-2.rs b/src/test/rustdoc/issue-74134-2.rs deleted file mode 100644 index f665e360b4982..0000000000000 --- a/src/test/rustdoc/issue-74134-2.rs +++ /dev/null @@ -1,11 +0,0 @@ -// compile-flags: --document-private-items -#![deny(intra_doc_link_resolution_failure)] - -// Linking from a private item to a private type is fine with --document-private-items. - -struct Private; - -pub struct Public { - /// [`Private`] - private: Private, -} diff --git a/src/test/rustdoc/issue-74134-3.rs b/src/test/rustdoc/issue-74134-3.rs deleted file mode 100644 index b2709ecdaddda..0000000000000 --- a/src/test/rustdoc/issue-74134-3.rs +++ /dev/null @@ -1,11 +0,0 @@ -// should-fail -#![deny(intra_doc_link_resolution_failure)] - -// Linking from a public item to a private type fails without --document-private-items. - -struct Private; - -pub struct Public { - /// [`Private`] - pub public: u32, -} diff --git a/src/test/rustdoc/issue-74134-4.rs b/src/test/rustdoc/issue-74134-4.rs deleted file mode 100644 index efff74f279721..0000000000000 --- a/src/test/rustdoc/issue-74134-4.rs +++ /dev/null @@ -1,11 +0,0 @@ -// compile-flags: --document-private-items -#![deny(intra_doc_link_resolution_failure)] - -// Linking from a public item to a private type is fine with --document-private-items. - -struct Private; - -pub struct Public { - /// [`Private`] - pub public: u32, -} From b8632e15483420784e9f4b95c882d24839dbada9 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sat, 11 Jul 2020 03:37:14 -0400 Subject: [PATCH 38/62] Removed FIXME --- src/libstd/io/buffered.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index c8c9ee4a4b47c..03a9fb91e5d96 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -448,9 +448,6 @@ impl Seek for BufReader { /// [`flush`]: #method.flush #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { - // FIXME: Can this just be W, instead of Option? I don't see any code - // paths that lead to this being None, or that ever check if it IS none, - // even in drop implementations. #72925. inner: Option, // FIXME: Replace this with a VecDeque. Because VecDeque is a Ring buffer, // this would enable BufWriter to operate without any interior copies. From 140bfc58aa9d193aeca1cce905a00195f8f23f3b Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sat, 11 Jul 2020 03:41:06 -0400 Subject: [PATCH 39/62] Removed another FIXME --- src/libstd/io/buffered.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 03a9fb91e5d96..2f2a67b0be96d 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -449,12 +449,6 @@ impl Seek for BufReader { #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { inner: Option, - // FIXME: Replace this with a VecDeque. Because VecDeque is a Ring buffer, - // this would enable BufWriter to operate without any interior copies. - // It was also allow a much simpler implementation of flush_buf. The main - // blocker here is that VecDeque doesn't currently have the same - // slice-specific specializations (extend_from_slice, `Extend` - // specializations) buf: Vec, // #30888: If the inner writer panics in a call to write, we don't want to // write the buffered data a second time in BufWriter's destructor. This From 97048595e1e7bf3fc270ecf14ccaaac4cc71a241 Mon Sep 17 00:00:00 2001 From: Dennis Hamester Date: Sat, 11 Jul 2020 10:02:18 +0200 Subject: [PATCH 40/62] test: rustdoc-ui: Expand issue-74134 to cover types in a private module --- src/test/rustdoc-ui/issue-74134.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/rustdoc-ui/issue-74134.rs b/src/test/rustdoc-ui/issue-74134.rs index d0747817580da..ad6ace43071a3 100644 --- a/src/test/rustdoc-ui/issue-74134.rs +++ b/src/test/rustdoc-ui/issue-74134.rs @@ -24,3 +24,18 @@ pub struct Public { /// [`PrivateType`] private_item: u32, } + +// The following cases are identical to the ones above, except that they are in a private +// module. Thus they all fall into cases 3 and 4 and should not produce a warning. + +mod private { + pub struct Public { + /// [`super::PublicType`] + /// [`super::PrivateType`] + pub public_item: u32, + + /// [`super::PublicType`] + /// [`super::PrivateType`] + private_item: u32, + } +} From 87895251ea372a8a776a229c45dd703b4e5b22eb Mon Sep 17 00:00:00 2001 From: Dennis Hamester Date: Sat, 11 Jul 2020 10:31:27 +0200 Subject: [PATCH 41/62] test: rustdoc-ui: issue-74134: Shorten a too long line --- src/test/rustdoc-ui/issue-74134.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/rustdoc-ui/issue-74134.rs b/src/test/rustdoc-ui/issue-74134.rs index ad6ace43071a3..d561c2dd89015 100644 --- a/src/test/rustdoc-ui/issue-74134.rs +++ b/src/test/rustdoc-ui/issue-74134.rs @@ -17,7 +17,7 @@ pub struct PublicType; pub struct Public { /// [`PublicType`] /// [`PrivateType`] - //[public]~^ WARNING `[PrivateType]` public documentation for `public_item` links to a private item + //[public]~^ WARNING public documentation for `public_item` links to a private pub public_item: u32, /// [`PublicType`] From 0979545330689186dff27e22f539c1a06887981f Mon Sep 17 00:00:00 2001 From: Andy Russell Date: Sun, 28 Jun 2020 18:20:06 -0400 Subject: [PATCH 42/62] rustdoc: insert newlines between attributes --- src/librustdoc/html/render.rs | 21 +++++++++++++-------- src/test/rustdoc/attributes.rs | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 8bba21a2e7ace..301896fd2c1ad 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -42,6 +42,7 @@ use std::str; use std::string::ToString; use std::sync::Arc; +use itertools::Itertools; use rustc_ast_pretty::pprust; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -3170,15 +3171,19 @@ const ALLOWED_ATTRIBUTES: &[Symbol] = &[ // bar: usize, // } fn render_attributes(w: &mut Buffer, it: &clean::Item, top: bool) { - let mut attrs = String::new(); - - for attr in &it.attrs.other_attrs { - if !ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { - continue; - } + let attrs = it + .attrs + .other_attrs + .iter() + .filter_map(|attr| { + if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) { + Some(pprust::attribute_to_string(&attr)) + } else { + None + } + }) + .join("\n"); - attrs.push_str(&pprust::attribute_to_string(&attr)); - } if !attrs.is_empty() { write!( w, diff --git a/src/test/rustdoc/attributes.rs b/src/test/rustdoc/attributes.rs index e9cd3514a07e2..54c5939f908d4 100644 --- a/src/test/rustdoc/attributes.rs +++ b/src/test/rustdoc/attributes.rs @@ -8,8 +8,8 @@ pub extern "C" fn f() {} #[export_name = "bar"] pub extern "C" fn g() {} -// @has foo/enum.Foo.html '//*[@class="docblock attributes top-attr"]' '#[repr(i64)]' -// @has foo/enum.Foo.html '//*[@class="docblock attributes top-attr"]' '#[must_use]' +// @matches foo/enum.Foo.html '//*[@class="docblock attributes top-attr"]' \ +// '(?m)\A#\[repr\(i64\)\]\n#\[must_use\]\Z' #[repr(i64)] #[must_use] pub enum Foo { From 472843834491b28b8dda558c50da2763574b4839 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 11 Jul 2020 17:34:11 +0200 Subject: [PATCH 43/62] Improve wording --- src/librustc_error_codes/error_codes/E0718.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc_error_codes/error_codes/E0718.md b/src/librustc_error_codes/error_codes/E0718.md index bb401ef6e2d4c..1fe62ecf1f4e0 100644 --- a/src/librustc_error_codes/error_codes/E0718.md +++ b/src/librustc_error_codes/error_codes/E0718.md @@ -1,4 +1,4 @@ -A `#[lang = ".."]` attribute was placed on the wrong type of item. +A `#[lang = ".."]` attribute was placed on the wrong item type. Erroneous code example: From 997accc214f3a915541b06f3126568d367161882 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sat, 11 Jul 2020 14:45:22 -0400 Subject: [PATCH 44/62] Remove doubled "is_write_vectored" --- src/libstd/io/buffered.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 2f2a67b0be96d..597bad0c2eeba 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -1117,7 +1117,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { fn is_write_vectored(&self) -> bool { // It's hard to imagine these diverging, but it's worth checking // just in case, because we call `write_vectored` on both. - self.buffer.is_write_vectored() && self.inner().is_write_vectored() + self.buffer.is_write_vectored() } /// Write some data into this BufReader with line buffering. This means From 6a7b5df55ffddd5a152ebbb865d490e52e93bec7 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sat, 11 Jul 2020 15:33:45 -0400 Subject: [PATCH 45/62] Removed unused method --- src/libstd/io/buffered.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index 597bad0c2eeba..cedc993b46147 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -909,12 +909,6 @@ impl<'a, W: Write> LineWriterShim<'a, W> { Self { buffer } } - /// Get a reference to the inner writer (that is, the writer wrapped by - /// the BufWriter) - fn inner(&self) -> &W { - self.buffer.get_ref() - } - /// Get a mutable reference to the inner writer (that is, the writer /// wrapped by the BufWriter). Be careful with this writer, as writes to /// it will bypass the buffer. From 905b5ad80aca3e4a6d9d5f124978b0278ccdd48e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 11 Jul 2020 17:20:33 -0400 Subject: [PATCH 46/62] don't mark linux kernel module targets as a unix environment --- src/librustc_target/spec/linux_kernel_base.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/librustc_target/spec/linux_kernel_base.rs b/src/librustc_target/spec/linux_kernel_base.rs index 201d6a0fff93b..6d929d1244789 100644 --- a/src/librustc_target/spec/linux_kernel_base.rs +++ b/src/librustc_target/spec/linux_kernel_base.rs @@ -17,7 +17,6 @@ pub fn opts() -> TargetOptions { needs_plt: true, relro_level: RelroLevel::Full, relocation_model: RelocModel::Static, - target_family: Some("unix".to_string()), pre_link_args, ..Default::default() From 606593fecec5510a32b6aa3b0bc2bd5cf81f28e2 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 12 Jul 2020 01:00:22 -0400 Subject: [PATCH 47/62] Minor updates - Remove outdated comment - Refactor flush-retry behavior into its own method - Some other comment updates --- src/libstd/io/buffered.rs | 40 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs index cedc993b46147..7d9c33582bca1 100644 --- a/src/libstd/io/buffered.rs +++ b/src/libstd/io/buffered.rs @@ -920,6 +920,16 @@ impl<'a, W: Write> LineWriterShim<'a, W> { fn buffered(&self) -> &[u8] { self.buffer.buffer() } + + /// Flush the buffer iff the last byte is a newline (indicating that an + /// earlier write only succeeded partially, and we want to retry flushing + /// the buffered line before continuing with a subsequent write) + fn flush_if_completed_line(&mut self) -> io::Result<()> { + match self.buffered().last().copied() { + Some(b'\n') => self.buffer.flush_buf(), + _ => Ok(()), + } + } } impl<'a, W: Write> Write for LineWriterShim<'a, W> { @@ -941,12 +951,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write None => { - // Check for prior partial line writes that need to be retried. - // Only retry if the buffer contains a completed line, to - // avoid flushing partial lines. - if let Some(b'\n') = self.buffered().last().copied() { - self.buffer.flush_buf()?; - } + self.flush_if_completed_line()?; return self.buffer.write(buf); } // Otherwise, arrange for the lines to be written directly to the @@ -1025,9 +1030,10 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { /// Because sorting through an array of `IoSlice` can be a bit convoluted, /// This method differs from write in the following ways: /// - /// - It attempts to write all the buffers up to and including the one - /// containing the last newline. This means that it may attempt to - /// write a partial line. + /// - It attempts to write the full content of all the buffers up to and + /// including the one containing the last newline. This means that it + /// may attempt to write a partial line, that buffer has data past the + /// newline. /// - If the write only reports partial success, it does not attempt to /// find the precise location of the written bytes and buffer the rest. /// @@ -1057,12 +1063,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { let last_newline_buf_idx = match last_newline_buf_idx { // No newlines; just do a normal buffered write None => { - // Check for prior partial line writes that need to be retried. - // Only retry if the buffer contains a completed line, to - // avoid flushing partial lines. - if let Some(b'\n') = self.buffered().last().copied() { - self.buffer.flush_buf()?; - } + self.flush_if_completed_line()?; return self.buffer.write_vectored(bufs); } Some(i) => i, @@ -1109,8 +1110,6 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { } fn is_write_vectored(&self) -> bool { - // It's hard to imagine these diverging, but it's worth checking - // just in case, because we call `write_vectored` on both. self.buffer.is_write_vectored() } @@ -1127,12 +1126,7 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> { // If there are no new newlines (that is, if this write is less than // one line), just do a regular buffered write None => { - // Check for prior partial line writes that need to be retried. - // Only retry if the buffer contains a completed line, to - // avoid flushing partial lines. - if let Some(b'\n') = self.buffered().last().copied() { - self.buffer.flush_buf()?; - } + self.flush_if_completed_line()?; return self.buffer.write_all(buf); } // Otherwise, arrange for the lines to be written directly to the From 8082fb988a5915e693f18e6d299deaae64834079 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 12 Jul 2020 11:37:11 +0200 Subject: [PATCH 48/62] rename fast_thread_local -> thread_local_dtor; thread_local -> thread_local_key --- src/libstd/sys/unix/mod.rs | 4 +- ...t_thread_local.rs => thread_local_dtor.rs} | 5 +- .../{thread_local.rs => thread_local_key.rs} | 0 src/libstd/sys/windows/mod.rs | 4 +- ...t_thread_local.rs => thread_local_dtor.rs} | 2 +- .../{thread_local.rs => thread_local_key.rs} | 0 src/libstd/sys_common/mod.rs | 3 +- src/libstd/sys_common/thread_local_dtor.rs | 49 +++++++++++++++++++ .../{thread_local.rs => thread_local_key.rs} | 39 ++------------- src/libstd/thread/local.rs | 4 +- 10 files changed, 65 insertions(+), 45 deletions(-) rename src/libstd/sys/unix/{fast_thread_local.rs => thread_local_dtor.rs} (94%) rename src/libstd/sys/unix/{thread_local.rs => thread_local_key.rs} (100%) rename src/libstd/sys/windows/{fast_thread_local.rs => thread_local_dtor.rs} (52%) rename src/libstd/sys/windows/{thread_local.rs => thread_local_key.rs} (100%) create mode 100644 src/libstd/sys_common/thread_local_dtor.rs rename src/libstd/sys_common/{thread_local.rs => thread_local_key.rs} (85%) diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index b1688e74173d7..7cde02baedbe8 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -47,7 +47,6 @@ pub mod cmath; pub mod condvar; pub mod env; pub mod ext; -pub mod fast_thread_local; pub mod fd; pub mod fs; pub mod io; @@ -68,7 +67,8 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local; +pub mod thread_local_key; +pub mod thread_local_dtor; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/unix/fast_thread_local.rs b/src/libstd/sys/unix/thread_local_dtor.rs similarity index 94% rename from src/libstd/sys/unix/fast_thread_local.rs rename to src/libstd/sys/unix/thread_local_dtor.rs index 8730b4de8bed2..c3275eb6f0e50 100644 --- a/src/libstd/sys/unix/fast_thread_local.rs +++ b/src/libstd/sys/unix/thread_local_dtor.rs @@ -1,6 +1,9 @@ #![cfg(target_thread_local)] #![unstable(feature = "thread_local_internals", issue = "none")] +//! Provides thread-local destructors without an associated "key", which +//! can be more efficient. + // Since what appears to be glibc 2.18 this symbol has been shipped which // GCC and clang both use to invoke destructors in thread_local globals, so // let's do the same! @@ -16,7 +19,7 @@ ))] pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { use crate::mem; - use crate::sys_common::thread_local::register_dtor_fallback; + use crate::sys_common::thread_local_dtor::register_dtor_fallback; extern "C" { #[linkage = "extern_weak"] diff --git a/src/libstd/sys/unix/thread_local.rs b/src/libstd/sys/unix/thread_local_key.rs similarity index 100% rename from src/libstd/sys/unix/thread_local.rs rename to src/libstd/sys/unix/thread_local_key.rs diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 193ab5b47ef13..d6a8eec4b80cd 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -20,7 +20,6 @@ pub mod cmath; pub mod condvar; pub mod env; pub mod ext; -pub mod fast_thread_local; pub mod fs; pub mod handle; pub mod io; @@ -35,7 +34,8 @@ pub mod process; pub mod rand; pub mod rwlock; pub mod thread; -pub mod thread_local; +pub mod thread_local_key; +pub mod thread_local_dtor; pub mod time; cfg_if::cfg_if! { if #[cfg(not(target_vendor = "uwp"))] { diff --git a/src/libstd/sys/windows/fast_thread_local.rs b/src/libstd/sys/windows/thread_local_dtor.rs similarity index 52% rename from src/libstd/sys/windows/fast_thread_local.rs rename to src/libstd/sys/windows/thread_local_dtor.rs index 191fa07f32a55..7be13bc4b2bc7 100644 --- a/src/libstd/sys/windows/fast_thread_local.rs +++ b/src/libstd/sys/windows/thread_local_dtor.rs @@ -1,4 +1,4 @@ #![unstable(feature = "thread_local_internals", issue = "none")] #![cfg(target_thread_local)] -pub use crate::sys_common::thread_local::register_dtor_fallback as register_dtor; +pub use crate::sys_common::thread_local_dtor::register_dtor_fallback as register_dtor; diff --git a/src/libstd/sys/windows/thread_local.rs b/src/libstd/sys/windows/thread_local_key.rs similarity index 100% rename from src/libstd/sys/windows/thread_local.rs rename to src/libstd/sys/windows/thread_local_key.rs diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs index e03e0fc83454b..1212b05c88aff 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -65,7 +65,8 @@ pub mod remutex; pub mod rwlock; pub mod thread; pub mod thread_info; -pub mod thread_local; +pub mod thread_local_key; +pub mod thread_local_dtor; pub mod util; pub mod wtf8; diff --git a/src/libstd/sys_common/thread_local_dtor.rs b/src/libstd/sys_common/thread_local_dtor.rs new file mode 100644 index 0000000000000..6f5ebf4a27158 --- /dev/null +++ b/src/libstd/sys_common/thread_local_dtor.rs @@ -0,0 +1,49 @@ +//! Thread-local destructor +//! +//! Besides thread-local "keys" (pointer-sized non-adressable thread-local store +//! with an associated destructor), many platforms also provide thread-local +//! destructors that are not associated with any particular data. These are +//! often more efficient. +//! +//! This module provides a fallback implementation for that interface, based +//! on the less efficient thread-local "keys". Each platform provides +//! a `thread_local_dtor` module which will either re-export the fallback, +//! or implement something more efficient. + +#![unstable(feature = "thread_local_internals", issue = "none")] +#![allow(dead_code)] // sys isn't exported yet + +use crate::ptr; +use crate::sys_common::thread_local_key::StaticKey; + +pub unsafe fn register_dtor_fallback(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { + // The fallback implementation uses a vanilla OS-based TLS key to track + // the list of destructors that need to be run for this thread. The key + // then has its own destructor which runs all the other destructors. + // + // The destructor for DTORS is a little special in that it has a `while` + // loop to continuously drain the list of registered destructors. It + // *should* be the case that this loop always terminates because we + // provide the guarantee that a TLS key cannot be set after it is + // flagged for destruction. + + static DTORS: StaticKey = StaticKey::new(Some(run_dtors)); + type List = Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>; + if DTORS.get().is_null() { + let v: Box = box Vec::new(); + DTORS.set(Box::into_raw(v) as *mut u8); + } + let list: &mut List = &mut *(DTORS.get() as *mut List); + list.push((t, dtor)); + + unsafe extern "C" fn run_dtors(mut ptr: *mut u8) { + while !ptr.is_null() { + let list: Box = Box::from_raw(ptr as *mut List); + for (ptr, dtor) in list.into_iter() { + dtor(ptr); + } + ptr = DTORS.get(); + DTORS.set(ptr::null_mut()); + } + } +} diff --git a/src/libstd/sys_common/thread_local.rs b/src/libstd/sys_common/thread_local_key.rs similarity index 85% rename from src/libstd/sys_common/thread_local.rs rename to src/libstd/sys_common/thread_local_key.rs index 756b8d044a20e..ac5b128298d78 100644 --- a/src/libstd/sys_common/thread_local.rs +++ b/src/libstd/sys_common/thread_local_key.rs @@ -4,7 +4,7 @@ //! using the native OS-provided facilities (think `TlsAlloc` or //! `pthread_setspecific`). The interface of this differs from the other types //! of thread-local-storage provided in this crate in that OS-based TLS can only -//! get/set pointers, +//! get/set pointer-sized data, possibly with an associated destructor. //! //! This module also provides two flavors of TLS. One is intended for static //! initialization, and does not contain a `Drop` implementation to deallocate @@ -14,7 +14,7 @@ //! # Usage //! //! This module should likely not be used directly unless other primitives are -//! being built on. types such as `thread_local::spawn::Key` are likely much +//! being built on. Types such as `thread_local::spawn::Key` are likely much //! more useful in practice than this OS-based version which likely requires //! unsafe code to interoperate with. //! @@ -48,9 +48,8 @@ #![unstable(feature = "thread_local_internals", issue = "none")] #![allow(dead_code)] // sys isn't exported yet -use crate::ptr; use crate::sync::atomic::{self, AtomicUsize, Ordering}; -use crate::sys::thread_local as imp; +use crate::sys::thread_local_key as imp; use crate::sys_common::mutex::Mutex; /// A type for TLS keys that are statically allocated. @@ -233,38 +232,6 @@ impl Drop for Key { } } -pub unsafe fn register_dtor_fallback(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { - // The fallback implementation uses a vanilla OS-based TLS key to track - // the list of destructors that need to be run for this thread. The key - // then has its own destructor which runs all the other destructors. - // - // The destructor for DTORS is a little special in that it has a `while` - // loop to continuously drain the list of registered destructors. It - // *should* be the case that this loop always terminates because we - // provide the guarantee that a TLS key cannot be set after it is - // flagged for destruction. - - static DTORS: StaticKey = StaticKey::new(Some(run_dtors)); - type List = Vec<(*mut u8, unsafe extern "C" fn(*mut u8))>; - if DTORS.get().is_null() { - let v: Box = box Vec::new(); - DTORS.set(Box::into_raw(v) as *mut u8); - } - let list: &mut List = &mut *(DTORS.get() as *mut List); - list.push((t, dtor)); - - unsafe extern "C" fn run_dtors(mut ptr: *mut u8) { - while !ptr.is_null() { - let list: Box = Box::from_raw(ptr as *mut List); - for (ptr, dtor) in list.into_iter() { - dtor(ptr); - } - ptr = DTORS.get(); - DTORS.set(ptr::null_mut()); - } - } -} - #[cfg(test)] mod tests { use super::{Key, StaticKey}; diff --git a/src/libstd/thread/local.rs b/src/libstd/thread/local.rs index 094c468a6770e..ecd6fbc6b9395 100644 --- a/src/libstd/thread/local.rs +++ b/src/libstd/thread/local.rs @@ -363,7 +363,7 @@ pub mod fast { use crate::cell::Cell; use crate::fmt; use crate::mem; - use crate::sys::fast_thread_local::register_dtor; + use crate::sys::thread_local_dtor::register_dtor; #[derive(Copy, Clone)] enum DtorState { @@ -468,7 +468,7 @@ pub mod os { use crate::fmt; use crate::marker; use crate::ptr; - use crate::sys_common::thread_local::StaticKey as OsStaticKey; + use crate::sys_common::thread_local_key::StaticKey as OsStaticKey; pub struct Key { // OS-TLS key that we'll use to key off. From 7dc388654d6ef038065db23340e8eff7a567e5b4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 12 Jul 2020 11:45:04 +0200 Subject: [PATCH 49/62] adjust remaining targets --- src/libstd/sys/cloudabi/mod.rs | 4 ++-- src/libstd/sys/hermit/mod.rs | 4 ++-- .../{fast_thread_local.rs => thread_local_dtor.rs} | 0 .../thread_local.rs => hermit/thread_local_key.rs} | 10 +++++----- src/libstd/sys/sgx/mod.rs | 2 +- .../sys/sgx/{thread_local.rs => thread_local_key.rs} | 0 src/libstd/sys/unix/mod.rs | 2 +- src/libstd/sys/vxworks/mod.rs | 4 ++-- .../{fast_thread_local.rs => thread_local_dtor.rs} | 4 ---- .../vxworks/{thread_local.rs => thread_local_key.rs} | 0 src/libstd/sys/wasi/mod.rs | 8 ++++---- src/libstd/sys/wasm/mod.rs | 4 ++-- .../{fast_thread_local.rs => thread_local_dtor.rs} | 0 .../thread_local.rs => wasm/thread_local_key.rs} | 0 src/libstd/sys/windows/mod.rs | 2 +- src/libstd/sys_common/mod.rs | 2 +- 16 files changed, 21 insertions(+), 25 deletions(-) rename src/libstd/sys/hermit/{fast_thread_local.rs => thread_local_dtor.rs} (100%) rename src/libstd/sys/{wasm/thread_local.rs => hermit/thread_local_key.rs} (54%) rename src/libstd/sys/sgx/{thread_local.rs => thread_local_key.rs} (100%) rename src/libstd/sys/vxworks/{fast_thread_local.rs => thread_local_dtor.rs} (82%) rename src/libstd/sys/vxworks/{thread_local.rs => thread_local_key.rs} (100%) rename src/libstd/sys/wasm/{fast_thread_local.rs => thread_local_dtor.rs} (100%) rename src/libstd/sys/{hermit/thread_local.rs => wasm/thread_local_key.rs} (100%) diff --git a/src/libstd/sys/cloudabi/mod.rs b/src/libstd/sys/cloudabi/mod.rs index 8dbc31472d637..f7dd2c8d00fd2 100644 --- a/src/libstd/sys/cloudabi/mod.rs +++ b/src/libstd/sys/cloudabi/mod.rs @@ -16,8 +16,8 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -#[path = "../unix/thread_local.rs"] -pub mod thread_local; +#[path = "../unix/thread_local_key.rs"] +pub mod thread_local_key; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/hermit/mod.rs b/src/libstd/sys/hermit/mod.rs index 7bdc1be3b1702..675b82ceb775f 100644 --- a/src/libstd/sys/hermit/mod.rs +++ b/src/libstd/sys/hermit/mod.rs @@ -22,7 +22,6 @@ pub mod cmath; pub mod condvar; pub mod env; pub mod ext; -pub mod fast_thread_local; pub mod fd; pub mod fs; pub mod io; @@ -37,7 +36,8 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local; +pub mod thread_local_dtor; +pub mod thread_local_key; pub mod time; use crate::io::ErrorKind; diff --git a/src/libstd/sys/hermit/fast_thread_local.rs b/src/libstd/sys/hermit/thread_local_dtor.rs similarity index 100% rename from src/libstd/sys/hermit/fast_thread_local.rs rename to src/libstd/sys/hermit/thread_local_dtor.rs diff --git a/src/libstd/sys/wasm/thread_local.rs b/src/libstd/sys/hermit/thread_local_key.rs similarity index 54% rename from src/libstd/sys/wasm/thread_local.rs rename to src/libstd/sys/hermit/thread_local_key.rs index f8be9863ed56f..bf1b49eb83b7e 100644 --- a/src/libstd/sys/wasm/thread_local.rs +++ b/src/libstd/sys/hermit/thread_local_key.rs @@ -2,25 +2,25 @@ pub type Key = usize; #[inline] pub unsafe fn create(_dtor: Option) -> Key { - panic!("should not be used on the wasm target"); + panic!("should not be used on the hermit target"); } #[inline] pub unsafe fn set(_key: Key, _value: *mut u8) { - panic!("should not be used on the wasm target"); + panic!("should not be used on the hermit target"); } #[inline] pub unsafe fn get(_key: Key) -> *mut u8 { - panic!("should not be used on the wasm target"); + panic!("should not be used on the hermit target"); } #[inline] pub unsafe fn destroy(_key: Key) { - panic!("should not be used on the wasm target"); + panic!("should not be used on the hermit target"); } #[inline] pub fn requires_synchronized_create() -> bool { - panic!("should not be used on the wasm target"); + panic!("should not be used on the hermit target"); } diff --git a/src/libstd/sys/sgx/mod.rs b/src/libstd/sys/sgx/mod.rs index 397dd496ae8af..a4968ff7d4f54 100644 --- a/src/libstd/sys/sgx/mod.rs +++ b/src/libstd/sys/sgx/mod.rs @@ -30,7 +30,7 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local; +pub mod thread_local_key; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/sgx/thread_local.rs b/src/libstd/sys/sgx/thread_local_key.rs similarity index 100% rename from src/libstd/sys/sgx/thread_local.rs rename to src/libstd/sys/sgx/thread_local_key.rs diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index 7cde02baedbe8..eddf00d3979f5 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -67,8 +67,8 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local_key; pub mod thread_local_dtor; +pub mod thread_local_key; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/vxworks/mod.rs b/src/libstd/sys/vxworks/mod.rs index 0787e7098988c..1132a849e2f18 100644 --- a/src/libstd/sys/vxworks/mod.rs +++ b/src/libstd/sys/vxworks/mod.rs @@ -13,7 +13,6 @@ pub mod cmath; pub mod condvar; pub mod env; pub mod ext; -pub mod fast_thread_local; pub mod fd; pub mod fs; pub mod io; @@ -29,7 +28,8 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local; +pub mod thread_local_dtor; +pub mod thread_local_key; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/vxworks/fast_thread_local.rs b/src/libstd/sys/vxworks/thread_local_dtor.rs similarity index 82% rename from src/libstd/sys/vxworks/fast_thread_local.rs rename to src/libstd/sys/vxworks/thread_local_dtor.rs index 098668cf521dd..3f73f6c490326 100644 --- a/src/libstd/sys/vxworks/fast_thread_local.rs +++ b/src/libstd/sys/vxworks/thread_local_dtor.rs @@ -5,7 +5,3 @@ pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { use crate::sys_common::thread_local::register_dtor_fallback; register_dtor_fallback(t, dtor); } - -pub fn requires_move_before_drop() -> bool { - false -} diff --git a/src/libstd/sys/vxworks/thread_local.rs b/src/libstd/sys/vxworks/thread_local_key.rs similarity index 100% rename from src/libstd/sys/vxworks/thread_local.rs rename to src/libstd/sys/vxworks/thread_local_key.rs diff --git a/src/libstd/sys/wasi/mod.rs b/src/libstd/sys/wasi/mod.rs index 4fe9661421b03..85f5282034ff1 100644 --- a/src/libstd/sys/wasi/mod.rs +++ b/src/libstd/sys/wasi/mod.rs @@ -36,8 +36,6 @@ pub mod net; pub mod os; pub use crate::sys_common::os_str_bytes as os_str; pub mod ext; -#[path = "../wasm/fast_thread_local.rs"] -pub mod fast_thread_local; pub mod path; pub mod pipe; pub mod process; @@ -47,8 +45,10 @@ pub mod rwlock; pub mod stack_overflow; pub mod stdio; pub mod thread; -#[path = "../wasm/thread_local.rs"] -pub mod thread_local; +#[path = "../wasm/thread_local_dtor.rs"] +pub mod thread_local_dtor; +#[path = "../wasm/thread_local_key.rs"] +pub mod thread_local_key; pub mod time; #[cfg(not(test))] diff --git a/src/libstd/sys/wasm/mod.rs b/src/libstd/sys/wasm/mod.rs index 050e8099af4ba..6939596e52d78 100644 --- a/src/libstd/sys/wasm/mod.rs +++ b/src/libstd/sys/wasm/mod.rs @@ -20,7 +20,6 @@ pub mod alloc; pub mod args; pub mod cmath; pub mod env; -pub mod fast_thread_local; pub mod fs; pub mod io; pub mod memchr; @@ -32,7 +31,8 @@ pub mod process; pub mod stack_overflow; pub mod stdio; pub mod thread; -pub mod thread_local; +pub mod thread_local_dtor; +pub mod thread_local_key; pub mod time; pub use crate::sys_common::os_str_bytes as os_str; diff --git a/src/libstd/sys/wasm/fast_thread_local.rs b/src/libstd/sys/wasm/thread_local_dtor.rs similarity index 100% rename from src/libstd/sys/wasm/fast_thread_local.rs rename to src/libstd/sys/wasm/thread_local_dtor.rs diff --git a/src/libstd/sys/hermit/thread_local.rs b/src/libstd/sys/wasm/thread_local_key.rs similarity index 100% rename from src/libstd/sys/hermit/thread_local.rs rename to src/libstd/sys/wasm/thread_local_key.rs diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index d6a8eec4b80cd..9a52371280e15 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -34,8 +34,8 @@ pub mod process; pub mod rand; pub mod rwlock; pub mod thread; -pub mod thread_local_key; pub mod thread_local_dtor; +pub mod thread_local_key; pub mod time; cfg_if::cfg_if! { if #[cfg(not(target_vendor = "uwp"))] { diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs index 1212b05c88aff..e57bb267cbd0f 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -65,8 +65,8 @@ pub mod remutex; pub mod rwlock; pub mod thread; pub mod thread_info; -pub mod thread_local_key; pub mod thread_local_dtor; +pub mod thread_local_key; pub mod util; pub mod wtf8; From ff5e1078fc911c6ca97c64fbaefe7a2aebedbb0d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 12 Jul 2020 12:48:43 +0200 Subject: [PATCH 50/62] assign tracking issue --- src/libcore/ptr/const_ptr.rs | 6 +++--- src/libcore/ptr/mut_ptr.rs | 6 +++--- src/libcore/ptr/non_null.rs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libcore/ptr/const_ptr.rs b/src/libcore/ptr/const_ptr.rs index 22c741d4651a5..39d4aca636a05 100644 --- a/src/libcore/ptr/const_ptr.rs +++ b/src/libcore/ptr/const_ptr.rs @@ -842,8 +842,8 @@ impl *const [T] { /// assert_eq!(slice.as_ptr(), 0 as *const i8); /// ``` #[inline] - #[unstable(feature = "slice_ptr_get", issue = "none")] - #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "74265")] pub const fn as_ptr(self) -> *const T { self as *const T } @@ -867,7 +867,7 @@ impl *const [T] { /// assert_eq!(x.get_unchecked(1), x.as_ptr().add(1)); /// } /// ``` - #[unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] #[inline] pub unsafe fn get_unchecked(self, index: I) -> *const I::Output where diff --git a/src/libcore/ptr/mut_ptr.rs b/src/libcore/ptr/mut_ptr.rs index 56bade706942a..644465d7d17f8 100644 --- a/src/libcore/ptr/mut_ptr.rs +++ b/src/libcore/ptr/mut_ptr.rs @@ -1043,8 +1043,8 @@ impl *mut [T] { /// assert_eq!(slice.as_mut_ptr(), 0 as *mut i8); /// ``` #[inline] - #[unstable(feature = "slice_ptr_get", issue = "none")] - #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "74265")] pub const fn as_mut_ptr(self) -> *mut T { self as *mut T } @@ -1068,7 +1068,7 @@ impl *mut [T] { /// assert_eq!(x.get_unchecked_mut(1), x.as_mut_ptr().add(1)); /// } /// ``` - #[unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] #[inline] pub unsafe fn get_unchecked_mut(self, index: I) -> *mut I::Output where diff --git a/src/libcore/ptr/non_null.rs b/src/libcore/ptr/non_null.rs index f59da631b46ab..b362a49d604e7 100644 --- a/src/libcore/ptr/non_null.rs +++ b/src/libcore/ptr/non_null.rs @@ -217,8 +217,8 @@ impl NonNull<[T]> { /// assert_eq!(slice.as_non_null_ptr(), NonNull::new(1 as *mut i8).unwrap()); /// ``` #[inline] - #[unstable(feature = "slice_ptr_get", issue = "none")] - #[rustc_const_unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] + #[rustc_const_unstable(feature = "slice_ptr_get", issue = "74265")] pub const fn as_non_null_ptr(self) -> NonNull { // SAFETY: We know `self` is non-null. unsafe { NonNull::new_unchecked(self.as_ptr().as_mut_ptr()) } @@ -245,7 +245,7 @@ impl NonNull<[T]> { /// assert_eq!(x.get_unchecked_mut(1).as_ptr(), x.as_non_null_ptr().as_ptr().add(1)); /// } /// ``` - #[unstable(feature = "slice_ptr_get", issue = "none")] + #[unstable(feature = "slice_ptr_get", issue = "74265")] #[inline] pub unsafe fn get_unchecked_mut(self, index: I) -> NonNull where From 5afbc5201c7bf82cadd69a1a0cea49cfe1e41172 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sun, 12 Jul 2020 16:40:22 +0100 Subject: [PATCH 51/62] typeck: report placeholder type error w/out span This commit fixes a regression introduced in rust-lang/rust#70369 which meant that an error was not being emitted for invalid placeholder types when there wasn't a span available. Signed-off-by: David Wood --- src/librustc_typeck/astconv.rs | 4 +- src/librustc_typeck/collect.rs | 16 ++- src/test/ui/issues/issue-74086.rs | 4 + src/test/ui/issues/issue-74086.stderr | 12 ++ .../ui/typeck/typeck_type_placeholder_item.rs | 2 + .../typeck_type_placeholder_item.stderr | 128 ++++++++++-------- 6 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 src/test/ui/issues/issue-74086.rs create mode 100644 src/test/ui/issues/issue-74086.stderr diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs index 5d1949626dd84..7581940b6ac69 100644 --- a/src/librustc_typeck/astconv.rs +++ b/src/librustc_typeck/astconv.rs @@ -3049,14 +3049,14 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let bare_fn_ty = ty::Binder::bind(tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, unsafety, abi)); - if let (false, Some(ident_span)) = (self.allow_ty_infer(), ident_span) { + if !self.allow_ty_infer() { // We always collect the spans for placeholder types when evaluating `fn`s, but we // only want to emit an error complaining about them if infer types (`_`) are not // allowed. `allow_ty_infer` gates this behavior. We check for the presence of // `ident_span` to not emit an error twice when we have `fn foo(_: fn() -> _)`. crate::collect::placeholder_type_error( tcx, - ident_span.shrink_to_hi(), + ident_span.map(|sp| sp.shrink_to_hi()), &generics.params[..], visitor.0, true, diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 15481660a5218..625b72091a6cc 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -129,7 +129,7 @@ struct CollectItemTypesVisitor<'tcx> { /// all already existing generic type parameters to avoid suggesting a name that is already in use. crate fn placeholder_type_error( tcx: TyCtxt<'tcx>, - span: Span, + span: Option, generics: &[hir::GenericParam<'_>], placeholder_types: Vec, suggest: bool, @@ -137,12 +137,15 @@ crate fn placeholder_type_error( if placeholder_types.is_empty() { return; } - let type_name = generics.next_type_param_name(None); + let type_name = generics.next_type_param_name(None); let mut sugg: Vec<_> = placeholder_types.iter().map(|sp| (*sp, (*type_name).to_string())).collect(); + if generics.is_empty() { - sugg.push((span, format!("<{}>", type_name))); + if let Some(span) = span { + sugg.push((span, format!("<{}>", type_name))); + } } else if let Some(arg) = generics.iter().find(|arg| match arg.name { hir::ParamName::Plain(Ident { name: kw::Underscore, .. }) => true, _ => false, @@ -158,6 +161,7 @@ crate fn placeholder_type_error( format!(", {}", type_name), )); } + let mut err = bad_placeholder_type(tcx, placeholder_types); if suggest { err.multipart_suggestion( @@ -186,7 +190,7 @@ fn reject_placeholder_type_signatures_in_item(tcx: TyCtxt<'tcx>, item: &'tcx hir let mut visitor = PlaceholderHirTyCollector::default(); visitor.visit_item(item); - placeholder_type_error(tcx, generics.span, &generics.params[..], visitor.0, suggest); + placeholder_type_error(tcx, Some(generics.span), &generics.params[..], visitor.0, suggest); } impl Visitor<'tcx> for CollectItemTypesVisitor<'tcx> { @@ -722,7 +726,7 @@ fn convert_trait_item(tcx: TyCtxt<'_>, trait_item_id: hir::HirId) { // Account for `const C: _;` and `type T = _;`. let mut visitor = PlaceholderHirTyCollector::default(); visitor.visit_trait_item(trait_item); - placeholder_type_error(tcx, DUMMY_SP, &[], visitor.0, false); + placeholder_type_error(tcx, None, &[], visitor.0, false); } hir::TraitItemKind::Type(_, None) => {} @@ -745,7 +749,7 @@ fn convert_impl_item(tcx: TyCtxt<'_>, impl_item_id: hir::HirId) { // Account for `type T = _;` let mut visitor = PlaceholderHirTyCollector::default(); visitor.visit_impl_item(impl_item); - placeholder_type_error(tcx, DUMMY_SP, &[], visitor.0, false); + placeholder_type_error(tcx, None, &[], visitor.0, false); } hir::ImplItemKind::Const(..) => {} } diff --git a/src/test/ui/issues/issue-74086.rs b/src/test/ui/issues/issue-74086.rs new file mode 100644 index 0000000000000..f68a665b2f38d --- /dev/null +++ b/src/test/ui/issues/issue-74086.rs @@ -0,0 +1,4 @@ +fn main() { + static BUG: fn(_) -> u8 = |_| 8; + //~^ ERROR the type placeholder `_` is not allowed within types on item signatures [E0121] +} diff --git a/src/test/ui/issues/issue-74086.stderr b/src/test/ui/issues/issue-74086.stderr new file mode 100644 index 0000000000000..4127f48a093f4 --- /dev/null +++ b/src/test/ui/issues/issue-74086.stderr @@ -0,0 +1,12 @@ +error[E0121]: the type placeholder `_` is not allowed within types on item signatures + --> $DIR/issue-74086.rs:2:20 + | +LL | static BUG: fn(_) -> u8 = |_| 8; + | ^ + | | + | not allowed in type signatures + | help: use type parameters instead: `T` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0121`. diff --git a/src/test/ui/typeck/typeck_type_placeholder_item.rs b/src/test/ui/typeck/typeck_type_placeholder_item.rs index 99a7023089283..133c5231031fd 100644 --- a/src/test/ui/typeck/typeck_type_placeholder_item.rs +++ b/src/test/ui/typeck/typeck_type_placeholder_item.rs @@ -32,6 +32,7 @@ fn test7(x: _) { let _x: usize = x; } fn test8(_f: fn() -> _) { } //~^ ERROR the type placeholder `_` is not allowed within types on item signatures +//~^^ ERROR the type placeholder `_` is not allowed within types on item signatures struct Test9; @@ -98,6 +99,7 @@ pub fn main() { fn fn_test8(_f: fn() -> _) { } //~^ ERROR the type placeholder `_` is not allowed within types on item signatures + //~^^ ERROR the type placeholder `_` is not allowed within types on item signatures struct FnTest9; diff --git a/src/test/ui/typeck/typeck_type_placeholder_item.stderr b/src/test/ui/typeck/typeck_type_placeholder_item.stderr index 6c0653d5fcb7c..a1945f2b9cf4e 100644 --- a/src/test/ui/typeck/typeck_type_placeholder_item.stderr +++ b/src/test/ui/typeck/typeck_type_placeholder_item.stderr @@ -1,35 +1,35 @@ error: expected identifier, found reserved identifier `_` - --> $DIR/typeck_type_placeholder_item.rs:152:18 + --> $DIR/typeck_type_placeholder_item.rs:154:18 | LL | struct BadStruct<_>(_); | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/typeck_type_placeholder_item.rs:155:16 + --> $DIR/typeck_type_placeholder_item.rs:157:16 | LL | trait BadTrait<_> {} | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/typeck_type_placeholder_item.rs:165:19 + --> $DIR/typeck_type_placeholder_item.rs:167:19 | LL | struct BadStruct1<_, _>(_); | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/typeck_type_placeholder_item.rs:165:22 + --> $DIR/typeck_type_placeholder_item.rs:167:22 | LL | struct BadStruct1<_, _>(_); | ^ expected identifier, found reserved identifier error: expected identifier, found reserved identifier `_` - --> $DIR/typeck_type_placeholder_item.rs:170:19 + --> $DIR/typeck_type_placeholder_item.rs:172:19 | LL | struct BadStruct2<_, T>(_, T); | ^ expected identifier, found reserved identifier error: associated constant in `impl` without body - --> $DIR/typeck_type_placeholder_item.rs:201:5 + --> $DIR/typeck_type_placeholder_item.rs:203:5 | LL | const C: _; | ^^^^^^^^^^- @@ -37,7 +37,7 @@ LL | const C: _; | help: provide a definition for the constant: `= ;` error[E0403]: the name `_` is already used for a generic parameter in this item's generic parameters - --> $DIR/typeck_type_placeholder_item.rs:165:22 + --> $DIR/typeck_type_placeholder_item.rs:167:22 | LL | struct BadStruct1<_, _>(_); | - ^ already used @@ -131,6 +131,15 @@ help: use type parameters instead LL | fn test7(x: T) { let _x: usize = x; } | ^^^ ^ +error[E0121]: the type placeholder `_` is not allowed within types on item signatures + --> $DIR/typeck_type_placeholder_item.rs:33:22 + | +LL | fn test8(_f: fn() -> _) { } + | ^ + | | + | not allowed in type signatures + | help: use type parameters instead: `T` + error[E0121]: the type placeholder `_` is not allowed within types on item signatures --> $DIR/typeck_type_placeholder_item.rs:33:22 | @@ -143,7 +152,7 @@ LL | fn test8(_f: fn() -> T) { } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:46:26 + --> $DIR/typeck_type_placeholder_item.rs:47:26 | LL | fn test11(x: &usize) -> &_ { | -^ @@ -152,7 +161,7 @@ LL | fn test11(x: &usize) -> &_ { | help: replace with the correct return type: `&&usize` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:51:52 + --> $DIR/typeck_type_placeholder_item.rs:52:52 | LL | unsafe fn test12(x: *const usize) -> *const *const _ { | --------------^ @@ -161,7 +170,7 @@ LL | unsafe fn test12(x: *const usize) -> *const *const _ { | help: replace with the correct return type: `*const *const usize` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:65:8 + --> $DIR/typeck_type_placeholder_item.rs:66:8 | LL | a: _, | ^ not allowed in type signatures @@ -180,13 +189,13 @@ LL | b: (T, T), | error: missing type for `static` item - --> $DIR/typeck_type_placeholder_item.rs:71:12 + --> $DIR/typeck_type_placeholder_item.rs:72:12 | LL | static A = 42; | ^ help: provide a type for the item: `A: i32` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:73:15 + --> $DIR/typeck_type_placeholder_item.rs:74:15 | LL | static B: _ = 42; | ^ @@ -195,13 +204,13 @@ LL | static B: _ = 42; | help: replace `_` with the correct type: `i32` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:75:15 + --> $DIR/typeck_type_placeholder_item.rs:76:15 | LL | static C: Option<_> = Some(42); | ^^^^^^^^^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:78:21 + --> $DIR/typeck_type_placeholder_item.rs:79:21 | LL | fn fn_test() -> _ { 5 } | ^ @@ -210,7 +219,7 @@ LL | fn fn_test() -> _ { 5 } | help: replace with the correct return type: `i32` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:81:23 + --> $DIR/typeck_type_placeholder_item.rs:82:23 | LL | fn fn_test2() -> (_, _) { (5, 5) } | -^--^- @@ -220,7 +229,7 @@ LL | fn fn_test2() -> (_, _) { (5, 5) } | help: replace with the correct return type: `(i32, i32)` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:84:22 + --> $DIR/typeck_type_placeholder_item.rs:85:22 | LL | static FN_TEST3: _ = "test"; | ^ @@ -229,7 +238,7 @@ LL | static FN_TEST3: _ = "test"; | help: replace `_` with the correct type: `&str` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:87:22 + --> $DIR/typeck_type_placeholder_item.rs:88:22 | LL | static FN_TEST4: _ = 145; | ^ @@ -238,13 +247,13 @@ LL | static FN_TEST4: _ = 145; | help: replace `_` with the correct type: `i32` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:90:22 + --> $DIR/typeck_type_placeholder_item.rs:91:22 | LL | static FN_TEST5: (_, _) = (1, 2); | ^^^^^^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:93:20 + --> $DIR/typeck_type_placeholder_item.rs:94:20 | LL | fn fn_test6(_: _) { } | ^ not allowed in type signatures @@ -255,7 +264,7 @@ LL | fn fn_test6(_: T) { } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:96:20 + --> $DIR/typeck_type_placeholder_item.rs:97:20 | LL | fn fn_test7(x: _) { let _x: usize = x; } | ^ not allowed in type signatures @@ -266,7 +275,16 @@ LL | fn fn_test7(x: T) { let _x: usize = x; } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:99:29 + --> $DIR/typeck_type_placeholder_item.rs:100:29 + | +LL | fn fn_test8(_f: fn() -> _) { } + | ^ + | | + | not allowed in type signatures + | help: use type parameters instead: `T` + +error[E0121]: the type placeholder `_` is not allowed within types on item signatures + --> $DIR/typeck_type_placeholder_item.rs:100:29 | LL | fn fn_test8(_f: fn() -> _) { } | ^ not allowed in type signatures @@ -277,7 +295,7 @@ LL | fn fn_test8(_f: fn() -> T) { } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:121:12 + --> $DIR/typeck_type_placeholder_item.rs:123:12 | LL | a: _, | ^ not allowed in type signatures @@ -296,13 +314,13 @@ LL | b: (T, T), | error[E0282]: type annotations needed - --> $DIR/typeck_type_placeholder_item.rs:126:18 + --> $DIR/typeck_type_placeholder_item.rs:128:18 | LL | fn fn_test11(_: _) -> (_, _) { panic!() } | ^ cannot infer type error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:126:28 + --> $DIR/typeck_type_placeholder_item.rs:128:28 | LL | fn fn_test11(_: _) -> (_, _) { panic!() } | ^ ^ not allowed in type signatures @@ -310,7 +328,7 @@ LL | fn fn_test11(_: _) -> (_, _) { panic!() } | not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:130:30 + --> $DIR/typeck_type_placeholder_item.rs:132:30 | LL | fn fn_test12(x: i32) -> (_, _) { (x, x) } | -^--^- @@ -320,7 +338,7 @@ LL | fn fn_test12(x: i32) -> (_, _) { (x, x) } | help: replace with the correct return type: `(i32, i32)` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:133:33 + --> $DIR/typeck_type_placeholder_item.rs:135:33 | LL | fn fn_test13(x: _) -> (i32, _) { (x, x) } | ------^- @@ -329,7 +347,7 @@ LL | fn fn_test13(x: _) -> (i32, _) { (x, x) } | help: replace with the correct return type: `(i32, i32)` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:152:21 + --> $DIR/typeck_type_placeholder_item.rs:154:21 | LL | struct BadStruct<_>(_); | ^ not allowed in type signatures @@ -340,7 +358,7 @@ LL | struct BadStruct(T); | ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:157:15 + --> $DIR/typeck_type_placeholder_item.rs:159:15 | LL | impl BadTrait<_> for BadStruct<_> {} | ^ ^ not allowed in type signatures @@ -353,13 +371,13 @@ LL | impl BadTrait for BadStruct {} | ^^^ ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:160:34 + --> $DIR/typeck_type_placeholder_item.rs:162:34 | LL | fn impl_trait() -> impl BadTrait<_> { | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:165:25 + --> $DIR/typeck_type_placeholder_item.rs:167:25 | LL | struct BadStruct1<_, _>(_); | ^ not allowed in type signatures @@ -370,7 +388,7 @@ LL | struct BadStruct1(T); | ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:170:25 + --> $DIR/typeck_type_placeholder_item.rs:172:25 | LL | struct BadStruct2<_, T>(_, T); | ^ not allowed in type signatures @@ -381,13 +399,13 @@ LL | struct BadStruct2(U, T); | ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:174:14 + --> $DIR/typeck_type_placeholder_item.rs:176:14 | LL | type X = Box<_>; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:42:27 + --> $DIR/typeck_type_placeholder_item.rs:43:27 | LL | fn test10(&self, _x : _) { } | ^ not allowed in type signatures @@ -398,7 +416,7 @@ LL | fn test10(&self, _x : T) { } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:138:31 + --> $DIR/typeck_type_placeholder_item.rs:140:31 | LL | fn method_test1(&self, x: _); | ^ not allowed in type signatures @@ -409,7 +427,7 @@ LL | fn method_test1(&self, x: T); | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:140:31 + --> $DIR/typeck_type_placeholder_item.rs:142:31 | LL | fn method_test2(&self, x: _) -> _; | ^ ^ not allowed in type signatures @@ -422,7 +440,7 @@ LL | fn method_test2(&self, x: T) -> T; | ^^^ ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:142:31 + --> $DIR/typeck_type_placeholder_item.rs:144:31 | LL | fn method_test3(&self) -> _; | ^ not allowed in type signatures @@ -433,7 +451,7 @@ LL | fn method_test3(&self) -> T; | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:144:26 + --> $DIR/typeck_type_placeholder_item.rs:146:26 | LL | fn assoc_fn_test1(x: _); | ^ not allowed in type signatures @@ -444,7 +462,7 @@ LL | fn assoc_fn_test1(x: T); | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:146:26 + --> $DIR/typeck_type_placeholder_item.rs:148:26 | LL | fn assoc_fn_test2(x: _) -> _; | ^ ^ not allowed in type signatures @@ -457,7 +475,7 @@ LL | fn assoc_fn_test2(x: T) -> T; | ^^^ ^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:148:28 + --> $DIR/typeck_type_placeholder_item.rs:150:28 | LL | fn assoc_fn_test3() -> _; | ^ not allowed in type signatures @@ -468,7 +486,7 @@ LL | fn assoc_fn_test3() -> T; | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:60:37 + --> $DIR/typeck_type_placeholder_item.rs:61:37 | LL | fn clone_from(&mut self, other: _) { *self = Test9; } | ^ not allowed in type signatures @@ -479,7 +497,7 @@ LL | fn clone_from(&mut self, other: T) { *self = Test9; } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:108:34 + --> $DIR/typeck_type_placeholder_item.rs:110:34 | LL | fn fn_test10(&self, _x : _) { } | ^ not allowed in type signatures @@ -490,7 +508,7 @@ LL | fn fn_test10(&self, _x : T) { } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:116:41 + --> $DIR/typeck_type_placeholder_item.rs:118:41 | LL | fn clone_from(&mut self, other: _) { *self = FnTest9; } | ^ not allowed in type signatures @@ -501,25 +519,25 @@ LL | fn clone_from(&mut self, other: T) { *self = FnTest9; } | ^^^ ^ error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:180:21 + --> $DIR/typeck_type_placeholder_item.rs:182:21 | LL | type Y = impl Trait<_>; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:188:14 + --> $DIR/typeck_type_placeholder_item.rs:190:14 | LL | type B = _; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:190:14 + --> $DIR/typeck_type_placeholder_item.rs:192:14 | LL | const C: _; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:192:14 + --> $DIR/typeck_type_placeholder_item.rs:194:14 | LL | const D: _ = 42; | ^ @@ -528,7 +546,7 @@ LL | const D: _ = 42; | help: replace `_` with the correct type: `i32` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:39:24 + --> $DIR/typeck_type_placeholder_item.rs:40:24 | LL | fn test9(&self) -> _ { () } | ^ @@ -537,7 +555,7 @@ LL | fn test9(&self) -> _ { () } | help: replace with the correct return type: `()` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:57:24 + --> $DIR/typeck_type_placeholder_item.rs:58:24 | LL | fn clone(&self) -> _ { Test9 } | ^ @@ -546,7 +564,7 @@ LL | fn clone(&self) -> _ { Test9 } | help: replace with the correct return type: `Test9` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:105:31 + --> $DIR/typeck_type_placeholder_item.rs:107:31 | LL | fn fn_test9(&self) -> _ { () } | ^ @@ -555,7 +573,7 @@ LL | fn fn_test9(&self) -> _ { () } | help: replace with the correct return type: `()` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:113:28 + --> $DIR/typeck_type_placeholder_item.rs:115:28 | LL | fn clone(&self) -> _ { FnTest9 } | ^ @@ -564,25 +582,25 @@ LL | fn clone(&self) -> _ { FnTest9 } | help: replace with the correct return type: `main::FnTest9` error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:197:14 + --> $DIR/typeck_type_placeholder_item.rs:199:14 | LL | type A = _; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:199:14 + --> $DIR/typeck_type_placeholder_item.rs:201:14 | LL | type B = _; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:201:14 + --> $DIR/typeck_type_placeholder_item.rs:203:14 | LL | const C: _; | ^ not allowed in type signatures error[E0121]: the type placeholder `_` is not allowed within types on item signatures - --> $DIR/typeck_type_placeholder_item.rs:204:14 + --> $DIR/typeck_type_placeholder_item.rs:206:14 | LL | const D: _ = 42; | ^ @@ -590,7 +608,7 @@ LL | const D: _ = 42; | not allowed in type signatures | help: replace `_` with the correct type: `i32` -error: aborting due to 64 previous errors +error: aborting due to 66 previous errors Some errors have detailed explanations: E0121, E0282, E0403. For more information about an error, try `rustc --explain E0121`. From 5daedea3dbeb8fb2639d3d142b008135f4fd2b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 8 Jul 2020 19:39:26 -0700 Subject: [PATCH 52/62] Detect tuple struct incorrectly used as struct pat --- Cargo.lock | 1 + src/librustc_error_codes/error_codes.rs | 1 + src/librustc_error_codes/error_codes/E0769.md | 39 ++++++++ src/librustc_typeck/Cargo.toml | 1 + src/librustc_typeck/check/pat.rs | 91 ++++++++++++++++--- .../missing-fields-in-struct-pattern.rs | 3 +- .../missing-fields-in-struct-pattern.stderr | 17 +--- src/test/ui/type/type-check/issue-41314.rs | 4 +- .../ui/type/type-check/issue-41314.stderr | 17 +--- src/test/ui/union/union-fields-2.stderr | 12 +-- 10 files changed, 137 insertions(+), 49 deletions(-) create mode 100644 src/librustc_error_codes/error_codes/E0769.md diff --git a/Cargo.lock b/Cargo.lock index 2096a3dfff9ea..61a0b5caebec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3996,6 +3996,7 @@ dependencies = [ "rustc_data_structures", "rustc_errors", "rustc_hir", + "rustc_hir_pretty", "rustc_index", "rustc_infer", "rustc_middle", diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs index f687221d78e03..2e1b897216bb2 100644 --- a/src/librustc_error_codes/error_codes.rs +++ b/src/librustc_error_codes/error_codes.rs @@ -450,6 +450,7 @@ E0765: include_str!("./error_codes/E0765.md"), E0766: include_str!("./error_codes/E0766.md"), E0767: include_str!("./error_codes/E0767.md"), E0768: include_str!("./error_codes/E0768.md"), +E0769: include_str!("./error_codes/E0769.md"), ; // E0006, // merged with E0005 // E0008, // cannot bind by-move into a pattern guard diff --git a/src/librustc_error_codes/error_codes/E0769.md b/src/librustc_error_codes/error_codes/E0769.md new file mode 100644 index 0000000000000..d1995be9899b1 --- /dev/null +++ b/src/librustc_error_codes/error_codes/E0769.md @@ -0,0 +1,39 @@ +A tuple struct or tuple variant was used in a pattern as if it were a +struct or struct variant. + +Erroneous code example: + +```compile_fail,E0769 +enum E { + A(i32), +} +let e = E::A(42); +match e { + E::A { number } => println!("{}", x), +} +``` + +To fix this error, you can use the tuple pattern: + +``` +# enum E { +# A(i32), +# } +# let e = E::A(42); +match e { + E::A(number) => println!("{}", number), +} +``` + +Alternatively, you can also use the struct pattern by using the correct +field names and binding them to new identifiers: + +``` +# enum E { +# A(i32), +# } +# let e = E::A(42); +match e { + E::A { 0: number } => println!("{}", number), +} +``` diff --git a/src/librustc_typeck/Cargo.toml b/src/librustc_typeck/Cargo.toml index 9329069c48dd1..93b503c976be4 100644 --- a/src/librustc_typeck/Cargo.toml +++ b/src/librustc_typeck/Cargo.toml @@ -18,6 +18,7 @@ rustc_attr = { path = "../librustc_attr" } rustc_data_structures = { path = "../librustc_data_structures" } rustc_errors = { path = "../librustc_errors" } rustc_hir = { path = "../librustc_hir" } +rustc_hir_pretty = { path = "../librustc_hir_pretty" } rustc_target = { path = "../librustc_target" } rustc_session = { path = "../librustc_session" } smallvec = { version = "1.0", features = ["union", "may_dangle"] } diff --git a/src/librustc_typeck/check/pat.rs b/src/librustc_typeck/check/pat.rs index ea47ae68ce7d3..a654fc3dfc2df 100644 --- a/src/librustc_typeck/check/pat.rs +++ b/src/librustc_typeck/check/pat.rs @@ -1082,20 +1082,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .filter(|ident| !used_fields.contains_key(&ident)) .collect::>(); - if !inexistent_fields.is_empty() && !variant.recovered { - self.error_inexistent_fields( + let inexistent_fields_err = if !inexistent_fields.is_empty() && !variant.recovered { + Some(self.error_inexistent_fields( adt.variant_descr(), &inexistent_fields, &mut unmentioned_fields, variant, - ); - } + )) + } else { + None + }; // Require `..` if struct has non_exhaustive attribute. if variant.is_field_list_non_exhaustive() && !adt.did.is_local() && !etc { self.error_foreign_non_exhaustive_spat(pat, adt.variant_descr(), fields.is_empty()); } + let mut unmentioned_err = None; // Report an error if incorrect number of the fields were specified. if adt.is_union() { if fields.len() != 1 { @@ -1107,7 +1110,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit(); } } else if !etc && !unmentioned_fields.is_empty() { - self.error_unmentioned_fields(pat.span, &unmentioned_fields, variant); + unmentioned_err = Some(self.error_unmentioned_fields(pat.span, &unmentioned_fields)); + } + match (inexistent_fields_err, unmentioned_err) { + (Some(mut i), Some(mut u)) => { + if let Some(mut e) = self.error_tuple_variant_as_struct_pat(pat, fields, variant) { + // We don't want to show the inexistent fields error when this was + // `Foo { a, b }` when it should have been `Foo(a, b)`. + i.delay_as_bug(); + u.delay_as_bug(); + e.emit(); + } else { + i.emit(); + u.emit(); + } + } + (None, Some(mut err)) | (Some(mut err), None) => { + err.emit(); + } + (None, None) => {} } no_field_errors } @@ -1154,7 +1175,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { inexistent_fields: &[Ident], unmentioned_fields: &mut Vec, variant: &ty::VariantDef, - ) { + ) -> DiagnosticBuilder<'tcx> { let tcx = self.tcx; let (field_names, t, plural) = if inexistent_fields.len() == 1 { (format!("a field named `{}`", inexistent_fields[0]), "this", "") @@ -1221,15 +1242,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { it explicitly.", ); } - err.emit(); + err + } + + fn error_tuple_variant_as_struct_pat( + &self, + pat: &Pat<'_>, + fields: &'tcx [hir::FieldPat<'tcx>], + variant: &ty::VariantDef, + ) -> Option> { + if let (CtorKind::Fn, PatKind::Struct(qpath, ..)) = (variant.ctor_kind, &pat.kind) { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(qpath, false) + }); + let mut err = struct_span_err!( + self.tcx.sess, + pat.span, + E0769, + "tuple variant `{}` written as struct variant", + path + ); + let (sugg, appl) = if fields.len() == variant.fields.len() { + ( + fields + .iter() + .map(|f| match self.tcx.sess.source_map().span_to_snippet(f.pat.span) { + Ok(f) => f, + Err(_) => rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_pat(f.pat) + }), + }) + .collect::>() + .join(", "), + Applicability::MachineApplicable, + ) + } else { + ( + variant.fields.iter().map(|_| "_").collect::>().join(", "), + Applicability::MaybeIncorrect, + ) + }; + err.span_suggestion( + pat.span, + "use the tuple variant pattern syntax instead", + format!("{}({})", path, sugg), + appl, + ); + return Some(err); + } + None } fn error_unmentioned_fields( &self, span: Span, unmentioned_fields: &[Ident], - variant: &ty::VariantDef, - ) { + ) -> DiagnosticBuilder<'tcx> { let field_names = if unmentioned_fields.len() == 1 { format!("field `{}`", unmentioned_fields[0]) } else { @@ -1248,9 +1316,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { field_names ); diag.span_label(span, format!("missing {}", field_names)); - if variant.ctor_kind == CtorKind::Fn { - diag.note("trying to match a tuple variant with a struct variant pattern"); - } if self.tcx.sess.teach(&diag.get_code().unwrap()) { diag.note( "This error indicates that a pattern for a struct fails to specify a \ @@ -1259,7 +1324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ignore unwanted fields.", ); } - diag.emit(); + diag } fn check_pat_box( diff --git a/src/test/ui/missing/missing-fields-in-struct-pattern.rs b/src/test/ui/missing/missing-fields-in-struct-pattern.rs index 24b6b55db6692..40304a674a633 100644 --- a/src/test/ui/missing/missing-fields-in-struct-pattern.rs +++ b/src/test/ui/missing/missing-fields-in-struct-pattern.rs @@ -2,8 +2,7 @@ struct S(usize, usize, usize, usize); fn main() { if let S { a, b, c, d } = S(1, 2, 3, 4) { - //~^ ERROR struct `S` does not have fields named `a`, `b`, `c`, `d` [E0026] - //~| ERROR pattern does not mention fields `0`, `1`, `2`, `3` [E0027] + //~^ ERROR tuple variant `S` written as struct variant println!("hi"); } } diff --git a/src/test/ui/missing/missing-fields-in-struct-pattern.stderr b/src/test/ui/missing/missing-fields-in-struct-pattern.stderr index f7037468996f4..6583524aad18f 100644 --- a/src/test/ui/missing/missing-fields-in-struct-pattern.stderr +++ b/src/test/ui/missing/missing-fields-in-struct-pattern.stderr @@ -1,18 +1,9 @@ -error[E0026]: struct `S` does not have fields named `a`, `b`, `c`, `d` - --> $DIR/missing-fields-in-struct-pattern.rs:4:16 - | -LL | if let S { a, b, c, d } = S(1, 2, 3, 4) { - | ^ ^ ^ ^ struct `S` does not have these fields - -error[E0027]: pattern does not mention fields `0`, `1`, `2`, `3` +error[E0769]: tuple variant `S` written as struct variant --> $DIR/missing-fields-in-struct-pattern.rs:4:12 | LL | if let S { a, b, c, d } = S(1, 2, 3, 4) { - | ^^^^^^^^^^^^^^^^ missing fields `0`, `1`, `2`, `3` - | - = note: trying to match a tuple variant with a struct variant pattern + | ^^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `S(a, b, c, d)` -error: aborting due to 2 previous errors +error: aborting due to previous error -Some errors have detailed explanations: E0026, E0027. -For more information about an error, try `rustc --explain E0026`. +For more information about this error, try `rustc --explain E0769`. diff --git a/src/test/ui/type/type-check/issue-41314.rs b/src/test/ui/type/type-check/issue-41314.rs index 856d4ff6334bc..cbd39f5f9e6ed 100644 --- a/src/test/ui/type/type-check/issue-41314.rs +++ b/src/test/ui/type/type-check/issue-41314.rs @@ -4,7 +4,7 @@ enum X { fn main() { match X::Y(0) { - X::Y { number } => {} //~ ERROR does not have a field named `number` - //~^ ERROR pattern does not mention field `0` + X::Y { number } => {} + //~^ ERROR tuple variant `X::Y` written as struct variant } } diff --git a/src/test/ui/type/type-check/issue-41314.stderr b/src/test/ui/type/type-check/issue-41314.stderr index c2bba98d10a83..bd4d2071c2059 100644 --- a/src/test/ui/type/type-check/issue-41314.stderr +++ b/src/test/ui/type/type-check/issue-41314.stderr @@ -1,18 +1,9 @@ -error[E0026]: variant `X::Y` does not have a field named `number` - --> $DIR/issue-41314.rs:7:16 - | -LL | X::Y { number } => {} - | ^^^^^^ variant `X::Y` does not have this field - -error[E0027]: pattern does not mention field `0` +error[E0769]: tuple variant `X::Y` written as struct variant --> $DIR/issue-41314.rs:7:9 | LL | X::Y { number } => {} - | ^^^^^^^^^^^^^^^ missing field `0` - | - = note: trying to match a tuple variant with a struct variant pattern + | ^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `X::Y(number)` -error: aborting due to 2 previous errors +error: aborting due to previous error -Some errors have detailed explanations: E0026, E0027. -For more information about an error, try `rustc --explain E0026`. +For more information about this error, try `rustc --explain E0769`. diff --git a/src/test/ui/union/union-fields-2.stderr b/src/test/ui/union/union-fields-2.stderr index 68cb66d89d218..48654347285d3 100644 --- a/src/test/ui/union/union-fields-2.stderr +++ b/src/test/ui/union/union-fields-2.stderr @@ -48,18 +48,18 @@ error: union patterns should have exactly one field LL | let U { a, b } = u; | ^^^^^^^^^^ -error[E0026]: union `U` does not have a field named `c` - --> $DIR/union-fields-2.rs:18:19 - | -LL | let U { a, b, c } = u; - | ^ union `U` does not have this field - error: union patterns should have exactly one field --> $DIR/union-fields-2.rs:18:9 | LL | let U { a, b, c } = u; | ^^^^^^^^^^^^^ +error[E0026]: union `U` does not have a field named `c` + --> $DIR/union-fields-2.rs:18:19 + | +LL | let U { a, b, c } = u; + | ^ union `U` does not have this field + error: union patterns should have exactly one field --> $DIR/union-fields-2.rs:20:9 | From ffac887908916e99795d0e33e24dea94bce70bc8 Mon Sep 17 00:00:00 2001 From: Erin Power Date: Sun, 14 Jun 2020 20:59:40 +0200 Subject: [PATCH 53/62] Update RELEASES.md for 1.45.0 --- RELEASES.md | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 006682f505936..977796c66132e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,166 @@ +Version 1.45.0 (2020-07-16) +========================== + +Language +-------- +- [Out of range float to int conversions using `as` has been defined as a saturating + conversion.][71269] This was previously undefined behaviour, but you can use the + `{f64, f32}::to_int_unchecked` methods to continue using the current behaviour, which + may be desirable in rare performance sensitive situations. +- [`mem::Discriminant` now uses `T`'s discriminant type instead of always + using `u64`.][70705] +- [Function like procedural macros can now be used in expression, pattern, and statement + positions.][68717] This means you can now use a function-like procedural macro + anywhere you can use a declarative (`macro_rules!`) macro. + +Compiler +-------- +- [You can now override individual target features through the `target-feature` + flag.][72094] E.g. `-C target-feature=+avx2 -C target-feature=+fma` is now + equivalent to `-C target-feature=+avx2,+fma`. +- [Added the `force-unwind-tables` flag.][69984] This option allows + rustc to always generate unwind tables regardless of panic strategy. +- [Added the `embed-bitcode` flag.][71716] This codegen flag allows rustc + to include LLVM bitcode into generated `rlib`s (this is on by default). +- [Added the `tiny` value to the `code-model` codegen flag.][72397] +- [Added tier 3 support\* for the `mipsel-sony-psp` target.][72062] +- [Added tier 3 support for the `thumbv7a-uwp-windows-msvc` target.][72133] + +\* Refer to Rust's [platform support page][forge-platform-support] for more +information on Rust's tiered platform support. + + +Libraries +--------- +- [`net::{SocketAddr, SocketAddrV4, SocketAddrV6}` now implements `PartialOrd` + and `Ord`.][72239] +- [`proc_macro::TokenStream` now implements `Default`.][72234] +- [You can now use `char` with + `ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}` to iterate over + a range of codepoints.][72413] E.g. + you can now write the following; + ```rust + for ch in 'a'..='z' { + print!("{}", ch); + } + println!(); + // Prints "abcdefghijklmnopqrstuvwxyz" + ``` +- [`OsString` now implements `FromStr`.][71662] +- [The `saturating_neg` method as been added to all signed integer primitive + types, and the `saturating_abs` method has been added for all integer + primitive types.][71886] +- [`Arc`, `Rc` now implement `From>`, and `Box` now + implements `From` when `T` is `[T: Copy]`, `str`, `CStr`, `OsStr`, + or `Path`.][71447] +- [`Box<[T]>` now implements `From<[T; N]>`.][71095] +- [`BitOr` and `BitOrAssign` are implemented for all `NonZero` + integer types.][69813] +- [The `fetch_min`, and `fetch_max` methods have been added to all atomic + integer types.][72324] +- [The `fetch_update` method has been added to all atomic integer types.][71843] + +Stabilized APIs +--------------- +- [`Arc::as_ptr`] +- [`BTreeMap::remove_entry`] +- [`Rc::as_ptr`] +- [`rc::Weak::as_ptr`] +- [`rc::Weak::from_raw`] +- [`rc::Weak::into_raw`] +- [`str::strip_prefix`] +- [`str::strip_suffix`] +- [`sync::Weak::as_ptr`] +- [`sync::Weak::from_raw`] +- [`sync::Weak::into_raw`] +- [`char::UNICODE_VERSION`] +- [`Span::resolved_at`] +- [`Span::located_at`] +- [`Span::mixed_site`] +- [`unix::process::CommandExt::arg0`] + +Cargo +----- + +Misc +---- +- [Rustdoc now supports strikethrough text in Markdown.][71928] E.g. + `~~outdated information~~` becomes "~~outdated information~~". +- [Added an emoji to Rustdoc's deprecated API message.][72014] + +Compatibility Notes +------------------- +- [Trying to self initialize a static value (that is creating a value using + itself) is unsound and now causes a compile error.][71140] +- [`{f32, f64}::powi` now returns a slightly different value on Windows.][73420] + This is due to changes in LLVM's intrinsics which `{f32, f64}::powi` uses. +- [Rustdoc's CLI's extra error exit codes have been removed.][71900] These were + previously undocumented and not intended for public use. Rustdoc still provides + a non-zero exit code on errors. + +Internals Only +-------------- +- [Make clippy a git subtree instead of a git submodule][70655] +- [Unify the undo log of all snapshot types][69464] + +[73420]: https://github.com/rust-lang/rust/issues/73420/ +[72324]: https://github.com/rust-lang/rust/pull/72324/ +[71843]: https://github.com/rust-lang/rust/pull/71843/ +[71886]: https://github.com/rust-lang/rust/pull/71886/ +[72234]: https://github.com/rust-lang/rust/pull/72234/ +[72239]: https://github.com/rust-lang/rust/pull/72239/ +[72397]: https://github.com/rust-lang/rust/pull/72397/ +[72413]: https://github.com/rust-lang/rust/pull/72413/ +[72014]: https://github.com/rust-lang/rust/pull/72014/ +[72062]: https://github.com/rust-lang/rust/pull/72062/ +[72094]: https://github.com/rust-lang/rust/pull/72094/ +[72133]: https://github.com/rust-lang/rust/pull/72133/ +[71900]: https://github.com/rust-lang/rust/pull/71900/ +[71928]: https://github.com/rust-lang/rust/pull/71928/ +[71662]: https://github.com/rust-lang/rust/pull/71662/ +[71716]: https://github.com/rust-lang/rust/pull/71716/ +[71447]: https://github.com/rust-lang/rust/pull/71447/ +[71269]: https://github.com/rust-lang/rust/pull/71269/ +[71095]: https://github.com/rust-lang/rust/pull/71095/ +[71140]: https://github.com/rust-lang/rust/pull/71140/ +[70655]: https://github.com/rust-lang/rust/pull/70655/ +[70705]: https://github.com/rust-lang/rust/pull/70705/ +[69984]: https://github.com/rust-lang/rust/pull/69984/ +[69813]: https://github.com/rust-lang/rust/pull/69813/ +[69464]: https://github.com/rust-lang/rust/pull/69464/ +[68717]: https://github.com/rust-lang/rust/pull/68717/ +[`Arc::as_ptr`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.as_ptr +[`BTreeMap::remove_entry`]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html#method.remove_entry +[`Rc::as_ptr`]: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.as_ptr +[`rc::Weak::as_ptr`]: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.as_ptr +[`rc::Weak::from_raw`]: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.from_raw +[`rc::Weak::into_raw`]: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.into_raw +[`sync::Weak::as_ptr`]: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.as_ptr +[`sync::Weak::from_raw`]: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.from_raw +[`sync::Weak::into_raw`]: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.into_raw +[`str::strip_prefix`]: https://doc.rust-lang.org/stable/std/primitive.str.html#method.strip_prefix +[`str::strip_suffix`]: https://doc.rust-lang.org/stable/std/primitive.str.html#method.strip_suffix +[`char::UNICODE_VERSION`]: https://doc.rust-lang.org/stable/std/char/constant.UNICODE_VERSION.html +[`Span::resolved_at`]: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.resolved_at +[`Span::located_at`]: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.located_at +[`Span::mixed_site`]: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.mixed_site +[`unix::process::CommandExt::arg0`]: https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#tymethod.arg0 + + +Version 1.44.1 (2020-06-18) +=========================== + +* [rustfmt accepts rustfmt_skip in cfg_attr again.][73078] +* [Don't hash executable filenames on apple platforms, fixing backtraces.][cargo/8329] +* [Fix crashes when finding backtrace on macOS.][71397] +* [Clippy applies lint levels into different files.][clippy/5356] + +[71397]: https://github.com/rust-lang/rust/issues/71397 +[73078]: https://github.com/rust-lang/rust/issues/73078 +[cargo/8329]: https://github.com/rust-lang/cargo/pull/8329 +[clippy/5356]: https://github.com/rust-lang/rust-clippy/issues/5356 + + Version 1.44.0 (2020-06-04) ========================== From 0e89f50f6e937349233db1c52af2b33c683782e6 Mon Sep 17 00:00:00 2001 From: Jarek Samic Date: Sun, 12 Jul 2020 14:37:22 -0400 Subject: [PATCH 54/62] Clean up handling of style files in rustdoc Disable all themes other than `light.css` to prevent rule conflicts --- src/librustdoc/config.rs | 5 +- src/librustdoc/html/layout.rs | 25 +++++----- src/librustdoc/html/render.rs | 87 ++++++++++++++++++++++------------ src/librustdoc/html/sources.rs | 2 +- 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 14a6f3c89a3c9..39e33da44964e 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -20,6 +20,7 @@ use crate::core::new_handler; use crate::externalfiles::ExternalHtml; use crate::html; use crate::html::markdown::IdMap; +use crate::html::render::StylePath; use crate::html::static_files; use crate::opts; use crate::passes::{self, Condition, DefaultPassOption}; @@ -207,7 +208,7 @@ pub struct RenderOptions { pub sort_modules_alphabetically: bool, /// List of themes to extend the docs with. Original argument name is included to assist in /// displaying errors if it fails a theme check. - pub themes: Vec, + pub themes: Vec, /// If present, CSS file that contains rules to add to the default CSS. pub extension_css: Option, /// A map of crate names to the URL to use instead of querying the crate's `html_root_url`. @@ -410,7 +411,7 @@ impl Options { )) .emit(); } - themes.push(theme_file); + themes.push(StylePath { path: theme_file, disabled: true }); } } diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index ea65b3905272e..cc6b38ebcdb7f 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use crate::externalfiles::ExternalHtml; use crate::html::escape::Escape; use crate::html::format::{Buffer, Print}; -use crate::html::render::ensure_trailing_slash; +use crate::html::render::{ensure_trailing_slash, StylePath}; #[derive(Clone)] pub struct Layout { @@ -36,7 +36,7 @@ pub fn render( page: &Page<'_>, sidebar: S, t: T, - themes: &[PathBuf], + style_files: &[StylePath], ) -> String { let static_root_path = page.static_root_path.unwrap_or(page.root_path); format!( @@ -52,10 +52,7 @@ pub fn render( \ \ - {themes}\ - \ - \ + {style_files}\ \ \ {css_extension}\ @@ -172,13 +169,19 @@ pub fn render( after_content = layout.external_html.after_content, sidebar = Buffer::html().to_display(sidebar), krate = layout.krate, - themes = themes + style_files = style_files .iter() - .filter_map(|t| t.file_stem()) - .filter_map(|t| t.to_str()) + .filter_map(|t| { + if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None } + }) + .filter_map(|t| { + if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None } + }) .map(|t| format!( - r#""#, - Escape(&format!("{}{}{}", static_root_path, t, page.resource_suffix)) + r#""#, + Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)), + if t.1 { "disabled" } else { "" }, + if t.0 == "light" { "id=\"themeStyle\"" } else { "" } )) .collect::(), suffix = page.resource_suffix, diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 8bba21a2e7ace..be14e686cf146 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -187,8 +187,8 @@ crate struct SharedContext { /// This flag indicates whether listings of modules (in the side bar and documentation itself) /// should be ordered alphabetically or in order of appearance (in the source code). pub sort_modules_alphabetically: bool, - /// Additional themes to be added to the generated docs. - pub themes: Vec, + /// Additional CSS files to be added to the generated docs. + pub style_files: Vec, /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes /// "light-v2.css"). pub resource_suffix: String, @@ -417,6 +417,14 @@ impl Serialize for TypeWithKind { } } +#[derive(Debug, Clone)] +pub struct StylePath { + /// The path to the theme + pub path: PathBuf, + /// What the `disabled` attribute should be set to in the HTML tag + pub disabled: bool, +} + thread_local!(static CACHE_KEY: RefCell> = Default::default()); thread_local!(pub static CURRENT_DEPTH: Cell = Cell::new(0)); @@ -460,7 +468,7 @@ pub fn run( id_map, playground_url, sort_modules_alphabetically, - themes, + themes: style_files, extension_css, extern_html_root_urls, resource_suffix, @@ -530,7 +538,7 @@ pub fn run( layout, created_dirs: Default::default(), sort_modules_alphabetically, - themes, + style_files, resource_suffix, static_root_path, fs: DocFS::new(&errors), @@ -539,6 +547,18 @@ pub fn run( playground, }; + // Add the default themes to the `Vec` of stylepaths + // + // Note that these must be added before `sources::render` is called + // so that the resulting source pages are styled + // + // `light.css` is not disabled because it is the stylesheet that stays loaded + // by the browser as the theme stylesheet. The theme system (hackily) works by + // changing the href to this stylesheet. All other themes are disabled to + // prevent rule conflicts + scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); + scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); + let dst = output; scx.ensure_dir(&dst)?; krate = sources::render(&dst, &mut scx, krate)?; @@ -615,11 +635,34 @@ fn write_shared( // then we'll run over the "official" styles. let mut themes: FxHashSet = FxHashSet::default(); - for entry in &cx.shared.themes { - let content = try_err!(fs::read(&entry), &entry); - let theme = try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry); - let extension = try_none!(try_none!(entry.extension(), &entry).to_str(), &entry); - cx.shared.fs.write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?; + for entry in &cx.shared.style_files { + let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path); + let extension = + try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); + + // Handle the official themes + match theme { + "light" => write_minify( + &cx.shared.fs, + cx.path("light.css"), + static_files::themes::LIGHT, + options.enable_minification, + )?, + "dark" => write_minify( + &cx.shared.fs, + cx.path("dark.css"), + static_files::themes::DARK, + options.enable_minification, + )?, + _ => { + // Handle added third-party themes + let content = try_err!(fs::read(&entry.path), &entry.path); + cx.shared + .fs + .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?; + } + }; + themes.insert(theme.to_owned()); } @@ -633,20 +676,6 @@ fn write_shared( write(cx.path("brush.svg"), static_files::BRUSH_SVG)?; write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?; write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?; - write_minify( - &cx.shared.fs, - cx.path("light.css"), - static_files::themes::LIGHT, - options.enable_minification, - )?; - themes.insert("light".to_owned()); - write_minify( - &cx.shared.fs, - cx.path("dark.css"), - static_files::themes::DARK, - options.enable_minification, - )?; - themes.insert("dark".to_owned()); let mut themes: Vec<&String> = themes.iter().collect(); themes.sort(); @@ -957,7 +986,7 @@ themePicker.onblur = handleThemeButtonsBlur; }) .collect::() ); - let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.themes); + let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files); cx.shared.fs.write(&dst, v.as_bytes())?; } } @@ -1375,7 +1404,7 @@ impl Context { &page, sidebar, |buf: &mut Buffer| all.print(buf), - &self.shared.themes, + &self.shared.style_files, ); self.shared.fs.write(&final_file, v.as_bytes())?; @@ -1384,9 +1413,9 @@ impl Context { page.description = "Settings of Rustdoc"; page.root_path = "./"; - let mut themes = self.shared.themes.clone(); + let mut style_files = self.shared.style_files.clone(); let sidebar = "

Settings

"; - themes.push(PathBuf::from("settings.css")); + style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false }); let v = layout::render( &self.shared.layout, &page, @@ -1395,7 +1424,7 @@ impl Context { self.shared.static_root_path.as_deref().unwrap_or("./"), &self.shared.resource_suffix, ), - &themes, + &style_files, ); self.shared.fs.write(&settings_file, v.as_bytes())?; @@ -1457,7 +1486,7 @@ impl Context { &page, |buf: &mut _| print_sidebar(self, it, buf), |buf: &mut _| print_item(self, it, buf), - &self.shared.themes, + &self.shared.style_files, ) } else { let mut url = self.root_path(); diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index f0900c34a4ba3..03f79b931868b 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -123,7 +123,7 @@ impl<'a> SourceCollector<'a> { &page, "", |buf: &mut _| print_src(buf, &contents), - &self.scx.themes, + &self.scx.style_files, ); self.scx.fs.write(&cur, v.as_bytes())?; self.scx.local_sources.insert(p, href); From 8c45cf8e60960698262363cf15aa650437f49a56 Mon Sep 17 00:00:00 2001 From: Jarek Samic Date: Sun, 12 Jul 2020 14:48:13 -0400 Subject: [PATCH 55/62] Add Ayu theme to rustdoc --- src/librustdoc/html/render.rs | 7 + src/librustdoc/html/static/themes/ayu.css | 561 ++++++++++++++++++++++ src/librustdoc/html/static_files.rs | 3 + 3 files changed, 571 insertions(+) create mode 100644 src/librustdoc/html/static/themes/ayu.css diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index be14e686cf146..456eb9fcbc41a 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -558,6 +558,7 @@ pub fn run( // prevent rule conflicts scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); + scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true }); let dst = output; scx.ensure_dir(&dst)?; @@ -654,6 +655,12 @@ fn write_shared( static_files::themes::DARK, options.enable_minification, )?, + "ayu" => write_minify( + &cx.shared.fs, + cx.path("ayu.css"), + static_files::themes::AYU, + options.enable_minification, + )?, _ => { // Handle added third-party themes let content = try_err!(fs::read(&entry.path), &entry.path); diff --git a/src/librustdoc/html/static/themes/ayu.css b/src/librustdoc/html/static/themes/ayu.css new file mode 100644 index 0000000000000..bc21c28750fd8 --- /dev/null +++ b/src/librustdoc/html/static/themes/ayu.css @@ -0,0 +1,561 @@ +/* +Based off of the Ayu theme +Original by Dempfi (https://github.com/dempfi/ayu) +*/ + +/* General structure and fonts */ + +body { + background-color: #0f1419; + color: #c5c5c5; +} + +h1, h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod) { + color: white; +} +h1.fqn { + border-bottom-color: #5c6773; +} +h1.fqn a { + color: #fff; +} +h2, h3:not(.impl):not(.method):not(.type):not(.tymethod) { + border-bottom-color: #5c6773; +} +h4:not(.method):not(.type):not(.tymethod):not(.associatedconstant) { + border: none; +} + +.in-band { + background-color: #0f1419; +} + +.invisible { + background: rgba(0, 0, 0, 0); +} + +code { + color: #ffb454; +} +h3 > code, h4 > code, h5 > code { + color: #e6e1cf; +} +pre > code { + color: #e6e1cf; +} +span code { + color: #e6e1cf; +} +.docblock a > code { + color: #39AFD7 !important; +} +.docblock code, .docblock-short code { + background-color: #191f26; +} +pre { + color: #e6e1cf; + background-color: #191f26; +} + +.sidebar { + background-color: #14191f; +} + +/* Improve the scrollbar display on firefox */ +* { + scrollbar-color: #5c6773 transparent; +} + +.sidebar { + scrollbar-color: #5c6773 transparent; +} + +/* Improve the scrollbar display on webkit-based browsers */ +::-webkit-scrollbar-track { + background-color: transparent; +} +::-webkit-scrollbar-thumb { + background-color: #5c6773; +} +.sidebar::-webkit-scrollbar-track { + background-color: transparent; +} +.sidebar::-webkit-scrollbar-thumb { + background-color: #5c6773; +} + +.sidebar .current { + background-color: transparent; + color: #ffb44c; +} + +.source .sidebar { + background-color: #0f1419; +} + +.sidebar .location { + border-color: #000; + background-color: #0f1419; + color: #fff; +} + +.sidebar-elems .location { + color: #ff7733; +} + +.sidebar-elems .location a { + color: #fff; +} + +.sidebar .version { + border-bottom-color: #DDD; +} + +.sidebar-title { + border-top-color: #5c6773; + border-bottom-color: #5c6773; +} + +.block a:hover { + background: transparent; + color: #ffb44c; +} + +.line-numbers span { color: #5c6773ab; } +.line-numbers .line-highlighted { + background-color: rgba(255, 236, 164, 0.06) !important; + padding-right: 4px; + border-right: 1px solid #ffb44c; +} + +.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 { + border-bottom-color: #5c6773; +} + +.docblock table, .docblock table td, .docblock table th { + border-color: #5c6773; +} + +.content .method .where, +.content .fn .where, +.content .where.fmt-newline { + color: #c5c5c5; +} + +.content .highlighted { + color: #000 !important; + background-color: #c6afb3; +} +.content .highlighted a, .content .highlighted span { color: #000 !important; } +.content .highlighted { + background-color: #c6afb3; +} +.search-results a { + color: #0096cf; +} +.search-results a span.desc { + color: #c5c5c5; +} + +.content .stability::before { color: #ccc; } + +.content span.foreigntype, .content a.foreigntype { color: #ef57ff; } +.content span.union, .content a.union { color: #98a01c; } +.content span.constant, .content a.constant, +.content span.static, .content a.static { color: #6380a0; } +.content span.primitive, .content a.primitive { color: #32889b; } +.content span.traitalias, .content a.traitalias { color: #57d399; } +.content span.keyword, .content a.keyword { color: #de5249; } + +.content span.externcrate, .content span.mod, .content a.mod { + color: #acccf9; +} +.content span.struct, .content a.struct { + color: #ffa0a5; +} +.content span.enum, .content a.enum { + color: #99e0c9; +} +.content span.trait, .content a.trait { + color: #39AFD7; +} +.content span.type, .content a.type { + color: #cfbcf5; +} +.content span.fn, .content a.fn, .content span.method, +.content a.method, .content span.tymethod, +.content a.tymethod, .content .fnname { + color: #fdd687; +} +.content span.attr, .content a.attr, .content span.derive, +.content a.derive, .content span.macro, .content a.macro { + color: #a37acc; +} + +pre.rust .comment, pre.rust .doccomment { + color: #788797; + font-style: italic; +} + +nav:not(.sidebar) { + border-bottom-color: #e0e0e0; +} +nav.main .current { + border-top-color: #5c6773; + border-bottom-color: #5c6773; +} +nav.main .separator { + border: 1px solid #5c6773; +} +a { + color: #c5c5c5; +} + +.docblock:not(.type-decl) a:not(.srclink):not(.test-arrow), +.docblock-short a:not(.srclink):not(.test-arrow), .stability a { + color: #39AFD7; +} + +.stab.internal a { + color: #304FFE; +} + +.collapse-toggle { + color: #999; +} + +#crate-search { + color: #c5c5c5; + background-color: #141920; + border-radius: 4px; + box-shadow: none; + border-color: #5c6773; +} + +.search-input { + color: #ffffff; + background-color: #141920; + box-shadow: none; + transition: box-shadow 150ms ease-in-out; + border-radius: 4px; + margin-left: 8px; +} + +#crate-search+.search-input:focus { + box-shadow: 0px 6px 20px 0px black; +} + +.search-focus:disabled { + color: #929292; +} + +.module-item .stab { + color: #000; +} + +.stab.unstable, +.stab.internal, +.stab.deprecated, +.stab.portability { + color: #c5c5c5; + background: #314559 !important; + border-style: none !important; + border-radius: 4px; + padding: 3px 6px 3px 6px; +} + +.stab.portability > code { + color: #e6e1cf; + background-color: transparent; +} + +#help > div { + background: #14191f; + box-shadow: 0px 6px 20px 0px black; + border: none; + border-radius: 4px; +} + +.since { + color: grey; +} + +tr.result span.primitive::after, tr.result span.keyword::after { + color: #788797; +} + +.line-numbers :target { background-color: transparent; } + +/* Code highlighting */ +pre.rust .number, pre.rust .string { color: #b8cc52; } +pre.rust .kw, pre.rust .kw-2, pre.rust .prelude-ty, +pre.rust .bool-val, pre.rust .prelude-val, +pre.rust .op, pre.rust .lifetime { color: #ff7733; } +pre.rust .macro, pre.rust .macro-nonterminal { color: #a37acc; } +pre.rust .question-mark { + color: #ff9011; +} +pre.rust .self { + color: #36a3d9; + font-style: italic; +} +pre.rust .attribute { + color: #e6e1cf; +} +pre.rust .attribute .ident, pre.rust .attribute .op { + color: #e6e1cf; +} + +.example-wrap > pre.line-number { + color: #5c67736e; + border: none; +} + +a.test-arrow { + font-size: 100%; + color: #788797; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0); +} + +a.test-arrow:hover { + background-color: rgba(242, 151, 24, 0.05); + color: #ffb44c; +} + +.toggle-label { + color: #999; +} + +:target > code, :target > .in-band { + background: rgba(255, 236, 164, 0.06); + border-right: 3px solid #ffb44c; +} + +pre.compile_fail { + border-left: 2px solid rgba(255,0,0,.4); +} + +pre.compile_fail:hover, .information:hover + pre.compile_fail { + border-left: 2px solid #f00; +} + +pre.should_panic { + border-left: 2px solid rgba(255,0,0,.4); +} + +pre.should_panic:hover, .information:hover + pre.should_panic { + border-left: 2px solid #f00; +} + +pre.ignore { + border-left: 2px solid rgba(255,142,0,.6); +} + +pre.ignore:hover, .information:hover + pre.ignore { + border-left: 2px solid #ff9200; +} + +.tooltip.compile_fail { + color: rgba(255,0,0,.5); +} + +.information > .compile_fail:hover { + color: #f00; +} + +.tooltip.should_panic { + color: rgba(255,0,0,.5); +} + +.information > .should_panic:hover { + color: #f00; +} + +.tooltip.ignore { + color: rgba(255,142,0,.6); +} + +.information > .ignore:hover { + color: #ff9200; +} + +.search-failed a { + color: #39AFD7; +} + +.tooltip .tooltiptext { + background-color: #314559; + color: #c5c5c5; + border: 1px solid #5c6773; +} + +.tooltip .tooltiptext::after { + border-color: transparent #314559 transparent transparent; +} + +#titles > div.selected { + background-color: #141920 !important; + border-bottom: 1px solid #ffb44c !important; + border-top: none; +} + +#titles > div:not(.selected) { + background-color: transparent !important; + border: none; +} + +#titles > div:hover { + border-bottom: 1px solid rgba(242, 151, 24, 0.3); +} + +#titles > div > div.count { + color: #888; +} + +/* rules that this theme does not need to set, here to satisfy the rule checker */ +/* note that a lot of these are partially set in some way (meaning they are set +individually rather than as a group) */ +/* TODO: these rules should be at the bottom of the file but currently must be +above the `@media (max-width: 700px)` rules due to a bug in the css checker */ +/* see https://github.com/rust-lang/rust/pull/71237#issuecomment-618170143 */ +.content .highlighted.mod, .content .highlighted.externcrate {} +.search-input:focus {} +.content span.attr,.content a.attr,.block a.current.attr,.content span.derive,.content a.derive,.block a.current.derive,.content span.macro,.content a.macro,.block a.current.macro {} +.content .highlighted.trait {} +.content span.struct,.content a.struct,.block a.current.struct {} +#titles>div:hover,#titles>div.selected {} +.content .highlighted.traitalias {} +.content span.type,.content a.type,.block a.current.type {} +.content span.union,.content a.union,.block a.current.union {} +.content .highlighted.foreigntype {} +pre.rust .lifetime {} +.content .highlighted.primitive {} +.content .highlighted.constant,.content .highlighted.static {} +.stab.unstable {} +.content .highlighted.fn,.content .highlighted.method,.content .highlighted.tymethod {} +h2,h3:not(.impl):not(.method):not(.type):not(.tymethod),h4:not(.method):not(.type):not(.tymethod) {} +.content span.enum,.content a.enum,.block a.current.enum {} +.content span.constant,.content a.constant,.block a.current.constant,.content span.static,.content a.static,.block a.current.static {} +.content span.keyword,.content a.keyword,.block a.current.keyword {} +pre.rust .comment {} +.content .highlighted.enum {} +.content .highlighted.struct {} +.content .highlighted.keyword {} +.content span.traitalias,.content a.traitalias,.block a.current.traitalias {} +.content span.fn,.content a.fn,.block a.current.fn,.content span.method,.content a.method,.block a.current.method,.content span.tymethod,.content a.tymethod,.block a.current.tymethod,.content .fnname {} +pre.rust .kw {} +pre.rust .self,pre.rust .bool-val,pre.rust .prelude-val,pre.rust .attribute,pre.rust .attribute .ident {} +.content span.foreigntype,.content a.foreigntype,.block a.current.foreigntype {} +pre.rust .doccomment {} +.stab.deprecated {} +.content .highlighted.attr,.content .highlighted.derive,.content .highlighted.macro {} +.stab.portability {} +.content .highlighted.union {} +.content span.primitive,.content a.primitive,.block a.current.primitive {} +.content span.externcrate,.content span.mod,.content a.mod,.block a.current.mod {} +.content .highlighted.type {} +pre.rust .kw-2,pre.rust .prelude-ty {} +.content span.trait,.content a.trait,.block a.current.trait {} +.stab.internal {} + +@media (max-width: 700px) { + .sidebar-menu { + background-color: #14191f; + border-bottom-color: #5c6773; + border-right-color: #5c6773; + } + + .sidebar-elems { + background-color: #14191f; + border-right-color: #5c6773; + } + + #sidebar-filler { + background-color: #14191f; + border-bottom-color: #5c6773; + } +} + +kbd { + color: #c5c5c5; + background-color: #314559; + border-color: #5c6773; + border-bottom-color: #5c6773; + box-shadow-color: #c6cbd1; +} + +#theme-picker, #settings-menu { + border-color: #5c6773; + background-color: #0f1419; +} + +#theme-picker > img, #settings-menu > img { + filter: invert(100); +} + +#theme-picker:hover, #theme-picker:focus, +#settings-menu:hover, #settings-menu:focus { + border-color: #e0e0e0; +} + +#theme-choices { + border-color: #5c6773; + background-color: #0f1419; +} + +#theme-choices > button:not(:first-child) { + border-top-color: #c5c5c5; +} + +#theme-choices > button:hover, #theme-choices > button:focus { + background-color: rgba(70, 70, 70, 0.33); +} + +@media (max-width: 700px) { + #theme-picker { + background: #0f1419; + } +} + +#all-types { + background-color: #14191f; +} +#all-types:hover { + background-color: rgba(70, 70, 70, 0.33); +} + +.search-results td span.alias { + color: #c5c5c5; +} +.search-results td span.grey { + color: #999; +} + +#sidebar-toggle { + background-color: #14191f; +} +#sidebar-toggle:hover { + background-color: rgba(70, 70, 70, 0.33); +} +#source-sidebar { + background-color: #14191f; +} +#source-sidebar > .title { + color: #fff; + border-bottom-color: #5c6773; +} +div.files > a:hover, div.name:hover { + background-color: #14191f; + color: #ffb44c; +} +div.files > .selected { + background-color: #14191f; + color: #ffb44c; +} +.setting-line > .title { + border-bottom-color: #5c6773; +} +input:checked + .slider { + background-color: #ffb454 !important; +} diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 6790f3bd5d0b1..6bd7e53cdfbe2 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -64,6 +64,9 @@ pub mod themes { /// The "dark" theme. pub static DARK: &str = include_str!("static/themes/dark.css"); + + /// The "ayu" theme. + pub static AYU: &str = include_str!("static/themes/ayu.css"); } /// Files related to the Fira Sans font. From ed587f83cf721c8ad7d51ba76178e91d732e01c5 Mon Sep 17 00:00:00 2001 From: Adrian Cruceru Date: Sat, 11 Jul 2020 11:33:43 +0000 Subject: [PATCH 56/62] Update llvm-project to latest origin/rustc/10.0-2020-05-05 commit which includes LVI segfault fix --- src/llvm-project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm-project b/src/llvm-project index d134a53927fa0..86b120e6f302d 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit d134a53927fa033ae7e0f3e8ee872ff2dc71468d +Subproject commit 86b120e6f302d39cd6973b6391fb299d7bc22122 From c24b96dcb58a4f00d055128ea5caabd62f456b7f Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Thu, 2 Jul 2020 17:00:19 -0400 Subject: [PATCH 57/62] Teach bootstrap about target files vs target triples `rustc` allows passing in predefined target triples as well as JSON target specification files. This change allows bootstrap to have the first inkling about those differences. This allows building a cross-compiler for an out-of-tree architecture (even though that compiler won't work for other reasons). Even if no one ever uses this functionality, I think the newtype around the `Interned` improves the readability of the code. --- src/bootstrap/builder.rs | 59 +++++++++--------- src/bootstrap/builder/tests.rs | 56 ++++++++--------- src/bootstrap/cc_detect.rs | 34 +++++----- src/bootstrap/check.rs | 18 +++--- src/bootstrap/clean.rs | 2 +- src/bootstrap/compile.rs | 45 +++++++------- src/bootstrap/config.rs | 84 ++++++++++++++++++++++--- src/bootstrap/dist.rs | 79 +++++++++++------------ src/bootstrap/doc.rs | 30 ++++----- src/bootstrap/flags.rs | 11 ++-- src/bootstrap/install.rs | 29 ++++----- src/bootstrap/lib.rs | 110 +++++++++++++++++---------------- src/bootstrap/native.rs | 39 ++++++------ src/bootstrap/sanity.rs | 6 +- src/bootstrap/test.rs | 69 +++++++++++---------- src/bootstrap/tool.rs | 26 ++++---- src/bootstrap/util.rs | 11 ++-- 17 files changed, 391 insertions(+), 317 deletions(-) diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 3cbecbbaa06cb..10e8509c9bbdd 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -16,6 +16,7 @@ use build_helper::{output, t}; use crate::cache::{Cache, Interned, INTERNER}; use crate::check; use crate::compile; +use crate::config::TargetSelection; use crate::dist; use crate::doc; use crate::flags::Subcommand; @@ -86,8 +87,8 @@ pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash { pub struct RunConfig<'a> { pub builder: &'a Builder<'a>, - pub host: Interned, - pub target: Interned, + pub host: TargetSelection, + pub target: TargetSelection, pub path: PathBuf, } @@ -576,7 +577,7 @@ impl<'a> Builder<'a> { /// not take `Compiler` since all `Compiler` instances are meant to be /// obtained through this function, since it ensures that they are valid /// (i.e., built and assembled). - pub fn compiler(&self, stage: u32, host: Interned) -> Compiler { + pub fn compiler(&self, stage: u32, host: TargetSelection) -> Compiler { self.ensure(compile::Assemble { target_compiler: Compiler { stage, host } }) } @@ -594,8 +595,8 @@ impl<'a> Builder<'a> { pub fn compiler_for( &self, stage: u32, - host: Interned, - target: Interned, + host: TargetSelection, + target: TargetSelection, ) -> Compiler { if self.build.force_use_stage1(Compiler { stage, host }, target) { self.compiler(1, self.config.build) @@ -610,15 +611,11 @@ impl<'a> Builder<'a> { /// Returns the libdir where the standard library and other artifacts are /// found for a compiler's sysroot. - pub fn sysroot_libdir( - &self, - compiler: Compiler, - target: Interned, - ) -> Interned { + pub fn sysroot_libdir(&self, compiler: Compiler, target: TargetSelection) -> Interned { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] struct Libdir { compiler: Compiler, - target: Interned, + target: TargetSelection, } impl Step for Libdir { type Output = Interned; @@ -633,7 +630,7 @@ impl<'a> Builder<'a> { .sysroot(self.compiler) .join(lib) .join("rustlib") - .join(self.target) + .join(self.target.triple) .join("lib"); let _ = fs::remove_dir_all(&sysroot); t!(fs::create_dir_all(&sysroot)); @@ -656,7 +653,7 @@ impl<'a> Builder<'a> { Some(relative_libdir) if compiler.stage >= 1 => { self.sysroot(compiler).join(relative_libdir) } - _ => self.sysroot(compiler).join(libdir(&compiler.host)), + _ => self.sysroot(compiler).join(libdir(compiler.host)), } } } @@ -668,11 +665,11 @@ impl<'a> Builder<'a> { /// Windows. pub fn libdir_relative(&self, compiler: Compiler) -> &Path { if compiler.is_snapshot(self) { - libdir(&self.config.build).as_ref() + libdir(self.config.build).as_ref() } else { match self.config.libdir_relative() { Some(relative_libdir) if compiler.stage >= 1 => relative_libdir, - _ => libdir(&compiler.host).as_ref(), + _ => libdir(compiler.host).as_ref(), } } } @@ -707,7 +704,7 @@ impl<'a> Builder<'a> { if compiler.is_snapshot(self) { self.initial_rustc.clone() } else { - self.sysroot(compiler).join("bin").join(exe("rustc", &compiler.host)) + self.sysroot(compiler).join("bin").join(exe("rustc", compiler.host)) } } @@ -741,7 +738,7 @@ impl<'a> Builder<'a> { /// /// Note that this returns `None` if LLVM is disabled, or if we're in a /// check build or dry-run, where there's no need to build all of LLVM. - fn llvm_config(&self, target: Interned) -> Option { + fn llvm_config(&self, target: TargetSelection) -> Option { if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run { let llvm_config = self.ensure(native::Llvm { target }); if llvm_config.is_file() { @@ -763,7 +760,7 @@ impl<'a> Builder<'a> { compiler: Compiler, mode: Mode, source_type: SourceType, - target: Interned, + target: TargetSelection, cmd: &str, ) -> Cargo { let mut cargo = Command::new(&self.initial_cargo); @@ -794,7 +791,7 @@ impl<'a> Builder<'a> { } if cmd != "install" { - cargo.arg("--target").arg(target); + cargo.arg("--target").arg(target.rustc_target_arg()); } else { assert_eq!(target, compiler.host); } @@ -820,7 +817,7 @@ impl<'a> Builder<'a> { compiler.stage }; - let mut rustflags = Rustflags::new(&target); + let mut rustflags = Rustflags::new(target); if stage != 0 { if let Ok(s) = env::var("CARGOFLAGS_NOT_BOOTSTRAP") { cargo.args(s.split_whitespace()); @@ -993,7 +990,7 @@ impl<'a> Builder<'a> { // argument manually via `-C link-args=-Wl,-rpath,...`. Plus isn't it // fun to pass a flag to a tool to pass a flag to pass a flag to a tool // to change a flag in a binary? - if self.config.rust_rpath && util::use_host_linker(&target) { + if self.config.rust_rpath && util::use_host_linker(target) { let rpath = if target.contains("apple") { // Note that we need to take one extra step on macOS to also pass // `-Wl,-instal_name,@rpath/...` to get things to work right. To @@ -1021,7 +1018,7 @@ impl<'a> Builder<'a> { } if let Some(target_linker) = self.linker(target, can_use_lld) { - let target = crate::envify(&target); + let target = crate::envify(&target.triple); cargo.env(&format!("CARGO_TARGET_{}_LINKER", target), target_linker); } if !(["build", "check", "clippy", "fix", "rustc"].contains(&cmd)) && want_rustdoc { @@ -1182,21 +1179,23 @@ impl<'a> Builder<'a> { } }; let cc = ccacheify(&self.cc(target)); - cargo.env(format!("CC_{}", target), &cc); + cargo.env(format!("CC_{}", target.triple), &cc); let cflags = self.cflags(target, GitRepo::Rustc).join(" "); - cargo.env(format!("CFLAGS_{}", target), cflags.clone()); + cargo.env(format!("CFLAGS_{}", target.triple), cflags.clone()); if let Some(ar) = self.ar(target) { let ranlib = format!("{} s", ar.display()); - cargo.env(format!("AR_{}", target), ar).env(format!("RANLIB_{}", target), ranlib); + cargo + .env(format!("AR_{}", target.triple), ar) + .env(format!("RANLIB_{}", target.triple), ranlib); } if let Ok(cxx) = self.cxx(target) { let cxx = ccacheify(&cxx); cargo - .env(format!("CXX_{}", target), &cxx) - .env(format!("CXXFLAGS_{}", target), cflags); + .env(format!("CXX_{}", target.triple), &cxx) + .env(format!("CXXFLAGS_{}", target.triple), cflags); } } @@ -1230,7 +1229,7 @@ impl<'a> Builder<'a> { // Environment variables *required* throughout the build // // FIXME: should update code to not require this env var - cargo.env("CFG_COMPILER_HOST_TRIPLE", target); + cargo.env("CFG_COMPILER_HOST_TRIPLE", target.triple); // Set this for all builds to make sure doc builds also get it. cargo.env("CFG_RELEASE_CHANNEL", &self.config.channel); @@ -1386,7 +1385,7 @@ mod tests; struct Rustflags(String); impl Rustflags { - fn new(target: &str) -> Rustflags { + fn new(target: TargetSelection) -> Rustflags { let mut ret = Rustflags(String::new()); // Inherit `RUSTFLAGS` by default ... @@ -1394,7 +1393,7 @@ impl Rustflags { // ... and also handle target-specific env RUSTFLAGS if they're // configured. - let target_specific = format!("CARGO_TARGET_{}_RUSTFLAGS", crate::envify(target)); + let target_specific = format!("CARGO_TARGET_{}_RUSTFLAGS", crate::envify(&target.triple)); ret.env(&target_specific); ret diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs index 1e75e67af0c9e..69a54bec33b67 100644 --- a/src/bootstrap/builder/tests.rs +++ b/src/bootstrap/builder/tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::config::Config; +use crate::config::{Config, TargetSelection}; use std::thread; use pretty_assertions::assert_eq; @@ -17,16 +17,16 @@ fn configure(host: &[&str], target: &[&str]) -> Config { .join(&thread::current().name().unwrap_or("unknown").replace(":", "-")); t!(fs::create_dir_all(&dir)); config.out = dir; - config.build = INTERNER.intern_str("A"); + config.build = TargetSelection::from_user("A"); config.hosts = vec![config.build] .into_iter() - .chain(host.iter().map(|s| INTERNER.intern_str(s))) + .chain(host.iter().map(|s| TargetSelection::from_user(s))) .collect::>(); config.targets = config .hosts .clone() .into_iter() - .chain(target.iter().map(|s| INTERNER.intern_str(s))) + .chain(target.iter().map(|s| TargetSelection::from_user(s))) .collect::>(); config } @@ -41,7 +41,7 @@ fn dist_baseline() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); + let a = TargetSelection::from_user("A"); assert_eq!(first(builder.cache.all::()), &[dist::Docs { host: a },]); assert_eq!(first(builder.cache.all::()), &[dist::Mingw { host: a },]); @@ -67,8 +67,8 @@ fn dist_with_targets() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); assert_eq!( first(builder.cache.all::()), @@ -98,8 +98,8 @@ fn dist_with_hosts() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); assert_eq!( first(builder.cache.all::()), @@ -128,8 +128,8 @@ fn dist_with_hosts() { #[test] fn dist_only_cross_host() { - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); let mut build = Build::new(configure(&["B"], &[])); build.config.docs = false; build.config.extended = true; @@ -156,9 +156,9 @@ fn dist_with_targets_and_hosts() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); - let c = INTERNER.intern_str("C"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); + let c = TargetSelection::from_user("C"); assert_eq!( first(builder.cache.all::()), @@ -194,9 +194,9 @@ fn dist_with_target_flag() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); - let c = INTERNER.intern_str("C"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); + let c = TargetSelection::from_user("C"); assert_eq!( first(builder.cache.all::()), @@ -224,8 +224,8 @@ fn dist_with_same_targets_and_hosts() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Dist), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); assert_eq!( first(builder.cache.all::()), @@ -277,9 +277,9 @@ fn build_default() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Build), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); - let c = INTERNER.intern_str("C"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); + let c = TargetSelection::from_user("C"); assert_eq!( first(builder.cache.all::()), @@ -318,9 +318,9 @@ fn build_with_target_flag() { let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Build), &[]); - let a = INTERNER.intern_str("A"); - let b = INTERNER.intern_str("B"); - let c = INTERNER.intern_str("C"); + let a = TargetSelection::from_user("A"); + let b = TargetSelection::from_user("B"); + let c = TargetSelection::from_user("C"); assert_eq!( first(builder.cache.all::()), @@ -374,7 +374,7 @@ fn test_with_no_doc_stage0() { let build = Build::new(config); let mut builder = Builder::new(&build); - let host = INTERNER.intern_str("A"); + let host = TargetSelection::from_user("A"); builder .run_step_descriptions(&[StepDescription::from::()], &["src/libstd".into()]); @@ -428,7 +428,7 @@ fn doc_default() { let build = Build::new(config); let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Doc), &[]); - let a = INTERNER.intern_str("A"); + let a = TargetSelection::from_user("A"); // error_index_generator uses stage 1 to share rustdoc artifacts with the // rustdoc tool. @@ -466,7 +466,7 @@ fn test_docs() { let build = Build::new(config); let mut builder = Builder::new(&build); builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Test), &[]); - let a = INTERNER.intern_str("A"); + let a = TargetSelection::from_user("A"); // error_index_generator uses stage 1 to share rustdoc artifacts with the // rustdoc tool. diff --git a/src/bootstrap/cc_detect.rs b/src/bootstrap/cc_detect.rs index ab16ca3732c1f..7ff00d85dd2f2 100644 --- a/src/bootstrap/cc_detect.rs +++ b/src/bootstrap/cc_detect.rs @@ -28,16 +28,15 @@ use std::{env, iter}; use build_helper::output; -use crate::cache::Interned; -use crate::config::Target; +use crate::config::{Target, TargetSelection}; use crate::{Build, GitRepo}; // The `cc` crate doesn't provide a way to obtain a path to the detected archiver, // so use some simplified logic here. First we respect the environment variable `AR`, then // try to infer the archiver path from the C compiler path. // In the future this logic should be replaced by calling into the `cc` crate. -fn cc2ar(cc: &Path, target: &str) -> Option { - if let Some(ar) = env::var_os(format!("AR_{}", target.replace("-", "_"))) { +fn cc2ar(cc: &Path, target: TargetSelection) -> Option { + if let Some(ar) = env::var_os(format!("AR_{}", target.triple.replace("-", "_"))) { Some(PathBuf::from(ar)) } else if let Some(ar) = env::var_os("AR") { Some(PathBuf::from(ar)) @@ -79,8 +78,8 @@ pub fn find(build: &mut Build) { .opt_level(2) .warnings(false) .debug(false) - .target(&target) - .host(&build.build); + .target(&target.triple) + .host(&build.build.triple); match build.crt_static(target) { Some(a) => { cfg.static_crt(a); @@ -106,10 +105,10 @@ pub fn find(build: &mut Build) { let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) { ar } else { - cc2ar(compiler.path(), &target) + cc2ar(compiler.path(), target) }; - build.cc.insert(target, compiler); + build.cc.insert(target, compiler.clone()); let cflags = build.cflags(target, GitRepo::Rustc); // If we use llvm-libunwind, we will need a C++ compiler as well for all targets @@ -120,8 +119,8 @@ pub fn find(build: &mut Build) { .warnings(false) .debug(false) .cpp(true) - .target(&target) - .host(&build.build); + .target(&target.triple) + .host(&build.build.triple); let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) { cfg.compiler(cxx); @@ -138,14 +137,14 @@ pub fn find(build: &mut Build) { build.cxx.insert(target, compiler); } - build.verbose(&format!("CC_{} = {:?}", &target, build.cc(target))); - build.verbose(&format!("CFLAGS_{} = {:?}", &target, cflags)); + build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target))); + build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags)); if let Ok(cxx) = build.cxx(target) { - build.verbose(&format!("CXX_{} = {:?}", &target, cxx)); - build.verbose(&format!("CXXFLAGS_{} = {:?}", &target, cflags)); + build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx)); + build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cflags)); } if let Some(ar) = ar { - build.verbose(&format!("AR_{} = {:?}", &target, ar)); + build.verbose(&format!("AR_{} = {:?}", &target.triple, ar)); build.ar.insert(target, ar); } } @@ -154,17 +153,18 @@ pub fn find(build: &mut Build) { fn set_compiler( cfg: &mut cc::Build, compiler: Language, - target: Interned, + target: TargetSelection, config: Option<&Target>, build: &Build, ) { - match &*target { + match &*target.triple { // When compiling for android we may have the NDK configured in the // config.toml in which case we look there. Otherwise the default // compiler already takes into account the triple in question. t if t.contains("android") => { if let Some(ndk) = config.and_then(|c| c.ndk.as_ref()) { let target = target + .triple .replace("armv7neon", "arm") .replace("armv7", "arm") .replace("thumbv7neon", "arm") diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index 0d38d2eebe793..9f34bb4e6ccd7 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -1,15 +1,15 @@ //! Implementation of compiling the compiler and standard library, in "check"-based modes. use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; -use crate::cache::Interned; use crate::compile::{add_to_sysroot, run_cargo, rustc_cargo, std_cargo}; +use crate::config::TargetSelection; use crate::tool::{prepare_tool_cargo, SourceType}; use crate::{Compiler, Mode}; use std::path::PathBuf; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Std { - pub target: Interned, + pub target: TargetSelection, } fn args(kind: Kind) -> Vec { @@ -71,7 +71,7 @@ impl Step for Std { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rustc { - pub target: Interned, + pub target: TargetSelection, } impl Step for Rustc { @@ -127,7 +127,7 @@ macro_rules! tool_check_step { ($name:ident, $path:expr, $source_type:expr) => { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct $name { - pub target: Interned, + pub target: TargetSelection, } impl Step for $name { @@ -163,8 +163,8 @@ macro_rules! tool_check_step { println!( "Checking {} artifacts ({} -> {})", stringify!($name).to_lowercase(), - &compiler.host, - target + &compiler.host.triple, + target.triple ); run_cargo( builder, @@ -184,7 +184,7 @@ macro_rules! tool_check_step { fn stamp( builder: &Builder<'_>, compiler: Compiler, - target: Interned, + target: TargetSelection, ) -> PathBuf { builder .cargo_out(compiler, Mode::ToolRustc, target) @@ -204,12 +204,12 @@ tool_check_step!(Clippy, "src/tools/clippy", SourceType::InTree); /// Cargo's output path for the standard library in a given stage, compiled /// by a particular compiler for the specified target. -fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned) -> PathBuf { +fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf { builder.cargo_out(compiler, Mode::Std, target).join(".libstd-check.stamp") } /// Cargo's output path for librustc in a given stage, compiled by a particular /// compiler for the specified target. -fn librustc_stamp(builder: &Builder<'_>, compiler: Compiler, target: Interned) -> PathBuf { +fn librustc_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf { builder.cargo_out(compiler, Mode::Rustc, target).join(".librustc-check.stamp") } diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index b4e58c06fdea9..f83dfe8e635e3 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -23,7 +23,7 @@ pub fn clean(build: &Build, all: bool) { rm_rf(&build.out.join("dist")); for host in &build.hosts { - let entries = match build.out.join(host).read_dir() { + let entries = match build.out.join(host.triple).read_dir() { Ok(iter) => iter, Err(_) => continue, }; diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index 9b4926f28d4ed..f3063489c1854 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -22,6 +22,7 @@ use serde::Deserialize; use crate::builder::Cargo; use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; +use crate::config::TargetSelection; use crate::dist; use crate::native; use crate::tool::SourceType; @@ -30,7 +31,7 @@ use crate::{Compiler, DependencyType, GitRepo, Mode}; #[derive(Debug, PartialOrd, Ord, Copy, Clone, PartialEq, Eq, Hash)] pub struct Std { - pub target: Interned, + pub target: TargetSelection, pub compiler: Compiler, } @@ -129,7 +130,7 @@ fn copy_and_stamp( fn copy_third_party_objects( builder: &Builder<'_>, compiler: &Compiler, - target: Interned, + target: TargetSelection, ) -> Vec<(PathBuf, DependencyType)> { let mut target_deps = vec![]; @@ -157,7 +158,7 @@ fn copy_third_party_objects( fn copy_self_contained_objects( builder: &Builder<'_>, compiler: &Compiler, - target: Interned, + target: TargetSelection, ) -> Vec<(PathBuf, DependencyType)> { // cfg(bootstrap) // Remove when upgrading bootstrap compiler. @@ -212,7 +213,7 @@ fn copy_self_contained_objects( /// Configure cargo to compile the standard library, adding appropriate env vars /// and such. -pub fn std_cargo(builder: &Builder<'_>, target: Interned, stage: u32, cargo: &mut Cargo) { +pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, cargo: &mut Cargo) { if let Some(target) = env::var_os("MACOSX_STD_DEPLOYMENT_TARGET") { cargo.env("MACOSX_DEPLOYMENT_TARGET", target); } @@ -307,7 +308,7 @@ pub fn std_cargo(builder: &Builder<'_>, target: Interned, stage: u32, ca struct StdLink { pub compiler: Compiler, pub target_compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for StdLink { @@ -343,7 +344,7 @@ impl Step for StdLink { fn copy_sanitizers( builder: &Builder<'_>, compiler: &Compiler, - target: Interned, + target: TargetSelection, ) -> Vec { let runtimes: Vec = builder.ensure(native::Sanitizers { target }); @@ -378,7 +379,7 @@ fn copy_sanitizers( #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct StartupObjects { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for StartupObjects { @@ -425,7 +426,7 @@ impl Step for StartupObjects { .arg("--cfg") .arg("bootstrap") .arg("--target") - .arg(target) + .arg(target.rustc_target_arg()) .arg("--emit=obj") .arg("-o") .arg(dst_file) @@ -444,7 +445,7 @@ impl Step for StartupObjects { #[derive(Debug, PartialOrd, Ord, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rustc { - pub target: Interned, + pub target: TargetSelection, pub compiler: Compiler, } @@ -524,7 +525,7 @@ impl Step for Rustc { } } -pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: Interned) { +pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelection) { cargo .arg("--features") .arg(builder.rustc_features()) @@ -533,7 +534,7 @@ pub fn rustc_cargo(builder: &Builder<'_>, cargo: &mut Cargo, target: Interned, cargo: &mut Cargo, target: Interned) { +pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelection) { // Set some configuration variables picked up by build scripts and // the compiler alike cargo @@ -614,7 +615,7 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: Interne struct RustcLink { pub compiler: Compiler, pub target_compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for RustcLink { @@ -644,11 +645,7 @@ impl Step for RustcLink { /// Cargo's output path for the standard library in a given stage, compiled /// by a particular compiler for the specified target. -pub fn libstd_stamp( - builder: &Builder<'_>, - compiler: Compiler, - target: Interned, -) -> PathBuf { +pub fn libstd_stamp(builder: &Builder<'_>, compiler: Compiler, target: TargetSelection) -> PathBuf { builder.cargo_out(compiler, Mode::Std, target).join(".libstd.stamp") } @@ -657,7 +654,7 @@ pub fn libstd_stamp( pub fn librustc_stamp( builder: &Builder<'_>, compiler: Compiler, - target: Interned, + target: TargetSelection, ) -> PathBuf { builder.cargo_out(compiler, Mode::Rustc, target).join(".librustc.stamp") } @@ -665,7 +662,7 @@ pub fn librustc_stamp( pub fn compiler_file( builder: &Builder<'_>, compiler: &Path, - target: Interned, + target: TargetSelection, file: &str, ) -> PathBuf { let mut cmd = Command::new(compiler); @@ -696,9 +693,9 @@ impl Step for Sysroot { fn run(self, builder: &Builder<'_>) -> Interned { let compiler = self.compiler; let sysroot = if compiler.stage == 0 { - builder.out.join(&compiler.host).join("stage0-sysroot") + builder.out.join(&compiler.host.triple).join("stage0-sysroot") } else { - builder.out.join(&compiler.host).join(format!("stage{}", compiler.stage)) + builder.out.join(&compiler.host.triple).join(format!("stage{}", compiler.stage)) }; let _ = fs::remove_dir_all(&sysroot); t!(fs::create_dir_all(&sysroot)); @@ -812,8 +809,8 @@ impl Step for Assemble { let libdir = builder.sysroot_libdir(target_compiler, target_compiler.host); if let Some(lld_install) = lld_install { - let src_exe = exe("lld", &target_compiler.host); - let dst_exe = exe("rust-lld", &target_compiler.host); + let src_exe = exe("lld", target_compiler.host); + let dst_exe = exe("rust-lld", target_compiler.host); // we prepend this bin directory to the user PATH when linking Rust binaries. To // avoid shadowing the system LLD we rename the LLD we provide to `rust-lld`. let dst = libdir.parent().unwrap().join("bin"); @@ -828,7 +825,7 @@ impl Step for Assemble { // Link the compiler binary itself into place let out_dir = builder.cargo_out(build_compiler, Mode::Rustc, host); - let rustc = out_dir.join(exe("rustc_binary", &*host)); + let rustc = out_dir.join(exe("rustc_binary", host)); let bindir = sysroot.join("bin"); t!(fs::create_dir_all(&bindir)); let compiler = builder.rustc(target_compiler); diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index ff545a6ddcf47..d71f31704209e 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -7,6 +7,7 @@ use std::cmp; use std::collections::{HashMap, HashSet}; use std::env; use std::ffi::OsString; +use std::fmt; use std::fs; use std::path::{Path, PathBuf}; use std::process; @@ -39,7 +40,7 @@ pub struct Config { pub docs: bool, pub locked_deps: bool, pub vendor: bool, - pub target_config: HashMap, Target>, + pub target_config: HashMap, pub full_bootstrap: bool, pub extended: bool, pub tools: Option>, @@ -112,9 +113,9 @@ pub struct Config { pub rust_thin_lto_import_instr_limit: Option, pub rust_remap_debuginfo: bool, - pub build: Interned, - pub hosts: Vec>, - pub targets: Vec>, + pub build: TargetSelection, + pub hosts: Vec, + pub targets: Vec, pub local_rebuild: bool, pub jemalloc: bool, pub control_flow_guard: bool, @@ -158,6 +159,67 @@ pub struct Config { pub out: PathBuf, } +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TargetSelection { + pub triple: Interned, + file: Option>, +} + +impl TargetSelection { + pub fn from_user(selection: &str) -> Self { + let path = Path::new(selection); + + let (triple, file) = if path.exists() { + let triple = path + .file_stem() + .expect("Target specification file has no file stem") + .to_str() + .expect("Target specification file stem is not UTF-8"); + + (triple, Some(selection)) + } else { + (selection, None) + }; + + let triple = INTERNER.intern_str(triple); + let file = file.map(|f| INTERNER.intern_str(f)); + + Self { triple, file } + } + + pub fn rustc_target_arg(&self) -> &str { + self.file.as_ref().unwrap_or(&self.triple) + } + + pub fn contains(&self, needle: &str) -> bool { + self.triple.contains(needle) + } + + pub fn starts_with(&self, needle: &str) -> bool { + self.triple.starts_with(needle) + } + + pub fn ends_with(&self, needle: &str) -> bool { + self.triple.ends_with(needle) + } +} + +impl fmt::Display for TargetSelection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.triple)?; + if let Some(file) = self.file { + write!(f, "({})", file)?; + } + Ok(()) + } +} + +impl PartialEq<&str> for TargetSelection { + fn eq(&self, other: &&str) -> bool { + self.triple == *other + } +} + /// Per-target configuration stored in the global configuration structure. #[derive(Default)] pub struct Target { @@ -403,7 +465,7 @@ impl Config { config.missing_tools = false; // set by bootstrap.py - config.build = INTERNER.intern_str(&env::var("BUILD").expect("'BUILD' to be set")); + config.build = TargetSelection::from_user(&env::var("BUILD").expect("'BUILD' to be set")); config.src = Config::path_from_python("SRC"); config.out = Config::path_from_python("BUILD_DIR"); @@ -464,14 +526,16 @@ impl Config { let build = toml.build.clone().unwrap_or_default(); // set by bootstrap.py config.hosts.push(config.build.clone()); - for host in build.host.iter() { - let host = INTERNER.intern_str(host); + for host in build.host.iter().map(|h| TargetSelection::from_user(h)) { if !config.hosts.contains(&host) { config.hosts.push(host); } } - for target in - config.hosts.iter().cloned().chain(build.target.iter().map(|s| INTERNER.intern_str(s))) + for target in config + .hosts + .iter() + .copied() + .chain(build.target.iter().map(|h| TargetSelection::from_user(h))) { if !config.targets.contains(&target) { config.targets.push(target); @@ -637,7 +701,7 @@ impl Config { target.wasi_root = cfg.wasi_root.clone().map(PathBuf::from); target.qemu_rootfs = cfg.qemu_rootfs.clone().map(PathBuf::from); - config.target_config.insert(INTERNER.intern_string(triple.clone()), target); + config.target_config.insert(TargetSelection::from_user(triple), target); } } diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 5d2fcba7feaba..af30747a9592e 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -20,6 +20,7 @@ use crate::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; use crate::channel; use crate::compile; +use crate::config::TargetSelection; use crate::tool::{self, Tool}; use crate::util::{exe, is_dylib, timeit}; use crate::{Compiler, DependencyType, Mode, LLVM_TOOLS}; @@ -68,7 +69,7 @@ fn missing_tool(tool_name: &str, skip: bool) { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Docs { - pub host: Interned, + pub host: TargetSelection, } impl Step for Docs { @@ -131,7 +132,7 @@ impl Step for Docs { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustcDocs { - pub host: Interned, + pub host: TargetSelection, } impl Step for RustcDocs { @@ -210,11 +211,11 @@ fn find_files(files: &[&str], path: &[PathBuf]) -> Vec { fn make_win_dist( rust_root: &Path, plat_root: &Path, - target_triple: Interned, + target: TargetSelection, builder: &Builder<'_>, ) { //Ask gcc where it keeps its stuff - let mut cmd = Command::new(builder.cc(target_triple)); + let mut cmd = Command::new(builder.cc(target)); cmd.arg("-print-search-dirs"); let gcc_out = output(&mut cmd); @@ -234,16 +235,16 @@ fn make_win_dist( } } - let compiler = if target_triple == "i686-pc-windows-gnu" { + let compiler = if target == "i686-pc-windows-gnu" { "i686-w64-mingw32-gcc.exe" - } else if target_triple == "x86_64-pc-windows-gnu" { + } else if target == "x86_64-pc-windows-gnu" { "x86_64-w64-mingw32-gcc.exe" } else { "gcc.exe" }; let target_tools = [compiler, "ld.exe", "dlltool.exe", "libwinpthread-1.dll"]; let mut rustc_dlls = vec!["libwinpthread-1.dll"]; - if target_triple.starts_with("i686-") { + if target.starts_with("i686-") { rustc_dlls.push("libgcc_s_dw2-1.dll"); } else { rustc_dlls.push("libgcc_s_seh-1.dll"); @@ -311,7 +312,7 @@ fn make_win_dist( let target_bin_dir = plat_root .join("lib") .join("rustlib") - .join(target_triple) + .join(target.triple) .join("bin") .join("self-contained"); fs::create_dir_all(&target_bin_dir).expect("creating target_bin_dir failed"); @@ -331,7 +332,7 @@ fn make_win_dist( let target_lib_dir = plat_root .join("lib") .join("rustlib") - .join(target_triple) + .join(target.triple) .join("lib") .join("self-contained"); fs::create_dir_all(&target_lib_dir).expect("creating target_lib_dir failed"); @@ -342,7 +343,7 @@ fn make_win_dist( #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Mingw { - pub host: Interned, + pub host: TargetSelection, } impl Step for Mingw { @@ -530,11 +531,11 @@ impl Step for Rustc { // Copy over lld if it's there if builder.config.lld_enabled { - let exe = exe("rust-lld", &compiler.host); + let exe = exe("rust-lld", compiler.host); let src = builder.sysroot_libdir(compiler, host).parent().unwrap().join("bin").join(&exe); // for the rationale about this rename check `compile::copy_lld_to_sysroot` - let dst = image.join("lib/rustlib").join(&*host).join("bin").join(&exe); + let dst = image.join("lib/rustlib").join(&*host.triple).join("bin").join(&exe); t!(fs::create_dir_all(&dst.parent().unwrap())); builder.copy(&src, &dst); } @@ -592,7 +593,7 @@ impl Step for Rustc { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct DebuggerScripts { pub sysroot: Interned, - pub host: Interned, + pub host: TargetSelection, } impl Step for DebuggerScripts { @@ -662,8 +663,8 @@ fn skip_host_target_lib(builder: &Builder<'_>, compiler: Compiler) -> bool { } /// Copy stamped files into an image's `target/lib` directory. -fn copy_target_libs(builder: &Builder<'_>, target: &str, image: &Path, stamp: &Path) { - let dst = image.join("lib/rustlib").join(target).join("lib"); +fn copy_target_libs(builder: &Builder<'_>, target: TargetSelection, image: &Path, stamp: &Path) { + let dst = image.join("lib/rustlib").join(target.triple).join("lib"); let self_contained_dst = dst.join("self-contained"); t!(fs::create_dir_all(&dst)); t!(fs::create_dir_all(&self_contained_dst)); @@ -679,7 +680,7 @@ fn copy_target_libs(builder: &Builder<'_>, target: &str, image: &Path, stamp: &P #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Std { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Std { @@ -718,7 +719,7 @@ impl Step for Std { let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target); let stamp = compile::libstd_stamp(builder, compiler_to_use, target); - copy_target_libs(builder, &target, &image, &stamp); + copy_target_libs(builder, target, &image, &stamp); let mut cmd = rust_installer(builder); cmd.arg("generate") @@ -747,7 +748,7 @@ impl Step for Std { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustcDev { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for RustcDev { @@ -787,7 +788,7 @@ impl Step for RustcDev { let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target); let stamp = compile::librustc_stamp(builder, compiler_to_use, target); - copy_target_libs(builder, &target, &image, &stamp); + copy_target_libs(builder, target, &image, &stamp); let mut cmd = rust_installer(builder); cmd.arg("generate") @@ -818,7 +819,7 @@ impl Step for RustcDev { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Analysis { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Analysis { @@ -861,12 +862,12 @@ impl Step for Analysis { let src = builder .stage_out(compiler, Mode::Std) - .join(target) + .join(target.triple) .join(builder.cargo_dir()) .join("deps"); let image_src = src.join("save-analysis"); - let dst = image.join("lib/rustlib").join(target).join("analysis"); + let dst = image.join("lib/rustlib").join(target.triple).join("analysis"); t!(fs::create_dir_all(&dst)); builder.info(&format!("image_src: {:?}, dst: {:?}", image_src, dst)); builder.cp_r(&image_src, &dst); @@ -1163,7 +1164,7 @@ pub fn sanitize_sh(path: &Path) -> String { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Cargo { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Cargo { @@ -1255,7 +1256,7 @@ impl Step for Cargo { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Rls { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Rls { @@ -1345,7 +1346,7 @@ impl Step for Rls { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustAnalyzer { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for RustAnalyzer { @@ -1432,7 +1433,7 @@ impl Step for RustAnalyzer { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Clippy { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Clippy { @@ -1523,7 +1524,7 @@ impl Step for Clippy { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Miri { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Miri { @@ -1620,7 +1621,7 @@ impl Step for Miri { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Rustfmt { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Rustfmt { @@ -1714,8 +1715,8 @@ impl Step for Rustfmt { #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct Extended { stage: u32, - host: Interned, - target: Interned, + host: TargetSelection, + target: TargetSelection, } impl Step for Extended { @@ -2255,7 +2256,7 @@ impl Step for Extended { } } -fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: Interned) { +fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: TargetSelection) { let mut parts = channel::CFG_RELEASE_NUM.split('.'); cmd.env("CFG_RELEASE_INFO", builder.rust_version()) .env("CFG_RELEASE_NUM", channel::CFG_RELEASE_NUM) @@ -2266,7 +2267,7 @@ fn add_env(builder: &Builder<'_>, cmd: &mut Command, target: Interned) { .env("CFG_VER_BUILD", "0") // just needed to build .env("CFG_PACKAGE_VERS", builder.rust_package_vers()) .env("CFG_PACKAGE_NAME", pkgname(builder, "rust")) - .env("CFG_BUILD", target) + .env("CFG_BUILD", target.triple) .env("CFG_CHANNEL", &builder.config.channel); if target.contains("windows-gnu") { @@ -2348,7 +2349,7 @@ impl Step for HashSign { /// /// Note: This function does not yet support Windows, but we also don't support /// linking LLVM tools dynamically on Windows yet. -fn maybe_install_llvm(builder: &Builder<'_>, target: Interned, dst_libdir: &Path) { +fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir: &Path) { let src_libdir = builder.llvm_out(target).join("lib"); if target.contains("apple-darwin") { @@ -2373,13 +2374,13 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: Interned, dst_libdi } /// Maybe add libLLVM.so to the target lib-dir for linking. -pub fn maybe_install_llvm_target(builder: &Builder<'_>, target: Interned, sysroot: &Path) { - let dst_libdir = sysroot.join("lib/rustlib").join(&*target).join("lib"); +pub fn maybe_install_llvm_target(builder: &Builder<'_>, target: TargetSelection, sysroot: &Path) { + let dst_libdir = sysroot.join("lib/rustlib").join(&*target.triple).join("lib"); maybe_install_llvm(builder, target, &dst_libdir); } /// Maybe add libLLVM.so to the runtime lib-dir for rustc itself. -pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: Interned, sysroot: &Path) { +pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: TargetSelection, sysroot: &Path) { let dst_libdir = sysroot.join(builder.sysroot_libdir_relative(Compiler { stage: 1, host: target })); maybe_install_llvm(builder, target, &dst_libdir); @@ -2387,7 +2388,7 @@ pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: Interned, + pub target: TargetSelection, } impl Step for LlvmTools { @@ -2425,10 +2426,10 @@ impl Step for LlvmTools { // Prepare the image directory let src_bindir = builder.llvm_out(target).join("bin"); - let dst_bindir = image.join("lib/rustlib").join(&*target).join("bin"); + let dst_bindir = image.join("lib/rustlib").join(&*target.triple).join("bin"); t!(fs::create_dir_all(&dst_bindir)); for tool in LLVM_TOOLS { - let exe = src_bindir.join(exe(tool, &target)); + let exe = src_bindir.join(exe(tool, target)); builder.install(&exe, &dst_bindir, 0o755); } diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs index 582bc9da0e804..054daa3cf4814 100644 --- a/src/bootstrap/doc.rs +++ b/src/bootstrap/doc.rs @@ -18,7 +18,7 @@ use build_helper::{t, up_to_date}; use crate::builder::{Builder, Compiler, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; use crate::compile; -use crate::config::Config; +use crate::config::{Config, TargetSelection}; use crate::tool::{self, prepare_tool_cargo, SourceType, Tool}; use crate::util::symlink_dir; @@ -27,7 +27,7 @@ macro_rules! book { $( #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct $name { - target: Interned, + target: TargetSelection, } impl Step for $name { @@ -101,7 +101,7 @@ fn is_explicit_request(builder: &Builder<'_>, path: &str) -> bool { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct UnstableBook { - target: Interned, + target: TargetSelection, } impl Step for UnstableBook { @@ -129,7 +129,7 @@ impl Step for UnstableBook { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] struct RustbookSrc { - target: Interned, + target: TargetSelection, name: Interned, src: Interned, } @@ -169,7 +169,7 @@ impl Step for RustbookSrc { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct TheBook { compiler: Compiler, - target: Interned, + target: TargetSelection, } impl Step for TheBook { @@ -241,7 +241,7 @@ impl Step for TheBook { fn invoke_rustdoc( builder: &Builder<'_>, compiler: Compiler, - target: Interned, + target: TargetSelection, markdown: &str, ) { let out = builder.doc_out(target); @@ -277,7 +277,7 @@ fn invoke_rustdoc( #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Standalone { compiler: Compiler, - target: Interned, + target: TargetSelection, } impl Step for Standalone { @@ -386,7 +386,7 @@ impl Step for Standalone { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Std { pub stage: u32, - pub target: Interned, + pub target: TargetSelection, } impl Step for Std { @@ -415,7 +415,7 @@ impl Step for Std { let compiler = builder.compiler(stage, builder.config.build); builder.ensure(compile::Std { compiler, target }); - let out_dir = builder.stage_out(compiler, Mode::Std).join(target).join("doc"); + let out_dir = builder.stage_out(compiler, Mode::Std).join(target.triple).join("doc"); // Here what we're doing is creating a *symlink* (directory junction on // Windows) to the final output location. This is not done as an @@ -487,7 +487,7 @@ impl Step for Std { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Rustc { stage: u32, - target: Interned, + target: TargetSelection, } impl Step for Rustc { @@ -531,7 +531,7 @@ impl Step for Rustc { // We do not symlink to the same shared folder that already contains std library // documentation from previous steps as we do not want to include that. - let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target).join("doc"); + let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target.triple).join("doc"); t!(symlink_dir_force(&builder.config, &out, &out_dir)); // Build cargo command. @@ -568,7 +568,7 @@ impl Step for Rustc { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Rustdoc { stage: u32, - target: Interned, + target: TargetSelection, } impl Step for Rustdoc { @@ -613,7 +613,7 @@ impl Step for Rustdoc { builder.ensure(tool::Rustdoc { compiler }); // Symlink compiler docs to the output directory of rustdoc documentation. - let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc"); + let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target.triple).join("doc"); t!(fs::create_dir_all(&out_dir)); t!(symlink_dir_force(&builder.config, &out, &out_dir)); @@ -641,7 +641,7 @@ impl Step for Rustdoc { #[derive(Ord, PartialOrd, Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ErrorIndex { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for ErrorIndex { @@ -681,7 +681,7 @@ impl Step for ErrorIndex { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct UnstableBookGen { - target: Interned, + target: TargetSelection, } impl Step for UnstableBookGen { diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs index f477c75293385..1055689c81e6a 100644 --- a/src/bootstrap/flags.rs +++ b/src/bootstrap/flags.rs @@ -10,8 +10,7 @@ use std::process; use getopts::Options; use crate::builder::Builder; -use crate::cache::{Interned, INTERNER}; -use crate::config::Config; +use crate::config::{Config, TargetSelection}; use crate::{Build, DocTests}; /// Deserialized version of all flags for this compile. @@ -21,8 +20,8 @@ pub struct Flags { pub stage: Option, pub keep_stage: Vec, - pub host: Vec>, - pub target: Vec>, + pub host: Vec, + pub target: Vec, pub config: Option, pub jobs: Option, pub cmd: Subcommand, @@ -532,11 +531,11 @@ Arguments: .collect(), host: split(&matches.opt_strs("host")) .into_iter() - .map(|x| INTERNER.intern_string(x)) + .map(|x| TargetSelection::from_user(&x)) .collect::>(), target: split(&matches.opt_strs("target")) .into_iter() - .map(|x| INTERNER.intern_string(x)) + .map(|x| TargetSelection::from_user(&x)) .collect::>(), config: cfg_file, jobs: matches.opt_str("jobs").map(|j| j.parse().expect("`jobs` should be a number")), diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs index 7026b25d1b984..7266625ff39f8 100644 --- a/src/bootstrap/install.rs +++ b/src/bootstrap/install.rs @@ -14,46 +14,47 @@ use crate::dist::{self, pkgname, sanitize_sh, tmpdir}; use crate::Compiler; use crate::builder::{Builder, RunConfig, ShouldRun, Step}; -use crate::cache::Interned; -use crate::config::Config; +use crate::config::{Config, TargetSelection}; -pub fn install_docs(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_docs(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "docs", "rust-docs", stage, Some(host)); } -pub fn install_std(builder: &Builder<'_>, stage: u32, target: Interned) { +pub fn install_std(builder: &Builder<'_>, stage: u32, target: TargetSelection) { install_sh(builder, "std", "rust-std", stage, Some(target)); } -pub fn install_cargo(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_cargo(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "cargo", "cargo", stage, Some(host)); } -pub fn install_rls(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_rls(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "rls", "rls", stage, Some(host)); } -pub fn install_rust_analyzer(builder: &Builder<'_>, stage: u32, host: Interned) { + +pub fn install_rust_analyzer(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "rust-analyzer", "rust-analyzer", stage, Some(host)); } -pub fn install_clippy(builder: &Builder<'_>, stage: u32, host: Interned) { + +pub fn install_clippy(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "clippy", "clippy", stage, Some(host)); } -pub fn install_miri(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_miri(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "miri", "miri", stage, Some(host)); } -pub fn install_rustfmt(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_rustfmt(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "rustfmt", "rustfmt", stage, Some(host)); } -pub fn install_analysis(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_analysis(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "analysis", "rust-analysis", stage, Some(host)); } pub fn install_src(builder: &Builder<'_>, stage: u32) { install_sh(builder, "src", "rust-src", stage, None); } -pub fn install_rustc(builder: &Builder<'_>, stage: u32, host: Interned) { +pub fn install_rustc(builder: &Builder<'_>, stage: u32, host: TargetSelection) { install_sh(builder, "rustc", "rustc", stage, Some(host)); } @@ -62,7 +63,7 @@ fn install_sh( package: &str, name: &str, stage: u32, - host: Option>, + host: Option, ) { builder.info(&format!("Install {} stage{} ({:?})", package, stage, host)); @@ -150,7 +151,7 @@ macro_rules! install { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct $name { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl $name { diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 783a64c3581f9..05c63734b82ae 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -123,6 +123,7 @@ use std::os::windows::fs::symlink_file; use build_helper::{mtime, output, run, run_suppressed, t, try_run, try_run_suppressed}; use filetime::FileTime; +use crate::config::TargetSelection; use crate::util::{exe, libdir, CiEnv}; mod builder; @@ -187,7 +188,7 @@ const LLVM_TOOLS: &[&str] = &[ #[derive(Eq, PartialOrd, Ord, PartialEq, Clone, Copy, Hash, Debug)] pub struct Compiler { stage: u32, - host: Interned, + host: TargetSelection, } #[derive(PartialEq, Eq, Copy, Clone, Debug)] @@ -236,9 +237,9 @@ pub struct Build { verbosity: usize, // Targets for which to build - build: Interned, - hosts: Vec>, - targets: Vec>, + build: TargetSelection, + hosts: Vec, + targets: Vec, // Stage 0 (downloaded) compiler, lld and cargo or their local rust equivalents initial_rustc: PathBuf, @@ -248,10 +249,10 @@ pub struct Build { // Runtime state filled in later on // C/C++ compilers and archiver for all targets - cc: HashMap, cc::Tool>, - cxx: HashMap, cc::Tool>, - ar: HashMap, PathBuf>, - ranlib: HashMap, PathBuf>, + cc: HashMap, + cxx: HashMap, + ar: HashMap, + ranlib: HashMap, // Miscellaneous crates: HashMap, Crate>, is_sudo: bool, @@ -259,7 +260,7 @@ pub struct Build { delayed_failures: RefCell>, prerelease_version: Cell>, tool_artifacts: - RefCell, HashMap)>>>, + RefCell)>>>, } #[derive(Debug)] @@ -365,7 +366,7 @@ impl Build { output( Command::new(&config.initial_rustc) .arg("--target") - .arg(config.build) + .arg(config.build.rustc_target_arg()) .arg("--print") .arg("target-libdir"), ) @@ -453,7 +454,7 @@ impl Build { } pub fn build_triple(&self) -> &[Interned] { - unsafe { slice::from_raw_parts(&self.build, 1) } + slice::from_ref(&self.build.triple) } /// Executes the entire build, as configured by the flags and configuration. @@ -558,7 +559,10 @@ impl Build { } fn tools_dir(&self, compiler: Compiler) -> PathBuf { - let out = self.out.join(&*compiler.host).join(format!("stage{}-tools-bin", compiler.stage)); + let out = self + .out + .join(&*compiler.host.triple) + .join(format!("stage{}-tools-bin", compiler.stage)); t!(fs::create_dir_all(&out)); out } @@ -575,54 +579,54 @@ impl Build { Mode::ToolBootstrap => "-bootstrap-tools", Mode::ToolStd | Mode::ToolRustc => "-tools", }; - self.out.join(&*compiler.host).join(format!("stage{}{}", compiler.stage, suffix)) + self.out.join(&*compiler.host.triple).join(format!("stage{}{}", compiler.stage, suffix)) } /// Returns the root output directory for all Cargo output in a given stage, /// running a particular compiler, whether or not we're building the /// standard library, and targeting the specified architecture. - fn cargo_out(&self, compiler: Compiler, mode: Mode, target: Interned) -> PathBuf { - self.stage_out(compiler, mode).join(&*target).join(self.cargo_dir()) + fn cargo_out(&self, compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf { + self.stage_out(compiler, mode).join(&*target.triple).join(self.cargo_dir()) } /// Root output directory for LLVM compiled for `target` /// /// Note that if LLVM is configured externally then the directory returned /// will likely be empty. - fn llvm_out(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("llvm") + fn llvm_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("llvm") } - fn lld_out(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("lld") + fn lld_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("lld") } /// Output directory for all documentation for a target - fn doc_out(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("doc") + fn doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("doc") } /// Output directory for all documentation for a target - fn compiler_doc_out(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("compiler-doc") + fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("compiler-doc") } /// Output directory for some generated md crate documentation for a target (temporary) - fn md_doc_out(&self, target: Interned) -> Interned { - INTERNER.intern_path(self.out.join(&*target).join("md-doc")) + fn md_doc_out(&self, target: TargetSelection) -> Interned { + INTERNER.intern_path(self.out.join(&*target.triple).join("md-doc")) } /// Output directory for all crate documentation for a target (temporary) /// /// The artifacts here are then copied into `doc_out` above. - fn crate_doc_out(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("crate-docs") + fn crate_doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("crate-docs") } /// Returns `true` if no custom `llvm-config` is set for the specified target. /// /// If no custom `llvm-config` was specified then Rust's llvm will be used. - fn is_rust_llvm(&self, target: Interned) -> bool { + fn is_rust_llvm(&self, target: TargetSelection) -> bool { match self.config.target_config.get(&target) { Some(ref c) => c.llvm_config.is_none(), None => true, @@ -630,13 +634,13 @@ impl Build { } /// Returns the path to `FileCheck` binary for the specified target - fn llvm_filecheck(&self, target: Interned) -> PathBuf { + fn llvm_filecheck(&self, target: TargetSelection) -> PathBuf { let target_config = self.config.target_config.get(&target); if let Some(s) = target_config.and_then(|c| c.llvm_filecheck.as_ref()) { s.to_path_buf() } else if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) { let llvm_bindir = output(Command::new(s).arg("--bindir")); - let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", &*target)); + let filecheck = Path::new(llvm_bindir.trim()).join(exe("FileCheck", target)); if filecheck.exists() { filecheck } else { @@ -644,7 +648,7 @@ impl Build { // llvm subdirectory of the libdir. let llvm_libdir = output(Command::new(s).arg("--libdir")); let lib_filecheck = - Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", &*target)); + Path::new(llvm_libdir.trim()).join("llvm").join(exe("FileCheck", target)); if lib_filecheck.exists() { lib_filecheck } else { @@ -669,18 +673,18 @@ impl Build { } else { base }; - base.join("bin").join(exe("FileCheck", &*target)) + base.join("bin").join(exe("FileCheck", target)) } } /// Directory for libraries built from C/C++ code and shared between stages. - fn native_dir(&self, target: Interned) -> PathBuf { - self.out.join(&*target).join("native") + fn native_dir(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("native") } /// Root output directory for rust_test_helpers library compiled for /// `target` - fn test_helpers_out(&self, target: Interned) -> PathBuf { + fn test_helpers_out(&self, target: TargetSelection) -> PathBuf { self.native_dir(target).join("rust-test-helpers") } @@ -693,7 +697,7 @@ impl Build { /// Returns the libdir of the snapshot compiler. fn rustc_snapshot_libdir(&self) -> PathBuf { - self.rustc_snapshot_sysroot().join(libdir(&self.config.build)) + self.rustc_snapshot_sysroot().join(libdir(self.config.build)) } /// Returns the sysroot of the snapshot compiler. @@ -791,13 +795,13 @@ impl Build { } /// Returns the path to the C compiler for the target specified. - fn cc(&self, target: Interned) -> &Path { + fn cc(&self, target: TargetSelection) -> &Path { self.cc[&target].path() } /// Returns a list of flags to pass to the C compiler for the target /// specified. - fn cflags(&self, target: Interned, which: GitRepo) -> Vec { + fn cflags(&self, target: TargetSelection, which: GitRepo) -> Vec { // Filter out -O and /O (the optimization flags) that we picked up from // cc-rs because the build scripts will determine that for themselves. let mut base = self.cc[&target] @@ -818,7 +822,7 @@ impl Build { // Work around an apparently bad MinGW / GCC optimization, // See: http://lists.llvm.org/pipermail/cfe-dev/2016-December/051980.html // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78936 - if &*target == "i686-pc-windows-gnu" { + if &*target.triple == "i686-pc-windows-gnu" { base.push("-fno-omit-frame-pointer".into()); } @@ -836,17 +840,17 @@ impl Build { } /// Returns the path to the `ar` archive utility for the target specified. - fn ar(&self, target: Interned) -> Option<&Path> { + fn ar(&self, target: TargetSelection) -> Option<&Path> { self.ar.get(&target).map(|p| &**p) } /// Returns the path to the `ranlib` utility for the target specified. - fn ranlib(&self, target: Interned) -> Option<&Path> { + fn ranlib(&self, target: TargetSelection) -> Option<&Path> { self.ranlib.get(&target).map(|p| &**p) } /// Returns the path to the C++ compiler for the target specified. - fn cxx(&self, target: Interned) -> Result<&Path, String> { + fn cxx(&self, target: TargetSelection) -> Result<&Path, String> { match self.cxx.get(&target) { Some(p) => Ok(p.path()), None => { @@ -856,12 +860,12 @@ impl Build { } /// Returns the path to the linker for the given target if it needs to be overridden. - fn linker(&self, target: Interned, can_use_lld: bool) -> Option<&Path> { + fn linker(&self, target: TargetSelection, can_use_lld: bool) -> Option<&Path> { if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.as_ref()) { Some(linker) } else if target != self.config.build - && util::use_host_linker(&target) + && util::use_host_linker(target) && !target.contains("msvc") { Some(self.cc(target)) @@ -873,7 +877,7 @@ impl Build { } /// Returns if this target should statically link the C runtime, if specified - fn crt_static(&self, target: Interned) -> Option { + fn crt_static(&self, target: TargetSelection) -> Option { if target.contains("pc-windows-msvc") { Some(true) } else { @@ -882,7 +886,7 @@ impl Build { } /// Returns the "musl root" for this `target`, if defined - fn musl_root(&self, target: Interned) -> Option<&Path> { + fn musl_root(&self, target: TargetSelection) -> Option<&Path> { self.config .target_config .get(&target) @@ -892,7 +896,7 @@ impl Build { } /// Returns the "musl libdir" for this `target`. - fn musl_libdir(&self, target: Interned) -> Option { + fn musl_libdir(&self, target: TargetSelection) -> Option { let t = self.config.target_config.get(&target)?; if let libdir @ Some(_) = &t.musl_libdir { return libdir.clone(); @@ -901,18 +905,18 @@ impl Build { } /// Returns the sysroot for the wasi target, if defined - fn wasi_root(&self, target: Interned) -> Option<&Path> { + fn wasi_root(&self, target: TargetSelection) -> Option<&Path> { self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p) } /// Returns `true` if this is a no-std `target`, if defined - fn no_std(&self, target: Interned) -> Option { + fn no_std(&self, target: TargetSelection) -> Option { self.config.target_config.get(&target).map(|t| t.no_std) } /// Returns `true` if the target will be tested using the `remote-test-client` /// and `remote-test-server` binaries. - fn remote_tested(&self, target: Interned) -> bool { + fn remote_tested(&self, target: TargetSelection) -> bool { self.qemu_rootfs(target).is_some() || target.contains("android") || env::var_os("TEST_DEVICE_ADDR").is_some() @@ -923,7 +927,7 @@ impl Build { /// /// If `Some` is returned then that means that tests for this target are /// emulated with QEMU and binaries will need to be shipped to the emulator. - fn qemu_rootfs(&self, target: Interned) -> Option<&Path> { + fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> { self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p) } @@ -955,7 +959,7 @@ impl Build { /// /// When all of these conditions are met the build will lift artifacts from /// the previous stage forward. - fn force_use_stage1(&self, compiler: Compiler, target: Interned) -> bool { + fn force_use_stage1(&self, compiler: Compiler, target: TargetSelection) -> bool { !self.config.full_bootstrap && compiler.stage >= 2 && (self.hosts.iter().any(|h| *h == target) || target == self.build) @@ -1065,7 +1069,7 @@ impl Build { self.rust_version() } - fn llvm_link_tools_dynamically(&self, target: Interned) -> bool { + fn llvm_link_tools_dynamically(&self, target: TargetSelection) -> bool { target.contains("linux-gnu") || target.contains("apple-darwin") } diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs index e8ec575ea3746..bc71242b619db 100644 --- a/src/bootstrap/native.rs +++ b/src/bootstrap/native.rs @@ -19,8 +19,8 @@ use std::process::Command; use build_helper::{output, t}; use crate::builder::{Builder, RunConfig, ShouldRun, Step}; -use crate::cache::Interned; use crate::channel; +use crate::config::TargetSelection; use crate::util::{self, exe}; use crate::GitRepo; use build_helper::up_to_date; @@ -41,7 +41,7 @@ pub struct Meta { // if not). pub fn prebuilt_llvm_config( builder: &Builder<'_>, - target: Interned, + target: TargetSelection, ) -> Result { // If we're using a custom LLVM bail out here, but we can only use a // custom LLVM for the build triple. @@ -54,13 +54,14 @@ pub fn prebuilt_llvm_config( let root = "src/llvm-project/llvm"; let out_dir = builder.llvm_out(target); + let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build); if !builder.config.build.contains("msvc") || builder.config.ninja { llvm_config_ret_dir.push("build"); } llvm_config_ret_dir.push("bin"); - let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", &*builder.config.build)); + let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", builder.config.build)); let stamp = out_dir.join("llvm-finished-building"); let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha()); @@ -93,7 +94,7 @@ pub fn prebuilt_llvm_config( #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Llvm { - pub target: Interned, + pub target: TargetSelection, } impl Step for Llvm { @@ -165,8 +166,8 @@ impl Step for Llvm { .define("LLVM_ENABLE_BINDINGS", "OFF") .define("LLVM_ENABLE_Z3_SOLVER", "OFF") .define("LLVM_PARALLEL_COMPILE_JOBS", builder.jobs().to_string()) - .define("LLVM_TARGET_ARCH", target.split('-').next().unwrap()) - .define("LLVM_DEFAULT_TARGET_TRIPLE", target); + .define("LLVM_TARGET_ARCH", target.triple.split('-').next().unwrap()) + .define("LLVM_DEFAULT_TARGET_TRIPLE", target.triple); if !target.contains("netbsd") { cfg.define("LLVM_ENABLE_ZLIB", "ON"); @@ -339,7 +340,7 @@ fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) { fn configure_cmake( builder: &Builder<'_>, - target: Interned, + target: TargetSelection, cfg: &mut cmake::Config, use_compiler_launcher: bool, ) { @@ -350,7 +351,7 @@ fn configure_cmake( if builder.config.ninja { cfg.generator("Ninja"); } - cfg.target(&target).host(&builder.config.build); + cfg.target(&target.triple).host(&builder.config.build.triple); let sanitize_cc = |cc: &Path| { if target.contains("msvc") { @@ -380,7 +381,7 @@ fn configure_cmake( cfg.define("CMAKE_C_COMPILER", sanitize_cc(&wrap_cc)) .define("CMAKE_CXX_COMPILER", sanitize_cc(&wrap_cc)); cfg.env("SCCACHE_PATH", builder.config.ccache.as_ref().unwrap()) - .env("SCCACHE_TARGET", target) + .env("SCCACHE_TARGET", target.triple) .env("SCCACHE_CC", &cc) .env("SCCACHE_CXX", &cxx); @@ -480,7 +481,7 @@ fn configure_cmake( #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Lld { - pub target: Interned, + pub target: TargetSelection, } impl Step for Lld { @@ -553,8 +554,8 @@ impl Step for Lld { // brittle and will break over time. If anyone knows better how to // cross-compile LLD it would be much appreciated to fix this! if target != builder.config.build { - cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build) - .env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target) + cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build.triple) + .env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target.triple) .define( "LLVM_TABLEGEN_EXE", llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION), @@ -574,7 +575,7 @@ impl Step for Lld { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TestHelpers { - pub target: Interned, + pub target: TargetSelection, } impl Step for TestHelpers { @@ -621,8 +622,8 @@ impl Step for TestHelpers { cfg.cargo_metadata(false) .out_dir(&dst) - .target(&target) - .host(&builder.config.build) + .target(&target.triple) + .host(&builder.config.build.triple) .opt_level(0) .warnings(false) .debug(false) @@ -633,7 +634,7 @@ impl Step for TestHelpers { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Sanitizers { - pub target: Interned, + pub target: TargetSelection, } impl Step for Sanitizers { @@ -684,7 +685,7 @@ impl Step for Sanitizers { let mut cfg = cmake::Config::new(&compiler_rt_dir); cfg.profile("Release"); - cfg.define("CMAKE_C_COMPILER_TARGET", self.target); + cfg.define("CMAKE_C_COMPILER_TARGET", self.target.triple); cfg.define("COMPILER_RT_BUILD_BUILTINS", "OFF"); cfg.define("COMPILER_RT_BUILD_CRT", "OFF"); cfg.define("COMPILER_RT_BUILD_LIBFUZZER", "OFF"); @@ -727,7 +728,7 @@ pub struct SanitizerRuntime { /// Returns sanitizers available on a given target. fn supported_sanitizers( out_dir: &Path, - target: Interned, + target: TargetSelection, channel: &str, ) -> Vec { let darwin_libs = |os: &str, components: &[&str]| -> Vec { @@ -753,7 +754,7 @@ fn supported_sanitizers( .collect() }; - match &*target { + match &*target.triple { "aarch64-fuchsia" => common_libs("fuchsia", "aarch64", &["asan"]), "aarch64-unknown-linux-gnu" => { common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan"]) diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs index 3301d41cfeefa..f89bef50de982 100644 --- a/src/bootstrap/sanity.rs +++ b/src/bootstrap/sanity.rs @@ -183,7 +183,11 @@ pub fn check(build: &mut Build) { panic!("the iOS target is only supported on macOS"); } - build.config.target_config.entry(target.clone()).or_insert(Target::from_triple(target)); + build + .config + .target_config + .entry(target.clone()) + .or_insert(Target::from_triple(&target.triple)); if target.contains("-none-") || target.contains("nvptx") { if build.no_std(*target) == Some(false) { diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 1916d96bed71d..b6641180c92c0 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -16,6 +16,7 @@ use build_helper::{self, output, t}; use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; use crate::compile; +use crate::config::TargetSelection; use crate::dist; use crate::flags::Subcommand; use crate::native; @@ -93,7 +94,7 @@ fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Linkcheck { - host: Interned, + host: TargetSelection, } impl Step for Linkcheck { @@ -115,7 +116,7 @@ impl Step for Linkcheck { let _time = util::timeit(&builder); try_run( builder, - builder.tool_cmd(Tool::Linkchecker).arg(builder.out.join(host).join("doc")), + builder.tool_cmd(Tool::Linkchecker).arg(builder.out.join(host.triple).join("doc")), ); } @@ -132,7 +133,7 @@ impl Step for Linkcheck { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Cargotest { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Cargotest { @@ -177,7 +178,7 @@ impl Step for Cargotest { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Cargo { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Cargo { @@ -230,7 +231,7 @@ impl Step for Cargo { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rls { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Rls { @@ -281,7 +282,7 @@ impl Step for Rls { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rustfmt { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Rustfmt { @@ -338,7 +339,7 @@ impl Step for Rustfmt { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Miri { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Miri { @@ -464,7 +465,7 @@ impl Step for Miri { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CompiletestTest { - host: Interned, + host: TargetSelection, } impl Step for CompiletestTest { @@ -501,7 +502,7 @@ impl Step for CompiletestTest { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Clippy { stage: u32, - host: Interned, + host: TargetSelection, } impl Step for Clippy { @@ -542,8 +543,10 @@ impl Step for Clippy { cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler)); cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler)); let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir()); - let target_libs = - builder.stage_out(compiler, Mode::ToolRustc).join(&self.host).join(builder.cargo_dir()); + let target_libs = builder + .stage_out(compiler, Mode::ToolRustc) + .join(&self.host.triple) + .join(builder.cargo_dir()); cargo.env("HOST_LIBS", host_libs); cargo.env("TARGET_LIBS", target_libs); // clippy tests need to find the driver @@ -607,7 +610,7 @@ impl Step for RustdocTheme { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustdocJSStd { - pub target: Interned, + pub target: TargetSelection, } impl Step for RustdocJSStd { @@ -646,8 +649,8 @@ impl Step for RustdocJSStd { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustdocJSNotStd { - pub host: Interned, - pub target: Interned, + pub host: TargetSelection, + pub target: TargetSelection, pub compiler: Compiler, } @@ -683,8 +686,8 @@ impl Step for RustdocJSNotStd { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustdocUi { - pub host: Interned, - pub target: Interned, + pub host: TargetSelection, + pub target: TargetSelection, pub compiler: Compiler, } @@ -785,8 +788,8 @@ impl Step for ExpandYamlAnchors { } } -fn testdir(builder: &Builder<'_>, host: Interned) -> PathBuf { - builder.out.join(host).join("test") +fn testdir(builder: &Builder<'_>, host: TargetSelection) -> PathBuf { + builder.out.join(host.triple).join("test") } macro_rules! default_test { @@ -855,7 +858,7 @@ macro_rules! test_definitions { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct $name { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for $name { @@ -943,7 +946,7 @@ default_test!(Assembly { path: "src/test/assembly", mode: "assembly", suite: "as #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct Compiletest { compiler: Compiler, - target: Interned, + target: TargetSelection, mode: &'static str, suite: &'static str, path: &'static str, @@ -1023,8 +1026,8 @@ impl Step for Compiletest { cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite)); cmd.arg("--stage-id").arg(format!("stage{}-{}", compiler.stage, target)); cmd.arg("--mode").arg(mode); - cmd.arg("--target").arg(target); - cmd.arg("--host").arg(&*compiler.host); + cmd.arg("--target").arg(target.rustc_target_arg()); + cmd.arg("--host").arg(&*compiler.host.triple); cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.build)); if builder.config.cmd.bless() { @@ -1543,7 +1546,7 @@ impl Step for RustcGuide { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateLibrustc { compiler: Compiler, - target: Interned, + target: TargetSelection, test_kind: TestKind, krate: Interned, } @@ -1589,7 +1592,7 @@ impl Step for CrateLibrustc { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateNotDefault { compiler: Compiler, - target: Interned, + target: TargetSelection, test_kind: TestKind, krate: &'static str, } @@ -1638,7 +1641,7 @@ impl Step for CrateNotDefault { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Crate { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, pub mode: Mode, pub test_kind: TestKind, pub krate: Interned, @@ -1750,17 +1753,17 @@ impl Step for Crate { if target.contains("emscripten") { cargo.env( - format!("CARGO_TARGET_{}_RUNNER", envify(&target)), + format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), builder.config.nodejs.as_ref().expect("nodejs not configured"), ); } else if target.starts_with("wasm32") { let node = builder.config.nodejs.as_ref().expect("nodejs not configured"); let runner = format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display()); - cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target)), &runner); + cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner); } else if builder.remote_tested(target) { cargo.env( - format!("CARGO_TARGET_{}_RUNNER", envify(&target)), + format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()), ); } @@ -1776,7 +1779,7 @@ impl Step for Crate { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateRustdoc { - host: Interned, + host: TargetSelection, test_kind: TestKind, } @@ -1883,7 +1886,7 @@ impl Step for CrateRustdoc { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct RemoteCopyLibs { compiler: Compiler, - target: Interned, + target: TargetSelection, } impl Step for RemoteCopyLibs { @@ -1911,7 +1914,7 @@ impl Step for RemoteCopyLibs { // Spawn the emulator and wait for it to come online let tool = builder.tool_exe(Tool::RemoteTestClient); let mut cmd = Command::new(&tool); - cmd.arg("spawn-emulator").arg(target).arg(&server).arg(builder.out.join("tmp")); + cmd.arg("spawn-emulator").arg(target.triple).arg(&server).arg(builder.out.join("tmp")); if let Some(rootfs) = builder.qemu_rootfs(target) { cmd.arg(rootfs); } @@ -1966,7 +1969,9 @@ impl Step for Distcheck { .current_dir(&dir), ); builder.run( - Command::new(build_helper::make(&builder.config.build)).arg("check").current_dir(&dir), + Command::new(build_helper::make(&builder.config.build.triple)) + .arg("check") + .current_dir(&dir), ); // Now make sure that rust-src has all of libstd's dependencies diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 450b534d5dfdb..f66061975d64d 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -7,10 +7,10 @@ use std::process::{exit, Command}; use build_helper::t; use crate::builder::{Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step}; -use crate::cache::Interned; use crate::channel; use crate::channel::GitInfo; use crate::compile; +use crate::config::TargetSelection; use crate::toolstate::ToolState; use crate::util::{add_dylib_path, exe}; use crate::Compiler; @@ -25,7 +25,7 @@ pub enum SourceType { #[derive(Debug, Clone, Hash, PartialEq, Eq)] struct ToolBuild { compiler: Compiler, - target: Interned, + target: TargetSelection, tool: &'static str, path: &'static str, mode: Mode, @@ -111,7 +111,7 @@ impl Step for ToolBuild { .and_then(|p| p.file_name()) .and_then(|p| p.to_str()) .unwrap(); - if maybe_target != &*target { + if maybe_target != &*target.triple { continue; } } @@ -208,8 +208,8 @@ impl Step for ToolBuild { } } else { let cargo_out = - builder.cargo_out(compiler, self.mode, target).join(exe(tool, &compiler.host)); - let bin = builder.tools_dir(compiler).join(exe(tool, &compiler.host)); + builder.cargo_out(compiler, self.mode, target).join(exe(tool, compiler.host)); + let bin = builder.tools_dir(compiler).join(exe(tool, compiler.host)); builder.copy(&cargo_out, &bin); Some(bin) } @@ -220,7 +220,7 @@ pub fn prepare_tool_cargo( builder: &Builder<'_>, compiler: Compiler, mode: Mode, - target: Interned, + target: TargetSelection, command: &'static str, path: &'static str, source_type: SourceType, @@ -303,7 +303,7 @@ macro_rules! bootstrap_tool { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct $name { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for $name { @@ -416,7 +416,7 @@ impl Step for ErrorIndex { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RemoteTestServer { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for RemoteTestServer { @@ -476,7 +476,7 @@ impl Step for Rustdoc { if !target_compiler.is_snapshot(builder) { panic!("rustdoc in stage 0 must be snapshot rustdoc"); } - return builder.initial_rustc.with_file_name(exe("rustdoc", &target_compiler.host)); + return builder.initial_rustc.with_file_name(exe("rustdoc", target_compiler.host)); } let target = target_compiler.host; // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise @@ -514,14 +514,14 @@ impl Step for Rustdoc { // rustdoc a different name. let tool_rustdoc = builder .cargo_out(build_compiler, Mode::ToolRustc, target) - .join(exe("rustdoc_tool_binary", &target_compiler.host)); + .join(exe("rustdoc_tool_binary", target_compiler.host)); // don't create a stage0-sysroot/bin directory. if target_compiler.stage > 0 { let sysroot = builder.sysroot(target_compiler); let bindir = sysroot.join("bin"); t!(fs::create_dir_all(&bindir)); - let bin_rustdoc = bindir.join(exe("rustdoc", &*target_compiler.host)); + let bin_rustdoc = bindir.join(exe("rustdoc", target_compiler.host)); let _ = fs::remove_file(&bin_rustdoc); builder.copy(&tool_rustdoc, &bin_rustdoc); bin_rustdoc @@ -534,7 +534,7 @@ impl Step for Rustdoc { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct Cargo { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, } impl Step for Cargo { @@ -583,7 +583,7 @@ macro_rules! tool_extended { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct $name { pub compiler: Compiler, - pub target: Interned, + pub target: TargetSelection, pub extra_features: Vec, } diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs index 2bc6f1939d97b..a307ef39d03a8 100644 --- a/src/bootstrap/util.rs +++ b/src/bootstrap/util.rs @@ -14,17 +14,16 @@ use std::time::Instant; use build_helper::t; use crate::builder::Builder; -use crate::cache::Interned; -use crate::config::Config; +use crate::config::{Config, TargetSelection}; /// Returns the `name` as the filename of a static library for `target`. -pub fn staticlib(name: &str, target: &str) -> String { +pub fn staticlib(name: &str, target: TargetSelection) -> String { if target.contains("windows") { format!("{}.lib", name) } else { format!("lib{}.a", name) } } /// Given an executable called `name`, return the filename for the /// executable for a particular target. -pub fn exe(name: &str, target: &str) -> String { +pub fn exe(name: &str, target: TargetSelection) -> String { if target.contains("windows") { format!("{}.exe", name) } else { name.to_string() } } @@ -35,7 +34,7 @@ pub fn is_dylib(name: &str) -> bool { /// Returns the corresponding relative library directory that the compiler's /// dylibs will be found in. -pub fn libdir(target: &str) -> &'static str { +pub fn libdir(target: TargetSelection) -> &'static str { if target.contains("windows") { "bin" } else { "lib" } } @@ -294,7 +293,7 @@ pub fn forcing_clang_based_tests() -> bool { } } -pub fn use_host_linker(target: &Interned) -> bool { +pub fn use_host_linker(target: TargetSelection) -> bool { // FIXME: this information should be gotten by checking the linker flavor // of the rustc target !(target.contains("emscripten") From 9a3a31a153be84b9be33859a25b56be9cab8177d Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Sun, 12 Jul 2020 22:38:37 -0400 Subject: [PATCH 58/62] Added tests for volatile and nearbyint intrinsics --- src/test/codegen/intrinsics/nearby.rs | 18 ++++++++ src/test/codegen/intrinsics/volatile.rs | 55 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/test/codegen/intrinsics/nearby.rs create mode 100644 src/test/codegen/intrinsics/volatile.rs diff --git a/src/test/codegen/intrinsics/nearby.rs b/src/test/codegen/intrinsics/nearby.rs new file mode 100644 index 0000000000000..520fe2f1886eb --- /dev/null +++ b/src/test/codegen/intrinsics/nearby.rs @@ -0,0 +1,18 @@ +#![crate_type = "lib"] +#![feature(core_intrinsics)] + +use std::intrinsics; + +// CHECK-LABEL: @nearbyintf32 +#[no_mangle] +pub unsafe fn nearbyintf32(a: f32) -> f32 { + // CHECK: llvm.nearbyint.f32 + intrinsics::nearbyintf32(a) +} + +// CHECK-LABEL: @nearbyintf64 +#[no_mangle] +pub unsafe fn nearbyintf64(a: f64) -> f64 { + // CHECK: llvm.nearbyint.f64 + intrinsics::nearbyintf64(a) +} diff --git a/src/test/codegen/intrinsics/volatile.rs b/src/test/codegen/intrinsics/volatile.rs new file mode 100644 index 0000000000000..1970517e73262 --- /dev/null +++ b/src/test/codegen/intrinsics/volatile.rs @@ -0,0 +1,55 @@ +// compile-flags: -C no-prepopulate-passes + +#![crate_type = "lib"] +#![feature(core_intrinsics)] + +use std::intrinsics; + +// CHECK-LABEL: @volatile_copy_memory +#[no_mangle] +pub unsafe fn volatile_copy_memory(a: *mut u8, b: *const u8) { + // CHECK: llvm.memmove.p0i8.p0i8.{{\w*(.*true)}} + intrinsics::volatile_copy_memory(a, b, 1) +} + +// CHECK-LABEL: @volatile_copy_nonoverlapping_memory +#[no_mangle] +pub unsafe fn volatile_copy_nonoverlapping_memory(a: *mut u8, b: *const u8) { + // CHECK: llvm.memcpy.p0i8.p0i8.{{\w*(.*true)}} + intrinsics::volatile_copy_nonoverlapping_memory(a, b, 1) +} + +// CHECK-LABEL: @volatile_set_memory +#[no_mangle] +pub unsafe fn volatile_set_memory(a: *mut u8, b: u8) { + // CHECK: llvm.memset.p0i8.{{\w*(.*true)}} + intrinsics::volatile_set_memory(a, b, 1) +} + +// CHECK-LABEL: @volatile_load +#[no_mangle] +pub unsafe fn volatile_load(a: *const u8) -> u8 { + // CHECK: load volatile + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_store +#[no_mangle] +pub unsafe fn volatile_store(a: *mut u8, b: u8) { + // CHECK: store volatile + intrinsics::volatile_store(a, b) +} + +// CHECK-LABEL: @unaligned_volatile_load +#[no_mangle] +pub unsafe fn unaligned_volatile_load(a: *const u8) -> u8 { + // CHECK: load volatile + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_store +#[no_mangle] +pub unsafe fn unaligned_volatile_store(a: *mut u8, b: u8) { + // CHECK: store volatile + intrinsics::unaligned_volatile_store(a, b) +} From 5ff7e1ad2cc438bd1ecb29f6b4414d368115ca31 Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Mon, 13 Jul 2020 00:47:09 -0400 Subject: [PATCH 59/62] Added ui tests for volatile and nearby intrinsics --- src/test/codegen/intrinsics/volatile_order.rs | 18 ++++++++ src/test/ui/intrinsics/intrinsic-nearby.rs | 11 +++++ src/test/ui/intrinsics/intrinsic-volatile.rs | 43 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/test/codegen/intrinsics/volatile_order.rs create mode 100644 src/test/ui/intrinsics/intrinsic-nearby.rs create mode 100644 src/test/ui/intrinsics/intrinsic-volatile.rs diff --git a/src/test/codegen/intrinsics/volatile_order.rs b/src/test/codegen/intrinsics/volatile_order.rs new file mode 100644 index 0000000000000..29331219ba6ee --- /dev/null +++ b/src/test/codegen/intrinsics/volatile_order.rs @@ -0,0 +1,18 @@ +#![crate_type = "lib"] +#![feature(core_intrinsics)] + +use std::intrinsics::*; + +pub unsafe fn test_volatile_order() { + let mut a: Box = Box::new(0); + // CHECK: load volatile + let x = volatile_load(&*a); + // CHECK: load volatile + let x = volatile_load(&*a); + // CHECK: store volatile + volatile_store(&mut *a, 12); + // CHECK: store volatile + unaligned_volatile_store(&mut *a, 12); + // CHECK: llvm.memset.p0i8 + volatile_set_memory(&mut *a, 12, 1) +} diff --git a/src/test/ui/intrinsics/intrinsic-nearby.rs b/src/test/ui/intrinsics/intrinsic-nearby.rs new file mode 100644 index 0000000000000..7b1d1eeaadbd0 --- /dev/null +++ b/src/test/ui/intrinsics/intrinsic-nearby.rs @@ -0,0 +1,11 @@ +// run-pass +#![feature(core_intrinsics)] + +use std::intrinsics::*; + +fn main() { + unsafe { + assert_eq!(nearbyintf32(5.234f32), 5f32); + assert_eq!(nearbyintf64(6.777f64), 7f64); + } +} diff --git a/src/test/ui/intrinsics/intrinsic-volatile.rs b/src/test/ui/intrinsics/intrinsic-volatile.rs new file mode 100644 index 0000000000000..45bfe5ee3066d --- /dev/null +++ b/src/test/ui/intrinsics/intrinsic-volatile.rs @@ -0,0 +1,43 @@ +// run-pass +#![feature(core_intrinsics)] + +use std::intrinsics::*; + +pub fn main() { + unsafe { + let mut x: Box = Box::new(0); + let mut y: Box = Box::new(0); + + // test volatile load + assert_eq!(volatile_load(&*x), 0); + *x = 1; + assert_eq!(volatile_load(&*x), 1); + + // test volatile store + volatile_store(&mut *x, 2); + assert_eq!(*x, 2); + + // test volatile copy memory + volatile_copy_memory(&mut *y, &*x, 1); + assert_eq!(*y, 2); + + // test volatile copy non-overlapping memory + *x = 3; + volatile_copy_nonoverlapping_memory(&mut *y, &*x, 1); + assert_eq!(*y, 3); + + // test volatile set memory + volatile_set_memory(&mut *x, 4, 1); + assert_eq!(*x, 4); + + // test unaligned volatile load + let arr: [u8; 3] = [1, 2, 3]; + let ptr = arr[1..].as_ptr() as *const u16; + assert_eq!(unaligned_volatile_load(ptr), u16::from_ne_bytes([arr[1], arr[2]])); + + // test unaligned volatile store + let ptr = arr[1..].as_ptr() as *mut u16; + unaligned_volatile_store(ptr, 0); + assert_eq!(arr, [1, 0, 0]); + } +} From eac809244e881b9d0437492fb4aeac55fc93e000 Mon Sep 17 00:00:00 2001 From: Teddy_Wang Date: Mon, 13 Jul 2020 01:52:10 -0400 Subject: [PATCH 60/62] Removed trailing whitespace --- src/test/ui/intrinsics/intrinsic-volatile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/ui/intrinsics/intrinsic-volatile.rs b/src/test/ui/intrinsics/intrinsic-volatile.rs index 45bfe5ee3066d..e509363382f34 100644 --- a/src/test/ui/intrinsics/intrinsic-volatile.rs +++ b/src/test/ui/intrinsics/intrinsic-volatile.rs @@ -34,7 +34,7 @@ pub fn main() { let arr: [u8; 3] = [1, 2, 3]; let ptr = arr[1..].as_ptr() as *const u16; assert_eq!(unaligned_volatile_load(ptr), u16::from_ne_bytes([arr[1], arr[2]])); - + // test unaligned volatile store let ptr = arr[1..].as_ptr() as *mut u16; unaligned_volatile_store(ptr, 0); From e3ae4c7345cfd06b06c6996536d7c158ce6970db Mon Sep 17 00:00:00 2001 From: PankajChaudhary5 Date: Mon, 13 Jul 2020 14:05:48 +0530 Subject: [PATCH 61/62] Added proper explanation of ErrorCode-E0688 --- src/librustc_error_codes/error_codes.rs | 2 +- src/librustc_error_codes/error_codes/E0688.md | 36 +++++++++++++++++++ src/test/ui/in-band-lifetimes/E0688.stderr | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/librustc_error_codes/error_codes/E0688.md diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs index 225ede851b4d9..d1cc69b1ab8e2 100644 --- a/src/librustc_error_codes/error_codes.rs +++ b/src/librustc_error_codes/error_codes.rs @@ -380,6 +380,7 @@ E0668: include_str!("./error_codes/E0668.md"), E0669: include_str!("./error_codes/E0669.md"), E0670: include_str!("./error_codes/E0670.md"), E0671: include_str!("./error_codes/E0671.md"), +E0688: include_str!("./error_codes/E0688.md"), E0689: include_str!("./error_codes/E0689.md"), E0690: include_str!("./error_codes/E0690.md"), E0691: include_str!("./error_codes/E0691.md"), @@ -600,7 +601,6 @@ E0751: include_str!("./error_codes/E0751.md"), // E0645, // trait aliases not finished E0667, // `impl Trait` in projections E0687, // in-band lifetimes cannot be used in `fn`/`Fn` syntax - E0688, // in-band lifetimes cannot be mixed with explicit lifetime binders // E0694, // an unknown tool name found in scoped attributes E0696, // `continue` pointing to a labeled block // E0702, // replaced with a generic attribute input check diff --git a/src/librustc_error_codes/error_codes/E0688.md b/src/librustc_error_codes/error_codes/E0688.md new file mode 100644 index 0000000000000..db50f490208f4 --- /dev/null +++ b/src/librustc_error_codes/error_codes/E0688.md @@ -0,0 +1,36 @@ +In-band lifetimes were mixed with explicit lifetime binders. + +Erroneous code example: + +```compile_fail,E0688 +#![feature(in_band_lifetimes)] + +fn foo<'a>(x: &'a u32, y: &'b u32) {} // error! + +struct Foo<'a> { x: &'a u32 } + +impl Foo<'a> { + fn bar<'b>(x: &'a u32, y: &'b u32, z: &'c u32) {} // error! +} + +impl<'b> Foo<'a> { // error! + fn baz() {} +} +``` + +In-band lifetimes cannot be mixed with explicit lifetime binders. +For example: + +``` +fn foo<'a, 'b>(x: &'a u32, y: &'b u32) {} // ok! + +struct Foo<'a> { x: &'a u32 } + +impl<'a> Foo<'a> { + fn bar<'b,'c>(x: &'a u32, y: &'b u32, z: &'c u32) {} // ok! +} + +impl<'a> Foo<'a> { // ok! + fn baz() {} +} +``` diff --git a/src/test/ui/in-band-lifetimes/E0688.stderr b/src/test/ui/in-band-lifetimes/E0688.stderr index 0078cd58001e3..8662110679b5f 100644 --- a/src/test/ui/in-band-lifetimes/E0688.stderr +++ b/src/test/ui/in-band-lifetimes/E0688.stderr @@ -24,3 +24,4 @@ LL | impl<'b> Foo<'a> { error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0688`. \ No newline at end of file From 8df79fcc76814d0a47d64446eafbbb881f7204ca Mon Sep 17 00:00:00 2001 From: wangtheo Date: Mon, 13 Jul 2020 09:44:48 -0400 Subject: [PATCH 62/62] Remove trailing whitespace --- src/test/ui/intrinsics/intrinsic-volatile.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/ui/intrinsics/intrinsic-volatile.rs b/src/test/ui/intrinsics/intrinsic-volatile.rs index e509363382f34..7b2c825a2084b 100644 --- a/src/test/ui/intrinsics/intrinsic-volatile.rs +++ b/src/test/ui/intrinsics/intrinsic-volatile.rs @@ -1,4 +1,5 @@ // run-pass + #![feature(core_intrinsics)] use std::intrinsics::*;