Skip to content

Conversation

alejandro-alvarez-sonarsource
Copy link
Contributor

@alejandro-alvarez-sonarsource alejandro-alvarez-sonarsource commented Oct 2, 2025

Treat them as namespaces: if they are at the beginning of the line, they are likely a good recovery point.

For instance, in

1.3.0

extern "C" {
    extern int foo();

    extern "C++" {
        namespace bar {
            void baz();
        };
    }
}

namespace {}

Everything until namespace... is gone from the AST. Headers (like libc's C++ math.h) can be included from an extern "C" context, and they do an extern "C++" back again before including C++ headers (like __type_traits).
However, a malformed declaration just before the include (as the orphan 1.3.0 in the example) causes everything from these standard headers to go missing. This patch updates the heuristic to try to recover from the first extern keyword seen, pretty much as it is done for namespace.

CPP-4478

Treat them as namespaces: if they are at the beginning of the line,
they are likely a good recovery point.

CPP-4478
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 2, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 2, 2025

@llvm/pr-subscribers-clang

Author: Alejandro Álvarez Ayllón (alejandro-alvarez-sonarsource)

Changes

Treat them as namespaces: if they are at the beginning of the line, they are likely a good recovery point.

For instance, in

1.3.0

extern "C" {
    extern int foo();

    extern "C++" {
        namespace bar {
            void baz();
        };
    }
}

namespace {}

Everything until namespace... is gone from the AST. Headers (like libc's C++ math.h) can be included from an extern "C" contexts, and they do an extern "C++" back again before including C++ headers (like __type_traits).
However, a malformed declaration just before the include (as the orphan 1.3.0 in the example) causes everything from these standard libraries to go missing. This patch updates the heuristic to try to recover from the first extern keyword seen, pretty much as it is done for namespace.

CPP-4478


Full diff: https://github.com/llvm/llvm-project/pull/161641.diff

2 Files Affected:

  • (modified) clang/lib/Parse/ParseDecl.cpp (+3)
  • (added) clang/test/Parser/recovery-after-expected-unqualified-id.cpp (+9)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 22c01c4e371f3..d6cd7eb8c2c3d 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2083,6 +2083,9 @@ void Parser::SkipMalformedDecl() {
         return;
       break;
 
+    case tok::kw_extern:
+      // 'extern' at the start of a line is almost certainly a good
+      // place to pick back up parsing
     case tok::kw_namespace:
       // 'namespace' at the start of a line is almost certainly a good
       // place to pick back up parsing, except in an Objective-C
diff --git a/clang/test/Parser/recovery-after-expected-unqualified-id.cpp b/clang/test/Parser/recovery-after-expected-unqualified-id.cpp
new file mode 100644
index 0000000000000..8019b46df1e7b
--- /dev/null
+++ b/clang/test/Parser/recovery-after-expected-unqualified-id.cpp
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 -verify %s
+
+3.2 // expected-error {{expected unqualified-id}}
+
+extern "C" {
+    typedef int Int;
+}
+
+Int foo(); // Ok

@cor3ntin
Copy link
Contributor

cor3ntin commented Oct 2, 2025

Can you add a changelog entry? LGTM otherwise

@alejandro-alvarez-sonarsource alejandro-alvarez-sonarsource merged commit b147019 into llvm:main Oct 2, 2025
10 checks passed
Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the summary explicitly mentions that previously things went missing from the AST, should we not have a test that verifies that this does not happen anymore? CC @erichkeane

@erichkeane
Copy link
Collaborator

summary explicitly mentions that previously things went missing from the AST, should we not have a test that verifies that this does not happen anymore? CC @erichkeane

AST Dump tests are the easiest way, you don't need a valid TU to do so, but can do the ast-dump whenever. I'd love to see that sort of test added here.

mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
Treat them as namespaces: if they are at the beginning of the line, they
are likely a good recovery point.

For instance, in

```cpp
1.3.0

extern "C" {
    extern int foo();

    extern "C++" {
        namespace bar {
            void baz();
        };
    }
}

namespace {}
```

Everything until `namespace`... is gone from the AST. Headers (like
libc's C++ `math.h`) can be included from an `extern "C"` context, and
they do an `extern "C++"` back again before including C++ headers (like
`__type_traits`).
However, a malformed declaration just before the include (as the orphan
`1.3.0` in the example) causes everything from these standard headers to
go missing. This patch updates the heuristic to try to recover from the
first `extern` keyword seen, pretty much as it is done for `namespace`.

CPP-4478
@alejandro-alvarez-sonarsource
Copy link
Contributor Author

alejandro-alvarez-sonarsource commented Oct 3, 2025

Given the summary explicitly mentions that previously things went missing from the AST, should we not have a test that verifies that this does not happen anymore? CC @erichkeane

That was the point of this line. Before it would complain about unknown type Int, but I see that an AST dump would have been more explicit about what is going on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants