This somewhat differs between languages, but mostly solution #2: the parser handles the structure of type names, without handling their semantics.
Type names generally follow a grammar that can be handled by the parser. Then, the AST would contain a sub-tree representing the type syntax. The type checker might later transform this into a different form to facilitate equality checking, e.g. by resolving type aliases. While function pointers might have special syntax, the type checker can treat them equivalently to a parametrized type. E.g. the C function pointer type int (*)()
might be handled equivalently to a C++ template invocation _FunctionPointer<int>
, and might even be transformed by the parser to such a more generic form. Once fully evaluated, the type may be given a unique name via name mangling.
Unfortunately, the correct parsing of some programming languages depends on whether an identifier is a type or a value. If that is the case, parsing and type checking has to be interleaved. The parser would consume the smallest chunk of input that can be processed independently, then hand if off to the part of the compiler that handles semantics, e.g. the type checker. The type checker in turn updates a dictionary that maps identifiers to their kind, allowing the parser to continue.