Repository: dtolnay/cxx (language: rust)

Issue:
Add error message for invalid include! paths
As promised in [#367](https://github.com/dtolnay/cxx/issues/367#issuecomment-714738184), here's the pull request for an error message when `include!` receives a path that it cannot handle. This is to avoid that such errors propagate further down and only fail at the C++ compile step (and was a reason I spend a lot of time debugging my cxx examples).

**The logic is:** path must begin with the configured include prefix (crate name by default) or must be an absolute path.

Remarks:
1. There were multiple places where I could have added the verification, but I thought the parser is not a good idea, because what `include!` means depends on the user of the parser (can be an external crate such as autocxx). So I implemented it close to the bridge generation.
1. In the parser, I now no longer skip `include!` statements, but integrate them with the `Types` struct and carry that information upward several levels.
1. There are unit tests for the `verify_include_dir()` function, but I'm not sure what's the best location for them. I cannot move them to another crate without making the function under test public.
1. For a similar reason I couldn't write any integration tests (testing the whole chain like in `tests/cxx_gen.rs`). I did some manual tests however. Probably you know much better how and where to add extra tests, if it's even necessary (this PR is primarily a quality-of-life/diagnostic improvement)


Discussion / hints:

Fix patch:
diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs
index e6de9853b..3e37143fe 100644
--- a/gen/build/src/lib.rs
+++ b/gen/build/src/lib.rs
@@ -271,7 +271,10 @@ fn make_include_dir(prj: &Project) -> Result<PathBuf> {
 }
 
 fn generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Result<()> {
-    let opt = Opt::default();
+    let opt = Opt {
+        allow_dot_includes: false,
+        ..Opt::default()
+    };
     let generated = gen::generate_from_path(rust_source_file, &opt);
     let ref rel_path = paths::local_relative_path(rust_source_file);
 
diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs
index cd5830734..c7230393c 100644
--- a/gen/cmd/src/main.rs
+++ b/gen/cmd/src/main.rs
@@ -66,6 +66,7 @@ fn try_main() -> Result<()> {
         cxx_impl_annotations: opt.cxx_impl_annotations,
         gen_header,
         gen_implementation,
+        ..Default::default()
     };
 
     let generated_code = if let Some(input) = opt.input {
diff --git a/gen/src/check.rs b/gen/src/check.rs
new file mode 100644
index 000000000..35929ad34
--- /dev/null
+++ b/gen/src/check.rs
@@ -0,0 +1,27 @@
+use crate::gen::Opt;
+use crate::syntax::report::Errors;
+use crate::syntax::{error, Api};
+use quote::{quote, quote_spanned};
+use std::path::{Component, Path};
+
+pub(super) use crate::syntax::check::typecheck;
+
+pub(super) fn precheck(cx: &mut Errors, apis: &[Api], opt: &Opt) {
+    if !opt.allow_dot_includes {
+        check_dot_includes(cx, apis);
+    }
+}
+
+fn check_dot_includes(cx: &mut Errors, apis: &[Api]) {
+    for api in apis {
+        if let Api::Include(include) = api {
+            let first_component = Path::new(&include.path).components().next();
+            if let Some(Component::CurDir) | Some(Component::ParentDir) = first_component {
+                let begin = quote_spanned!(include.begin_span=> .);
+                let end = quote_spanned!(include.end_span=> .);
+                let span = quote!(#begin #end);
+                cx.error(span, error::DOT_INCLUDE.msg);
+            }
+        }
+    }
+}
diff --git a/gen/src/mod.rs b/gen/src/mod.rs
index 1a3febd31..236fece2d 100644
--- a/gen/src/mod.rs
+++ b/gen/src/mod.rs
@@ -1,6 +1,7 @@
 // Functionality that is shared between the cxx_build::bridge entry point and
 // the cxxbridge CLI command.
 
+mod check;
 pub(super) mod error;
 mod file;
 pub(super) mod fs;
@@ -13,7 +14,7 @@ use self::error::{format_err, Result};
 use self::file::File;
 use self::include::Include;
 use crate::syntax::report::Errors;
-use crate::syntax::{self, check, Types};
+use crate::syntax::{self, Types};
 use std::path::Path;
 
 /// Options for C++ code generation.
@@ -45,6 +46,7 @@ pub struct Opt {
 
     pub(super) gen_header: bool,
     pub(super) gen_implementation: bool,
+    pub(super) allow_dot_includes: bool,
 }
 
 /// Results of code generation.
@@ -63,6 +65,7 @@ impl Default for Opt {
             cxx_impl_annotations: None,
             gen_header: true,
             gen_implementation: true,
+            allow_dot_includes: true,
         }
     }
 }
@@ -112,6 +115,7 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
     let trusted = bridge.unsafety.is_some();
     let ref apis = syntax::parse_items(errors, bridge.content, trusted);
     let ref types = Types::collect(errors, apis);
+    check::precheck(errors, apis, opt);
     errors.propagate()?;
     check::typecheck(errors, namespace, apis, types);
     errors.propagate()?;
diff --git a/syntax/error.rs b/syntax/error.rs
index a60b7da51..959708954 100644
--- a/syntax/error.rs
+++ b/syntax/error.rs
@@ -19,6 +19,7 @@ pub static ERRORS: &[Error] = &[
     CXX_STRING_BY_VALUE,
     CXX_TYPE_BY_VALUE,
     DISCRIMINANT_OVERFLOW,
+    DOT_INCLUDE,
     DOUBLE_UNDERSCORE,
     RUST_TYPE_BY_VALUE,
     USE_NOT_ALLOWED,
@@ -54,6 +55,12 @@ pub static DISCRIMINANT_OVERFLOW: Error = Error {
     note: Some("note: explicitly set `= 0` if that is desired outcome"),
 };
 
+pub static DOT_INCLUDE: Error = Error {
+    msg: "#include relative to `.` or `..` is not supported in Cargo builds",
+    label: Some("#include relative to `.` or `..` is not supported in Cargo builds"),
+    note: Some("note: use a path starting with the crate name"),
+};
+
 pub static DOUBLE_UNDERSCORE: Error = Error {
     msg: "identifiers containing double underscore are reserved in C++",
     label: Some("reserved identifier"),


Test patch: