Add support for 'extern const fn'

This works just as you might expect - an 'extern const fn' is a 'const
fn' that is callable from foreign code.

Currently, panicking is not allowed in consts. When RFC 2345 is
stabilized, then panicking in an 'extern const fn' will produce a
compile-time error when invoked at compile time, and an abort when
invoked at runtime.

Since this is extending the language (we're allowing the `const` keyword
in a new context), I believe that this will need an FCP. However, it's a
very minor change, so I didn't think that filing an RFC was necessary.

This will allow libc (and other FFI crates) to make many functions
`const`, without having to give up on making them `extern` as well.
This commit is contained in:
Aaron Hill 2019-09-29 19:22:18 -04:00
parent 7130fc54e0
commit 73b50d211b
No known key found for this signature in database
GPG key ID: B4087E510E98B164
18 changed files with 302 additions and 21 deletions

View file

@ -519,6 +519,9 @@ declare_features! (
/// Allows the use of or-patterns (e.g., `0 | 1`).
(active, or_patterns, "1.38.0", Some(54883), None),
/// Allows the definition of `const extern fn` and `const unsafe extern fn`.
(active, const_extern_fn, "1.40.0", Some(64926), None),
// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------

View file

@ -821,6 +821,7 @@ pub fn check_crate(krate: &ast::Crate,
gate_all!(async_closure, "async closures are unstable");
gate_all!(yields, generators, "yield syntax is experimental");
gate_all!(or_patterns, "or-patterns syntax is experimental");
gate_all!(const_extern_fn, "`const extern fn` definitions are unstable");
visit::walk_crate(&mut visitor, krate);
}

View file

@ -57,6 +57,8 @@ pub struct GatedSpans {
pub yields: Lock<Vec<Span>>,
/// Spans collected for gating `or_patterns`, e.g. `Some(Foo | Bar)`.
pub or_patterns: Lock<Vec<Span>>,
/// Spans collected for gating `const_extern_fn`, e.g. `const extern fn foo`.
pub const_extern_fn: Lock<Vec<Span>>,
}
/// Info about a parsing session.

View file

@ -1486,6 +1486,15 @@ impl<'a> Parser<'a> {
Ok(())
}
/// Parses `extern` followed by an optional ABI string, or nothing.
fn parse_extern_abi(&mut self) -> PResult<'a, Abi> {
if self.eat_keyword(kw::Extern) {
Ok(self.parse_opt_abi()?.unwrap_or(Abi::C))
} else {
Ok(Abi::Rust)
}
}
/// Parses a string as an ABI spec on an extern type or module. Consumes
/// the `extern` keyword, if one is found.
fn parse_opt_abi(&mut self) -> PResult<'a, Option<Abi>> {

View file

@ -149,17 +149,23 @@ impl<'a> Parser<'a> {
}
if self.eat_keyword(kw::Const) {
let const_span = self.prev_span;
if self.check_keyword(kw::Fn)
|| (self.check_keyword(kw::Unsafe)
&& self.is_keyword_ahead(1, &[kw::Fn])) {
if [kw::Fn, kw::Unsafe, kw::Extern].iter().any(|k| self.check_keyword(*k)) {
// CONST FUNCTION ITEM
let unsafety = self.parse_unsafety();
self.bump();
if self.check_keyword(kw::Extern) {
self.sess.gated_spans.const_extern_fn.borrow_mut().push(
lo.to(self.token.span)
);
}
let abi = self.parse_extern_abi()?;
let header = FnHeader {
unsafety,
asyncness: respan(const_span, IsAsync::NotAsync),
constness: respan(const_span, Constness::Const),
abi: Abi::Rust,
abi,
};
return self.parse_item_fn(lo, visibility, attrs, header);
}
@ -257,11 +263,7 @@ impl<'a> Parser<'a> {
self.bump(); // `unsafe`
// `{` is also expected after `unsafe`; in case of error, include it in the diagnostic.
self.check(&token::OpenDelim(token::Brace));
let abi = if self.eat_keyword(kw::Extern) {
self.parse_opt_abi()?.unwrap_or(Abi::C)
} else {
Abi::Rust
};
let abi = self.parse_extern_abi()?;
self.expect_keyword(kw::Fn)?;
let fn_span = self.prev_span;
let header = FnHeader {
@ -834,11 +836,7 @@ impl<'a> Parser<'a> {
let (constness, unsafety, abi) = if is_const_fn {
(respan(const_span, Constness::Const), unsafety, Abi::Rust)
} else {
let abi = if self.eat_keyword(kw::Extern) {
self.parse_opt_abi()?.unwrap_or(Abi::C)
} else {
Abi::Rust
};
let abi = self.parse_extern_abi()?;
(respan(self.prev_span, Constness::NotConst), unsafety, abi)
};
if !self.eat_keyword(kw::Fn) {
@ -1278,14 +1276,30 @@ impl<'a> Parser<'a> {
// Treat `const` as `static` for error recovery, but don't add it to expected tokens.
if self.check_keyword(kw::Static) || self.token.is_keyword(kw::Const) {
if self.token.is_keyword(kw::Const) {
self.diagnostic()
.struct_span_err(self.token.span, "extern items cannot be `const`")
.span_suggestion(
let mut err = self
.struct_span_err(self.token.span, "extern items cannot be `const`");
// The user wrote 'const fn'
if self.is_keyword_ahead(1, &[kw::Fn, kw::Unsafe]) {
err.emit();
// Consume `const`
self.bump();
// Consume `unsafe` if present, since `extern` blocks
// don't allow it. This will leave behind a plain 'fn'
self.eat_keyword(kw::Unsafe);
// Treat 'const fn` as a plain `fn` for error recovery purposes.
// We've already emitted an error, so compilation is guaranteed
// to fail
return Ok(self.parse_item_foreign_fn(visibility, lo, attrs, extern_sp)?);
}
err.span_suggestion(
self.token.span,
"try using a static value",
"static".to_owned(),
Applicability::MachineApplicable
).emit();
);
err.emit();
}
self.bump(); // `static` or `const`
return Ok(self.parse_item_foreign_static(visibility, lo, attrs)?);

View file

@ -196,6 +196,7 @@ symbols! {
console,
const_compare_raw_pointers,
const_constructor,
const_extern_fn,
const_fn,
const_fn_union,
const_generics,

View file

@ -0,0 +1,23 @@
#![feature(const_extern_fn)]
extern "C" {
fn regular_in_block();
}
const extern fn bar() {
unsafe {
regular_in_block();
//~^ ERROR: cannot call functions with `"C"` abi in `min_const_fn`
}
}
extern fn regular() {}
const extern fn foo() {
unsafe {
regular();
//~^ ERROR: cannot call functions with `"C"` abi in `min_const_fn`
}
}
fn main() {}

View file

@ -0,0 +1,21 @@
error[E0723]: cannot call functions with `"C"` abi in `min_const_fn`
--> $DIR/const-extern-fn-call-extern-fn.rs:9:9
|
LL | regular_in_block();
| ^^^^^^^^^^^^^^^^^^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error[E0723]: cannot call functions with `"C"` abi in `min_const_fn`
--> $DIR/const-extern-fn-call-extern-fn.rs:18:9
|
LL | regular();
| ^^^^^^^^^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0723`.

View file

@ -0,0 +1,13 @@
#![feature(const_extern_fn)]
const extern fn unsize(x: &[u8; 3]) -> &[u8] { x }
//~^ ERROR unsizing casts are not allowed in const fn
const unsafe extern "C" fn closure() -> fn() { || {} }
//~^ ERROR function pointers in const fn are unstable
const unsafe extern fn use_float() { 1.0 + 1.0; }
//~^ ERROR only int, `bool` and `char` operations are stable in const fn
const extern "C" fn ptr_cast(val: *const u8) { val as usize; }
//~^ ERROR casting pointers to ints is unstable in const fn
fn main() {}

View file

@ -0,0 +1,39 @@
error[E0723]: unsizing casts are not allowed in const fn
--> $DIR/const-extern-fn-min-const-fn.rs:3:48
|
LL | const extern fn unsize(x: &[u8; 3]) -> &[u8] { x }
| ^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error[E0723]: function pointers in const fn are unstable
--> $DIR/const-extern-fn-min-const-fn.rs:5:41
|
LL | const unsafe extern "C" fn closure() -> fn() { || {} }
| ^^^^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error[E0723]: only int, `bool` and `char` operations are stable in const fn
--> $DIR/const-extern-fn-min-const-fn.rs:7:38
|
LL | const unsafe extern fn use_float() { 1.0 + 1.0; }
| ^^^^^^^^^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error[E0723]: casting pointers to ints is unstable in const fn
--> $DIR/const-extern-fn-min-const-fn.rs:9:48
|
LL | const extern "C" fn ptr_cast(val: *const u8) { val as usize; }
| ^^^^^^^^^^^^
|
= note: for more information, see issue https://github.com/rust-lang/rust/issues/57563
= help: add `#![feature(const_fn)]` to the crate attributes to enable
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0723`.

View file

@ -0,0 +1,10 @@
#![feature(const_extern_fn)]
const unsafe extern fn foo() -> usize { 5 }
fn main() {
let a: [u8; foo()];
//~^ ERROR call to unsafe function is unsafe and requires unsafe function or block
foo();
//~^ ERROR call to unsafe function is unsafe and requires unsafe function or block
}

View file

@ -0,0 +1,19 @@
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> $DIR/const-extern-fn-requires-unsafe.rs:8:5
|
LL | foo();
| ^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
--> $DIR/const-extern-fn-requires-unsafe.rs:6:17
|
LL | let a: [u8; foo()];
| ^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0133`.

View file

@ -0,0 +1,35 @@
// run-pass
#![feature(const_extern_fn)]
const extern fn foo1(val: u8) -> u8 {
val + 1
}
const extern "C" fn foo2(val: u8) -> u8 {
val + 1
}
const unsafe extern fn bar1(val: bool) -> bool {
!val
}
const unsafe extern "C" fn bar2(val: bool) -> bool {
!val
}
fn main() {
let a: [u8; foo1(25) as usize] = [0; 26];
let b: [u8; foo2(25) as usize] = [0; 26];
assert_eq!(a, b);
let bar1_res = unsafe { bar1(false) };
let bar2_res = unsafe { bar2(false) };
assert!(bar1_res);
assert_eq!(bar1_res, bar2_res);
let _foo1_cast: extern fn(u8) -> u8 = foo1;
let _foo2_cast: extern fn(u8) -> u8 = foo2;
let _bar1_cast: unsafe extern fn(bool) -> bool = bar1;
let _bar2_cast: unsafe extern fn(bool) -> bool = bar2;
}

View file

@ -0,0 +1,12 @@
// Check that `const extern fn` and `const unsafe extern fn` are feature-gated.
#[cfg(FALSE)] const extern fn foo1() {} //~ ERROR `const extern fn` definitions are unstable
#[cfg(FALSE)] const extern "C" fn foo2() {} //~ ERROR `const extern fn` definitions are unstable
#[cfg(FALSE)] const extern "Rust" fn foo3() {} //~ ERROR `const extern fn` definitions are unstable
#[cfg(FALSE)] const unsafe extern fn bar1() {} //~ ERROR `const extern fn` definitions are unstable
#[cfg(FALSE)] const unsafe extern "C" fn bar2() {}
//~^ ERROR `const extern fn` definitions are unstable
#[cfg(FALSE)] const unsafe extern "Rust" fn bar3() {}
//~^ ERROR `const extern fn` definitions are unstable
fn main() {}

View file

@ -0,0 +1,57 @@
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:3:15
|
LL | #[cfg(FALSE)] const extern fn foo1() {}
| ^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:4:15
|
LL | #[cfg(FALSE)] const extern "C" fn foo2() {}
| ^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:5:15
|
LL | #[cfg(FALSE)] const extern "Rust" fn foo3() {}
| ^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:6:15
|
LL | #[cfg(FALSE)] const unsafe extern fn bar1() {}
| ^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:7:15
|
LL | #[cfg(FALSE)] const unsafe extern "C" fn bar2() {}
| ^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error[E0658]: `const extern fn` definitions are unstable
--> $DIR/feature-gate-const_extern_fn.rs:9:15
|
LL | #[cfg(FALSE)] const unsafe extern "Rust" fn bar3() {}
| ^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/64926
= help: add `#![feature(const_extern_fn)]` to the crate attributes to enable
error: aborting due to 6 previous errors
For more information about this error, try `rustc --explain E0658`.

View file

@ -0,0 +1,8 @@
extern {
const fn foo();
//~^ ERROR extern items cannot be `const`
const unsafe fn bar();
//~^ ERROR extern items cannot be `const`
}
fn main() {}

View file

@ -0,0 +1,14 @@
error: extern items cannot be `const`
--> $DIR/no-const-fn-in-extern-block.rs:2:5
|
LL | const fn foo();
| ^^^^^
error: extern items cannot be `const`
--> $DIR/no-const-fn-in-extern-block.rs:4:5
|
LL | const unsafe fn bar();
| ^^^^^
error: aborting due to 2 previous errors

View file

@ -8,9 +8,9 @@ LL | let _: unsafe extern "rust-intrinsic" fn(isize) -> usize = std::mem::tr
| help: use parentheses to call this function: `std::mem::transmute(...)`
|
= note: expected type `unsafe extern "rust-intrinsic" fn(isize) -> usize`
found type `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}`
found type `unsafe extern "rust-intrinsic" fn(_) -> _ {std::mem::transmute::<_, _>}`
error[E0606]: casting `unsafe extern "rust-intrinsic" fn(_) -> _ {std::intrinsics::transmute::<_, _>}` as `unsafe extern "rust-intrinsic" fn(isize) -> usize` is invalid
error[E0606]: casting `unsafe extern "rust-intrinsic" fn(_) -> _ {std::mem::transmute::<_, _>}` as `unsafe extern "rust-intrinsic" fn(isize) -> usize` is invalid
--> $DIR/reify-intrinsic.rs:11:13
|
LL | let _ = std::mem::transmute as unsafe extern "rust-intrinsic" fn(isize) -> usize;