Skip to content

Commit

Permalink
Auto merge of #64906 - Aaron1011:feature/extern-const-fn, r=Centril
Browse files Browse the repository at this point in the history
Add support for `const unsafe? extern fn`

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

Currently, panicking is not allowed in `const`s. When rust-lang/rfcs#2345 (#51999) is stabilized, then panicking in an `const extern 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.

Tracking issue: #64926.
  • Loading branch information
bors committed Oct 7, 2019
2 parents 09868a5 + a4cad41 commit 4ac4809
Show file tree
Hide file tree
Showing 17 changed files with 301 additions and 19 deletions.
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/feature_gate/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/libsyntax/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,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>> {
Expand Down
53 changes: 34 additions & 19 deletions src/libsyntax/parse/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,24 @@ 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()?;
self.bump(); // 'fn'

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);
}
Expand Down Expand Up @@ -257,11 +264,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 {
Expand Down Expand Up @@ -834,11 +837,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) {
Expand Down Expand Up @@ -1278,14 +1277,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)?);
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax_pos/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ symbols! {
console,
const_compare_raw_pointers,
const_constructor,
const_extern_fn,
const_fn,
const_fn_union,
const_generics,
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
13 changes: 13 additions & 0 deletions src/test/ui/consts/const-extern-fn/const-extern-fn-min-const-fn.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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`.
35 changes: 35 additions & 0 deletions src/test/ui/consts/const-extern-fn/const-extern-fn.rs
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 12 additions & 0 deletions src/test/ui/consts/const-extern-fn/feature-gate-const_extern_fn.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
8 changes: 8 additions & 0 deletions src/test/ui/parser/no-const-fn-in-extern-block.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading

0 comments on commit 4ac4809

Please sign in to comment.