diff --git a/src/cc65/asmstmt.c b/src/cc65/asmstmt.c
index 6cb6f2ef7..166d05434 100644
--- a/src/cc65/asmstmt.c
+++ b/src/cc65/asmstmt.c
@@ -102,7 +102,7 @@ static SymEntry* AsmGetSym (unsigned Arg, unsigned Type)
 
     /* Did we find a symbol with this name? */
     if (Sym == 0) {
-        Error ("Undefined symbol '%s' for argument %u", CurTok.Ident, Arg);
+        Error ("Undeclared symbol '%s' for argument %u", CurTok.Ident, Arg);
         AsmErrorSkip ();
         return 0;
     }
diff --git a/src/cc65/compile.c b/src/cc65/compile.c
index a591a60b8..02d37c53e 100644
--- a/src/cc65/compile.c
+++ b/src/cc65/compile.c
@@ -94,6 +94,8 @@ static void Parse (void)
     while (CurTok.Tok != TOK_CEOF) {
 
         DeclSpec        Spec;
+        int             NeedClean = 0;
+        unsigned        PrevErrorCount = ErrorCount;
 
         /* Check for empty statements */
         if (CurTok.Tok == TOK_SEMI) {
@@ -144,7 +146,11 @@ static void Parse (void)
             Declarator Decl;
 
             /* Read the next declaration */
-            ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
+            NeedClean = ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
+            if (Decl.Ident[0] == '\0') {
+                Sym = 0;
+                goto NextDecl;
+            }
 
             /* Check if we must reserve storage for the variable. We do this,
             **
@@ -310,6 +316,7 @@ static void Parse (void)
 
             }
 
+NextDecl:
             /* Check for end of declaration list */
             if (CurTok.Tok == TOK_COMMA) {
                 NextToken ();
@@ -325,6 +332,7 @@ static void Parse (void)
             /* Function */
             if (CurTok.Tok == TOK_SEMI) {
                 /* Prototype only */
+                NeedClean = 0;
                 NextToken ();
             } else if (CurTok.Tok == TOK_LCURLY) {
                 /* ISO C: The type category in a function definition cannot be
@@ -337,6 +345,7 @@ static void Parse (void)
                 }
 
                 /* Parse the function body anyways */
+                NeedClean = 0;
                 NewFunc (Sym, FuncDef);
 
                 /* Make sure we aren't omitting any work */
@@ -345,10 +354,27 @@ static void Parse (void)
 
         } else {
 
-            /* Must be followed by a semicolon */
-            ConsumeSemi ();
+            if (Sym) {
+                /* Must be followed by a semicolon */
+                if (CurTok.Tok != TOK_SEMI) {
+                    NeedClean = -1;
+                }
+                ConsumeSemi ();
+            }
 
         }
+
+        /* Try some smart error recovery */
+        if (PrevErrorCount != ErrorCount && NeedClean < 0) {
+            /* Some fix point tokens that are used for error recovery */
+            static const token_t TokenList[] = { TOK_SEMI, TOK_RCURLY };
+
+            SmartErrorSkip ();
+            SkipTokens (TokenList, sizeof (TokenList) / sizeof (TokenList[0]));
+            if (CurTok.Tok == TOK_SEMI || CurTok.Tok == TOK_RCURLY) {
+                NextToken ();
+            }
+        }
     }
 
     /* Done with deferred operations */
diff --git a/src/cc65/declare.c b/src/cc65/declare.c
index 35d0d20fb..45d031ff2 100644
--- a/src/cc65/declare.c
+++ b/src/cc65/declare.c
@@ -83,6 +83,104 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
 
 
 
+static void OpenBrace (Collection* C, token_t Tok)
+/* Consume an opening parenthesis/bracket/curly brace and remember that */
+{
+    switch (Tok) {
+        case TOK_LPAREN: Tok = TOK_RPAREN; break;
+        case TOK_LBRACK: Tok = TOK_RBRACK; break;
+        case TOK_LCURLY: Tok = TOK_RCURLY; break;
+        default:         Internal ("Unexpected opening token: %02X", (unsigned)Tok);
+    }
+    CollAppend (C, (void*)Tok);
+    NextToken ();
+}
+
+
+
+static int CloseBrace (Collection* C, token_t Tok)
+/* Consume a closing parenthesis/bracket/curly brace if it is matched with an
+** opening one and return 0, or bail out and return -1 if it is not matched.
+*/
+{
+    if (CollCount (C) > 0) {
+        token_t LastTok = (token_t)CollLast (C);
+        if (LastTok == Tok) {
+            CollPop (C);
+            NextToken ();
+            return 0;
+        }
+    }
+
+    return -1;
+}
+
+
+
+int SmartErrorSkip (void)
+/* Try some smart error recovery. Skip tokens until either a comma or semicolon
+** that is not enclosed in an open parenthesis/bracket/curly brace, or until an
+** unpaired right parenthesis/bracket/curly brace is reached. Return 0 if it is
+** the former case, or -1 if it is the latter case. */
+{
+    Collection C = AUTO_COLLECTION_INITIALIZER;
+    int Res = 0;
+
+    /* Some fix point tokens that are used for error recovery */
+    static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI,
+        TOK_LPAREN, TOK_RPAREN, TOK_LBRACK, TOK_RBRACK, TOK_LCURLY, TOK_RCURLY };
+
+    while (CurTok.Tok != TOK_CEOF) {
+        SkipTokens (TokenList, sizeof (TokenList) / sizeof (TokenList[0]));
+
+        switch (CurTok.Tok) {
+            case TOK_LPAREN:
+            case TOK_LBRACK:
+            case TOK_LCURLY:
+                OpenBrace (&C, CurTok.Tok);
+                break;
+
+            case TOK_RPAREN:
+            case TOK_RBRACK:
+                if (CloseBrace (&C, CurTok.Tok)) {
+                    Res = -1;
+                    goto ExitPoint;
+                }
+                break;
+
+            case TOK_RCURLY:
+                if (CloseBrace (&C, CurTok.Tok)) {
+                    Res = -1;
+                    goto ExitPoint;
+                } else if (CollCount (&C) == 0) {
+                    goto ExitPoint;
+                }
+                break;
+
+            case TOK_COMMA:
+                if (CollCount (&C) == 0) {
+                    goto ExitPoint;
+                }
+                NextToken ();
+                break;
+
+            case TOK_SEMI:
+            case TOK_CEOF:
+                Res = -1;
+                goto ExitPoint;
+
+            default:
+                Internal ("Unexpected token: %02X", (unsigned)CurTok.Tok);
+        }
+    }
+
+ExitPoint:
+    DoneCollection (&C);
+    return Res;
+}
+
+
+
 static unsigned ParseOneStorageClass (void)
 /* Parse and return a storage class specifier */
 {
@@ -1589,6 +1687,12 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
                         *SignednessSpecified = 1;
                     }
                     break;
+                } else if ((TSFlags & TS_MASK_DEFAULT_TYPE) == TS_DEFAULT_TYPE_NONE) {
+                    /* Treat this identifier as an unknown type */
+                    Error ("Unknown type name '%s'", CurTok.Ident);
+                    TypeCopy (Spec->Type, type_int);
+                    NextToken ();
+                    break;
                 }
             } else {
                 /* This is a label. Use the default type flag to end the loop
@@ -1670,14 +1774,13 @@ static void ParseOldStyleParamList (FuncDesc* F)
             NextToken ();
 
         } else {
-            /* Some fix point tokens that are used for error recovery */
-            static const token_t TokenList[] = { TOK_COMMA, TOK_RPAREN, TOK_SEMI };
-
             /* Not a parameter name */
             Error ("Identifier expected for parameter name");
 
             /* Try some smart error recovery */
-            SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
+            if (SmartErrorSkip () < 0) {
+                break;
+            }
         }
 
         /* Check for more parameters */
@@ -1763,12 +1866,9 @@ static void ParseOldStyleParamList (FuncDesc* F)
         ConsumeSemi ();
     }
 
-    if (PrevErrorCount != ErrorCount) {
-        /* Some fix point tokens that are used for error recovery */
-        static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI };
-
+    if (PrevErrorCount != ErrorCount && CurTok.Tok != TOK_LCURLY) {
         /* Try some smart error recovery */
-        SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
+        SmartErrorSkip ();
     }
 }
 
@@ -1783,6 +1883,7 @@ static void ParseAnsiParamList (FuncDesc* F)
         DeclSpec    Spec;
         Declarator  Decl;
         SymEntry*   Param;
+        unsigned    PrevErrorCount = ErrorCount;
 
         /* Allow an ellipsis as last parameter */
         if (CurTok.Tok == TOK_ELLIPSIS) {
@@ -1818,7 +1919,7 @@ static void ParseAnsiParamList (FuncDesc* F)
         /* Allow parameters without a name, but remember if we had some to
         ** eventually print an error message later.
         */
-        ParseDecl (&Spec, &Decl, DM_ACCEPT_IDENT);
+        ParseDecl (&Spec, &Decl, DM_ACCEPT_PARAM_IDENT);
         if (Decl.Ident[0] == '\0') {
 
             /* Unnamed symbol. Generate a name that is not user accessible,
@@ -1850,6 +1951,13 @@ static void ParseAnsiParamList (FuncDesc* F)
         /* Count arguments */
         ++F->ParamCount;
 
+        if (PrevErrorCount != ErrorCount) {
+            /* Try some smart error recovery */
+            if (SmartErrorSkip () < 0) {
+                break;
+            }
+        }
+
         /* Check for more parameters */
         if (CurTok.Tok == TOK_COMMA) {
             NextToken ();
@@ -1940,7 +2048,7 @@ static FuncDesc* ParseFuncDecl (void)
 
 
 
-static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
+static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
 /* Recursively process direct declarators. Build a type array in reverse order. */
 {
     /* Read optional function or pointer qualifiers that modify the identifier
@@ -1957,61 +2065,49 @@ static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
         /* Skip the star */
         NextToken ();
 
+        /* A pointer type cannot be used as an empty declaration */
+        if (Mode == DM_ACCEPT_IDENT) {
+            Mode = DM_NEED_IDENT;
+        }
+
         /* Allow const, restrict, and volatile qualifiers */
         Qualifiers |= OptionalQualifiers (Qualifiers, T_QUAL_CVR);
 
         /* Parse the type that the pointer points to */
-        DirectDecl (Spec, D, Mode);
+        Mode = DirectDecl (Spec, D, Mode);
 
         /* Add the type */
         AddTypeCodeToDeclarator (D, T_PTR | Qualifiers);
-        return;
+        return Mode;
     }
 
     if (CurTok.Tok == TOK_LPAREN) {
         NextToken ();
-        DirectDecl (Spec, D, Mode);
-        ConsumeRParen ();
-    } else {
-        /* Things depend on Mode now:
-        **  - Mode == DM_NEED_IDENT means:
-        **      we *must* have a type and a variable identifer.
-        **  - Mode == DM_NO_IDENT means:
-        **      we must have a type but no variable identifer
-        **      (if there is one, it's not read).
-        **  - Mode == DM_ACCEPT_IDENT means:
-        **      we *may* have an identifier. If there is an identifier,
-        **      it is read, but it is no error, if there is none.
+        /* An empty declaration cannot contain parentheses where an identifier
+        ** would show up if it were a non-empty declaration.
         */
-        if (Mode == DM_NO_IDENT) {
-            D->Ident[0] = '\0';
-        } else if (CurTok.Tok == TOK_IDENT) {
-            strcpy (D->Ident, CurTok.Ident);
-            NextToken ();
-        } else {
-            if (Mode == DM_NEED_IDENT) {
-                /* Some fix point tokens that are used for error recovery */
-                static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI, TOK_LCURLY, TOK_RCURLY };
-
-                Error ("Identifier expected");
-
-                /* Try some smart error recovery */
-                SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
-
-                /* Skip curly braces */
-                if (CurTok.Tok == TOK_LCURLY) {
-                    static const token_t CurlyToken[] = { TOK_RCURLY };
-                    SkipTokens (CurlyToken, sizeof(CurlyToken) / sizeof(CurlyToken[0]));
-                    NextToken ();
-                } else if (CurTok.Tok == TOK_RCURLY) {
-                    NextToken ();
-                }
-            }
-            D->Ident[0] = '\0';
+        if (Mode == DM_ACCEPT_IDENT) {
+            Mode = DM_NEED_IDENT;
+        }
+        Mode = DirectDecl (Spec, D, Mode);
+        ConsumeRParen ();
+    } else if (CurTok.Tok == TOK_IDENT) {
+        strcpy (D->Ident, CurTok.Ident);
+        NextToken ();
+    } else {
+        D->Ident[0] = '\0';
+        if (Mode == DM_NEED_IDENT) {
+            Error ("Identifier expected");
         }
     }
 
     while (CurTok.Tok == TOK_LBRACK || CurTok.Tok == TOK_LPAREN) {
+        /* An array or function type cannot be used as an empty declaration */
+        if (Mode == DM_ACCEPT_IDENT && D->Ident[0] == '\0') {
+            Mode = DM_NEED_IDENT;
+            Error ("Identifier expected");
+        }
+
         if (CurTok.Tok == TOK_LPAREN) {
 
             /* Function declarator */
@@ -2097,6 +2193,8 @@ static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
     if (Qualifiers & T_QUAL_CDECL) {
         Error ("Invalid '__cdecl__' qualifier");
     }
+
+    return Mode;
 }
 
 
@@ -2129,8 +2227,10 @@ Type* ParseType (Type* T)
 
 
 
-void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
-/* Parse a variable, type or function declarator */
+int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
+/* Parse a variable, type or function declarator. Return -1 if this stops at
+** an unpaired right parenthesis/bracket/curly brace.
+*/
 {
     /* Used to check if we have any errors during parsing this */
     unsigned PrevErrorCount = ErrorCount;
@@ -2181,7 +2281,7 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
     }
 
     /* Check a few pre-C99 things */
-    if ((Spec->Flags & DS_DEF_TYPE) != 0) {
+    if (D->Ident[0] != '\0' && (Spec->Flags & DS_DEF_TYPE) != 0) {
         /* Check and warn about an implicit int return in the function */
         if (IsTypeFunc (D->Type) && IsRankInt (GetFuncReturnType (D->Type))) {
             /* Function has an implicit int return. Output a warning if we don't
@@ -2193,7 +2293,7 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
             GetFuncDesc (D->Type)->Flags |= FD_OLDSTYLE_INTRET;
         }
 
-        /* For anthing that is not a function or typedef, check for an implicit
+        /* For anything that is not a function or typedef, check for an implicit
         ** int declaration.
         */
         if ((D->StorageClass & SC_FUNC) != SC_FUNC &&
@@ -2208,14 +2308,38 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
     }
 
     if (PrevErrorCount != ErrorCount) {
-        /* Make the declaration fictitious if is is not parsed correctly */
-        D->StorageClass |= SC_FICTITIOUS;
+        if ((Spec->Flags & DS_DEF_TYPE) == 0 && Mode == DM_NEED_IDENT && D->Ident[0] == '\0') {
+            /* Make the declaration fictitious if is is not parsed correctly */
+            D->StorageClass |= SC_FICTITIOUS;
 
-        if (Mode == DM_NEED_IDENT && D->Ident[0] == '\0') {
             /* Use a fictitious name for the identifier if it is missing */
-            AnonName (D->Ident, "global");
+            const char* Level = "";
+
+            switch (GetLexicalLevel ()) {
+                case LEX_LEVEL_GLOBAL:
+                    Level = "global";
+                    break;
+                case LEX_LEVEL_FUNCTION:
+                case LEX_LEVEL_BLOCK:
+                    Level = "local";
+                    break;
+                case LEX_LEVEL_STRUCT:
+                    Level = "field";
+                    break;
+                default:
+                    Level = "unknown";
+                    break;
+            }
+            AnonName (D->Ident, Level);
+        }
+
+        /* Try some smart error recovery */
+        if (CurTok.Tok != TOK_LCURLY || !IsTypeFunc (D->Type)) {
+            return SmartErrorSkip ();
         }
     }
+
+    return 0;
 }
 
 
diff --git a/src/cc65/declare.h b/src/cc65/declare.h
index 89d7be7ea..add86594d 100644
--- a/src/cc65/declare.h
+++ b/src/cc65/declare.h
@@ -98,11 +98,26 @@ struct Declarator {
     unsigned    Index;              /* Used to build Type */
 };
 
-/* Modes for ParseDecl */
+/* Modes for ParseDecl:
+**  - DM_NEED_IDENT means:
+**      we *must* have a type and a variable identifer.
+**  - DM_NO_IDENT means:
+**      we must have a type but no variable identifer
+**      (if there is one, it's not read).
+**  - DM_ACCEPT_IDENT means:
+**      we *may* have an identifier, or none. If it is the latter case,
+**      the type must be used as an empty declaration, or it is an error.
+**      Note: this is used for struct/union members.
+**  - DM_IGNORE_IDENT means:
+**      we *may* have an identifier. If there is an identifier,
+**      it is read, but it is no error, if there is none.
+**      Note: this is used for function parameter type lists.
+*/
 typedef enum {
-    DM_NEED_IDENT,                      /* We must have an identifier */
-    DM_NO_IDENT,                        /* We won't read an identifier */
-    DM_ACCEPT_IDENT,                    /* We will accept an id if there is one */
+    DM_NEED_IDENT,
+    DM_NO_IDENT,
+    DM_ACCEPT_IDENT,
+    DM_ACCEPT_PARAM_IDENT,
 } declmode_t;
 
 
@@ -113,11 +128,19 @@ typedef enum {
 
 
 
+int SmartErrorSkip (void);
+/* Try some smart error recovery. Skip tokens until either a comma or semicolon
+** that is not enclosed in an open parenthesis/bracket/curly brace, or until an
+** unpaired right parenthesis/bracket/curly brace is reached. Return 0 if it is
+** the former case, or -1 if it is the latter case. */
+
 Type* ParseType (Type* Type);
 /* Parse a complete type specification */
 
-void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode);
-/* Parse a variable, type or function declarator */
+int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode);
+/* Parse a variable, type or function declarator. Return -1 if this stops at
+** an unpaired right parenthesis/bracket/curly brace.
+*/
 
 void ParseDeclSpec (DeclSpec* Spec, typespec_t TSFlags, unsigned DefStorage);
 /* Parse a declaration specification */
diff --git a/src/cc65/error.c b/src/cc65/error.c
index 39b067825..2ad7133ed 100644
--- a/src/cc65/error.c
+++ b/src/cc65/error.c
@@ -61,6 +61,8 @@
 /* Count of errors/warnings */
 unsigned ErrorCount     = 0;
 unsigned WarningCount   = 0;
+unsigned RecentLineNo     = 0;
+unsigned RecentErrorCount = 0;
 
 /* Warning and error options */
 IntStack WarnEnable         = INTSTACK(1);  /* Enable warnings */
@@ -205,8 +207,16 @@ static void IntError (const char* Filename, unsigned LineNo, const char* Msg, va
     if (Line) {
         Print (stderr, 1, "Input: %.*s\n", (int) SB_GetLen (Line), SB_GetConstBuf (Line));
     }
+
     ++ErrorCount;
-    if (ErrorCount > 20) {
+    if (RecentLineNo != LineNo) {
+        RecentLineNo = LineNo;
+        RecentErrorCount = 0;
+    } else {
+        ++RecentErrorCount;
+    }
+
+    if (RecentErrorCount > 20 || ErrorCount > 200) {
         Fatal ("Too many errors");
     }
 }
diff --git a/src/cc65/expr.c b/src/cc65/expr.c
index f7ad5affc..6d4b04892 100644
--- a/src/cc65/expr.c
+++ b/src/cc65/expr.c
@@ -1322,10 +1322,10 @@ static void Primary (ExprDesc* E)
                     E->Name  = (uintptr_t) Sym->Name;
                 } else {
                     /* Undeclared Variable */
+                    Error ("Undeclared identifier '%s'", Ident);
                     Sym = AddLocalSym (Ident, type_int, SC_AUTO | SC_REF, 0);
                     E->Flags = E_LOC_STACK | E_RTYPE_LVAL;
                     E->Type  = type_int;
-                    Error ("Undefined symbol: '%s'", Ident);
                 }
 
             }
diff --git a/src/cc65/symtab.c b/src/cc65/symtab.c
index 47e4b3153..d30e591c9 100644
--- a/src/cc65/symtab.c
+++ b/src/cc65/symtab.c
@@ -1493,7 +1493,7 @@ void MakeZPSym (const char* Name)
     if (Entry) {
         Entry->Flags |= SC_ZEROPAGE;
     } else {
-        Error ("Undefined symbol: '%s'", Name);
+        Error ("Undeclared symbol: '%s'", Name);
     }
 }
 
diff --git a/test/ref/bug1889-missing-identifier.cref b/test/ref/bug1889-missing-identifier.cref
index acaf53f94..534c6aaba 100644
--- a/test/ref/bug1889-missing-identifier.cref
+++ b/test/ref/bug1889-missing-identifier.cref
@@ -1,5 +1,3 @@
 bug1889-missing-identifier.c:3: Error: Identifier expected
 bug1889-missing-identifier.c:3: Error: ';' expected
-bug1889-missing-identifier.c:3: Warning: Implicit 'int' is an obsolete feature
 bug1889-missing-identifier.c:4: Error: Identifier expected
-bug1889-missing-identifier.c:4: Warning: Implicit 'int' is an obsolete feature
diff --git a/test/ref/custom-reference-error.cref b/test/ref/custom-reference-error.cref
index fa584f307..728cc0e15 100644
--- a/test/ref/custom-reference-error.cref
+++ b/test/ref/custom-reference-error.cref
@@ -1,5 +1,5 @@
 custom-reference-error.c:18: Error: Call to undeclared function 'printf'
-custom-reference-error.c:19: Error: Undefined symbol: 'n'
+custom-reference-error.c:19: Error: Undeclared identifier 'n'
 custom-reference-error.c:21: Warning: Control reaches end of non-void function [-Wreturn-type]
 custom-reference-error.c:21: Warning: Parameter 'argc' is never used
 custom-reference-error.c:21: Warning: Parameter 'argv' is never used