[Pitch] Lazy ClangImporter Diagnostics Enabled by Default

Hi all,

A couple weeks ago, a change to improve the diagnostics of the ClangImporter was landed. The new diagnostics are hidden behind two flags, enabling lazily and eagerly triggered diagnostics respectively. I am proposing lazily triggered diagnostics be enabled by default and replacing the -enable-experimental-clang-importer-diagnostics flag with a -disable-experimental-clang-importer-diagnostics flag allowing users to opt out. Notably, these lazily triggered diagnostics are only produced in association with a Clang node that was referenced from Swift and failed to import. This has two consequences:

  • No noise about unreferenced diagnostics will be produced.
  • The diagnostics cannot be reasonably ignored, as they help explain a fatal compile error (reference to unimportable entity).

Examples

Below I present a series of examples that help illustrate the nature of these diagnostics. Here is a header from which I will attempt to import declarations into Swift. For each declaration, I have declared an "unreferenced" counterpart.

#include <Foundation.h>

// Unsupported built-in types
int unsupported_parameter_type(int param1, _Complex int param2);
_Complex int referenced_unsupported_return_type();

int unreferenced_unsupported_parameter_type(int param1, _Complex int param2);
_Complex int unsupported_return_type();

struct PartialImport {
  int a;
  int b;
  int _Complex unsupported_field;
  int _Complex unreferenced_unsupported_field;
};

struct PartialImport partialImport = {1, 2, 3, 4};

// Unsupported macros
#define INVALID_INTEGER_LITERAL_1 10_9
#define STRING_LITERAL "MyString"

#define FUNC_LIKE_MACRO() 0
#define UNREFERENCED_FUNC_LIKE_MACRO() 0

#define INVALID_ARITHMETIC_1 5 + INVALID_INTERGER_LITERAL_1
#define UNREFERENCED_INVALID_ARITHMETIC_1 5 + INVALID_INTERGER_LITERAL_1

#define INVALID_ARITHMETIC_2 1 + STRING_LITERAL
#define UNREFERENCED_INVALID_ARITHMETIC_2 1 + STRING_LITERAL

#define INVALID_ARITHMETIC_3 1 + 'c'
#define UNREFERENCED_INVALID_ARITHMETIC_3 1 + 'c'

#define INVALID_ARITHMETIC_4 3 % 2
#define UNREFERENCED_INVALID_ARITHMETIC_4 3 % 2

#define CHAR_LITERAL 'a'
#define UNREFERENCED_CHAR_LITERAL 'a'

#define UNSUPPORTED_1 FUNC_LIKE_MACRO()
#define UNREFERENCED_UNSUPPORTED_1 FUNC_LIKE_MACRO()

// Incomplete Objective-C entities
@class ForwardDeclaredInterface;
@protocol ForwardDeclaredProtocol;

@interface Bar : NSObject
@property id<ForwardDeclaredProtocol> propertyUsingAForwardDeclaredProtocol;
@property id<ForwardDeclaredProtocol> unreferencedPropertyUsingAForwardDeclaredProtocol;
@property ForwardDeclaredInterface* propertyUsingAForwardDeclaredInterface;
@property ForwardDeclaredInterface* unreferencedPropertyUsingAForwardDeclaredInterface;
- (NSObject<ForwardDeclaredProtocol> *) methodReturningForwardDeclaredProtocol;
- (NSObject<ForwardDeclaredProtocol> *) unreferencedMethodReturningForwardDeclaredProtocol;
- (ForwardDeclaredInterface *) methodReturningForwardDeclaredInterface;
- (ForwardDeclaredInterface *) unreferencedMethodReturningForwardDeclaredInterface;
- (int)methodTakingAForwardDeclaredProtocolAsAParameter:(id<ForwardDeclaredProtocol>)param1;
- (int)unreferencedMethodTakingAForwardDeclaredProtocolAsAParameter:(id<ForwardDeclaredProtocol>)param1;
- (int)methodTakingAForwardDeclaredInterfaceAsAParameter:(ForwardDeclaredInterface *)param1 andAnother:(ForwardDeclaredInterface *)param2;
- (int)unreferencedMethodTakingAForwardDeclaredInterfaceAsAParameter:(ForwardDeclaredInterface *)param1 andAnother:(ForwardDeclaredInterface *)param2;
@end

ForwardDeclaredInterface* CFunctionReturningAForwardDeclaredInterface();
ForwardDeclaredInterface* unreferencedCFunctionReturningAForwardDeclaredInterface();
void CFunctionTakingAForwardDeclaredInterfaceAsAParameter(ForwardDeclaredInterface* param1);
void unreferencedCFunctionTakingAForwardDeclaredInterfaceAsAParameter(ForwardDeclaredInterface* param1);

NSObject<ForwardDeclaredProtocol> *CFunctionReturningAForwardDeclaredProtocol();
NSObject<ForwardDeclaredProtocol> *unreferencedCFunctionReturningAForwardDeclaredProtocol();
void CFunctionTakingAForwardDeclaredProtocolAsAParameter(id<ForwardDeclaredProtocol> param1);
void unreferencedCFunctionTakingAForwardDeclaredProtocolAsAParameter(id<ForwardDeclaredProtocol> param1);

I created a small test ClangModule from this header and imported it into a Swift file. I attempt to reference
the declarations from Swift like so:

import Test

// Unsupported built-ins
unsupported_parameter_type(1,2)
unsupported_return_type()
unsupported_parameter_type(1,2)
unsupported_return_type()

let s: PartialImport
s.unsupported_field = 5
partialImport.unsupported_field = 5

// Unsupported macros

_ = FUNC_LIKE_MACRO()
_ = INVALID_ARITHMETIC_1
_ = INVALID_ARITHMETIC_2
_ = INVALID_ARITHMETIC_3
_ = INVALID_ARITHMETIC_4
_ = CHAR_LITERAL
UNSUPPORTED_1

// Incomplete Objective-C entities
let bar = Bar()
_ = bar.methodReturningForwardDeclaredInterface()
_ = bar.methodTakingAForwardDeclaredInterfaceAsAParameter(nil, andAnother: nil)
bar.propertyUsingAForwardDeclaredInterface = nil
_ = CFunctionReturningAForwardDeclaredInterface()
CFunctionTakingAForwardDeclaredInterfaceAsAParameter(nil)
_ = bar.methodReturningForwardDeclaredProtocol()
_ = bar.methodTakingAForwardDeclaredProtocolAsAParameter(nil)
bar.propertyUsingAForwardDeclaredProtocol = nil
_ = CFunctionReturningAForwardDeclaredProtocol()
CFunctionTakingAForwardDeclaredProtocolAsAParameter(nil)

When I typecheck the file, I get the following diagnostics:

/home/nuria/Git/swift-project/swift/scratch/test.h:4:1: warning: function 'unsupported_parameter_type' not imported
int unsupported_parameter_type(int param1, _Complex int param2);
^
/home/nuria/Git/swift-project/swift/scratch/test.h:4:44: note: parameter 'param2' not imported
int unsupported_parameter_type(int param1, _Complex int param2);
                                           ^
/home/nuria/Git/swift-project/swift/scratch/test.h:4:44: note: built-in type 'Complex' not supported
int unsupported_parameter_type(int param1, _Complex int param2);
                                           ^
test.swift:4:1: error: cannot find 'unsupported_parameter_type' in scope
unsupported_parameter_type(1,2)
^~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:8:1: warning: function 'unsupported_return_type' not imported
_Complex int unsupported_return_type();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:8:1: note: return type not imported
_Complex int unsupported_return_type();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:8:1: note: built-in type 'Complex' not supported
_Complex int unsupported_return_type();
^
test.swift:5:1: error: cannot find 'unsupported_return_type' in scope
unsupported_return_type()
^~~~~~~~~~~~~~~~~~~~~~~


test.swift:6:1: error: cannot find 'unsupported_parameter_type' in scope
unsupported_parameter_type(1,2)
^~~~~~~~~~~~~~~~~~~~~~~~~~


test.swift:7:1: error: cannot find 'unsupported_return_type' in scope
unsupported_return_type()
^~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:13:3: warning: field 'unsupported_field' not imported
  int _Complex unsupported_field;
  ^
/home/nuria/Git/swift-project/swift/scratch/test.h:13:3: note: built-in type 'Complex' not supported
  int _Complex unsupported_field;
  ^
test.swift:10:3: error: value of type 'PartialImport' has no member 'unsupported_field'
s.unsupported_field = 5
~ ^~~~~~~~~~~~~~~~~


test.swift:11:15: error: value of type 'PartialImport' has no member 'unsupported_field'
partialImport.unsupported_field = 5
~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:23:9: error: macro 'FUNC_LIKE_MACRO' not imported: function like macros not supported
#define FUNC_LIKE_MACRO() 0
        ^
test.swift:15:5: error: cannot find 'FUNC_LIKE_MACRO' in scope
_ = FUNC_LIKE_MACRO()
    ^~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:26:9: error: macro 'INVALID_ARITHMETIC_1' not imported: structure not supported
#define INVALID_ARITHMETIC_1 5 + INVALID_INTERGER_LITERAL_1
        ^
test.swift:16:5: error: cannot find 'INVALID_ARITHMETIC_1' in scope
_ = INVALID_ARITHMETIC_1
    ^~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:29:9: error: macro 'INVALID_ARITHMETIC_2' not imported: structure not supported
#define INVALID_ARITHMETIC_2 1 + STRING_LITERAL
        ^
test.swift:17:5: error: cannot find 'INVALID_ARITHMETIC_2' in scope
_ = INVALID_ARITHMETIC_2
    ^~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:32:9: error: macro 'INVALID_ARITHMETIC_3' not imported: structure not supported
#define INVALID_ARITHMETIC_3 1 + 'c'
        ^
test.swift:18:5: error: cannot find 'INVALID_ARITHMETIC_3' in scope
_ = INVALID_ARITHMETIC_3
    ^~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:35:9: error: macro 'INVALID_ARITHMETIC_4' not imported
#define INVALID_ARITHMETIC_4 3 % 2
        ^
/home/nuria/Git/swift-project/swift/scratch/test.h:35:32: note: operator '%' not supported in macro arithmetic
#define INVALID_ARITHMETIC_4 3 % 2
                               ^
test.swift:19:5: error: cannot find 'INVALID_ARITHMETIC_4' in scope
_ = INVALID_ARITHMETIC_4
    ^~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:38:9: error: macro 'CHAR_LITERAL' not imported
#define CHAR_LITERAL 'a'
        ^
/home/nuria/Git/swift-project/swift/scratch/test.h:38:22: note: only numeric and string macro literals supported
#define CHAR_LITERAL 'a'
                     ^
test.swift:20:5: error: cannot find 'CHAR_LITERAL' in scope
_ = CHAR_LITERAL
    ^~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:41:9: error: macro 'UNSUPPORTED_1' not imported: structure not supported
#define UNSUPPORTED_1 FUNC_LIKE_MACRO()
        ^
test.swift:21:1: error: cannot find 'UNSUPPORTED_1' in scope
UNSUPPORTED_1
^~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:55:1: warning: method 'methodReturningForwardDeclaredInterface' not imported
- (ForwardDeclaredInterface *) methodReturningForwardDeclaredInterface;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:55:1: note: return type not imported
- (ForwardDeclaredInterface *) methodReturningForwardDeclaredInterface;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:55:1: note: interface 'ForwardDeclaredInterface' is incomplete
- (ForwardDeclaredInterface *) methodReturningForwardDeclaredInterface;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:45:1: note: interface 'ForwardDeclaredInterface' forward declared here
@class ForwardDeclaredInterface;
^
test.swift:25:9: error: value of type 'Bar' has no member 'methodReturningForwardDeclaredInterface'
_ = bar.methodReturningForwardDeclaredInterface()
    ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:59:1: warning: method 'methodTakingAForwardDeclaredInterfaceAsAParameter:andAnother:' not imported
- (int)methodTakingAForwardDeclaredInterfaceAsAParameter:(ForwardDeclaredInterface *)param1 andAnother:(ForwardDeclaredInterface *)param2;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:59:59: note: parameter 'param1' not imported
- (int)methodTakingAForwardDeclaredInterfaceAsAParameter:(ForwardDeclaredInterface *)param1 andAnother:(ForwardDeclaredInterface *)param2;
                                                          ^
/home/nuria/Git/swift-project/swift/scratch/test.h:59:59: note: interface 'ForwardDeclaredInterface' is incomplete
- (int)methodTakingAForwardDeclaredInterfaceAsAParameter:(ForwardDeclaredInterface *)param1 andAnother:(ForwardDeclaredInterface *)param2;
                                                          ^
/home/nuria/Git/swift-project/swift/scratch/test.h:45:1: note: interface 'ForwardDeclaredInterface' forward declared here
@class ForwardDeclaredInterface;
^
test.swift:26:9: error: value of type 'Bar' has no member 'methodTakingAForwardDeclaredInterfaceAsAParameter'
_ = bar.methodTakingAForwardDeclaredInterfaceAsAParameter(nil as Any?, andAnother: nil as Any?)
    ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:51:1: warning: property 'propertyUsingAForwardDeclaredInterface' not imported
@property ForwardDeclaredInterface* propertyUsingAForwardDeclaredInterface;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:51:1: note: interface 'ForwardDeclaredInterface' is incomplete
@property ForwardDeclaredInterface* propertyUsingAForwardDeclaredInterface;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:45:1: note: interface 'ForwardDeclaredInterface' forward declared here
@class ForwardDeclaredInterface;
^
test.swift:27:5: error: value of type 'Bar' has no member 'propertyUsingAForwardDeclaredInterface'
bar.propertyUsingAForwardDeclaredInterface = nil as Any?
~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:63:1: warning: function 'CFunctionReturningAForwardDeclaredInterface' not imported
ForwardDeclaredInterface* CFunctionReturningAForwardDeclaredInterface();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:63:1: note: return type not imported
ForwardDeclaredInterface* CFunctionReturningAForwardDeclaredInterface();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:63:1: note: interface 'ForwardDeclaredInterface' is incomplete
ForwardDeclaredInterface* CFunctionReturningAForwardDeclaredInterface();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:45:1: note: interface 'ForwardDeclaredInterface' forward declared here
@class ForwardDeclaredInterface;
^
test.swift:28:5: error: cannot find 'CFunctionReturningAForwardDeclaredInterface' in scope
_ = CFunctionReturningAForwardDeclaredInterface()
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:65:1: warning: function 'CFunctionTakingAForwardDeclaredInterfaceAsAParameter' not imported
void CFunctionTakingAForwardDeclaredInterfaceAsAParameter(ForwardDeclaredInterface* param1);
^
/home/nuria/Git/swift-project/swift/scratch/test.h:65:59: note: parameter 'param1' not imported
void CFunctionTakingAForwardDeclaredInterfaceAsAParameter(ForwardDeclaredInterface* param1);
                                                          ^
/home/nuria/Git/swift-project/swift/scratch/test.h:65:59: note: interface 'ForwardDeclaredInterface' is incomplete
void CFunctionTakingAForwardDeclaredInterfaceAsAParameter(ForwardDeclaredInterface* param1);
                                                          ^
/home/nuria/Git/swift-project/swift/scratch/test.h:45:1: note: interface 'ForwardDeclaredInterface' forward declared here
@class ForwardDeclaredInterface;
^
test.swift:29:1: error: cannot find 'CFunctionTakingAForwardDeclaredInterfaceAsAParameter' in scope
CFunctionTakingAForwardDeclaredInterfaceAsAParameter(nil as Any?)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:53:1: warning: method 'methodReturningForwardDeclaredProtocol' not imported
- (NSObject<ForwardDeclaredProtocol> *) methodReturningForwardDeclaredProtocol;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:53:1: note: return type not imported
- (NSObject<ForwardDeclaredProtocol> *) methodReturningForwardDeclaredProtocol;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:53:1: note: protocol 'ForwardDeclaredProtocol' is incomplete
- (NSObject<ForwardDeclaredProtocol> *) methodReturningForwardDeclaredProtocol;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:46:1: note: protocol 'ForwardDeclaredProtocol' forward declared here
@protocol ForwardDeclaredProtocol;
^
test.swift:30:9: error: value of type 'Bar' has no member 'methodReturningForwardDeclaredProtocol'
_ = bar.methodReturningForwardDeclaredProtocol()
    ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:57:1: warning: method 'methodTakingAForwardDeclaredProtocolAsAParameter:' not imported
- (int)methodTakingAForwardDeclaredProtocolAsAParameter:(id<ForwardDeclaredProtocol>)param1;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:57:58: note: parameter 'param1' not imported
- (int)methodTakingAForwardDeclaredProtocolAsAParameter:(id<ForwardDeclaredProtocol>)param1;
                                                         ^
/home/nuria/Git/swift-project/swift/scratch/test.h:57:58: note: protocol 'ForwardDeclaredProtocol' is incomplete
- (int)methodTakingAForwardDeclaredProtocolAsAParameter:(id<ForwardDeclaredProtocol>)param1;
                                                         ^
/home/nuria/Git/swift-project/swift/scratch/test.h:46:1: note: protocol 'ForwardDeclaredProtocol' forward declared here
@protocol ForwardDeclaredProtocol;
^
test.swift:31:9: error: value of type 'Bar' has no member 'methodTakingAForwardDeclaredProtocolAsAParameter'
_ = bar.methodTakingAForwardDeclaredProtocolAsAParameter(nil as Any?)
    ~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:49:1: warning: property 'propertyUsingAForwardDeclaredProtocol' not imported
@property id<ForwardDeclaredProtocol> propertyUsingAForwardDeclaredProtocol;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:49:1: note: protocol 'ForwardDeclaredProtocol' is incomplete
@property id<ForwardDeclaredProtocol> propertyUsingAForwardDeclaredProtocol;
^
/home/nuria/Git/swift-project/swift/scratch/test.h:46:1: note: protocol 'ForwardDeclaredProtocol' forward declared here
@protocol ForwardDeclaredProtocol;
^
test.swift:32:5: error: value of type 'Bar' has no member 'propertyUsingAForwardDeclaredProtocol'
bar.propertyUsingAForwardDeclaredProtocol = nil as Any?
~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:68:1: warning: function 'CFunctionReturningAForwardDeclaredProtocol' not imported
NSObject<ForwardDeclaredProtocol> *CFunctionReturningAForwardDeclaredProtocol();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:68:1: note: return type not imported
NSObject<ForwardDeclaredProtocol> *CFunctionReturningAForwardDeclaredProtocol();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:68:1: note: protocol 'ForwardDeclaredProtocol' is incomplete
NSObject<ForwardDeclaredProtocol> *CFunctionReturningAForwardDeclaredProtocol();
^
/home/nuria/Git/swift-project/swift/scratch/test.h:46:1: note: protocol 'ForwardDeclaredProtocol' forward declared here
@protocol ForwardDeclaredProtocol;
^
test.swift:33:5: error: cannot find 'CFunctionReturningAForwardDeclaredProtocol' in scope
_ = CFunctionReturningAForwardDeclaredProtocol()
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


/home/nuria/Git/swift-project/swift/scratch/test.h:70:1: warning: function 'CFunctionTakingAForwardDeclaredProtocolAsAParameter' not imported
void CFunctionTakingAForwardDeclaredProtocolAsAParameter(id<ForwardDeclaredProtocol> param1);
^
/home/nuria/Git/swift-project/swift/scratch/test.h:70:58: note: parameter 'param1' not imported
void CFunctionTakingAForwardDeclaredProtocolAsAParameter(id<ForwardDeclaredProtocol> param1);
                                                         ^
/home/nuria/Git/swift-project/swift/scratch/test.h:70:58: note: protocol 'ForwardDeclaredProtocol' is incomplete
void CFunctionTakingAForwardDeclaredProtocolAsAParameter(id<ForwardDeclaredProtocol> param1);
                                                         ^
/home/nuria/Git/swift-project/swift/scratch/test.h:46:1: note: protocol 'ForwardDeclaredProtocol' forward declared here
@protocol ForwardDeclaredProtocol;
^
test.swift:34:1: error: cannot find 'CFunctionTakingAForwardDeclaredProtocolAsAParameter' in scope
CFunctionTakingAForwardDeclaredProtocolAsAParameter(nil as Any?)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Hopefully, this example is illustrative. Note that the "unreferenced" declarations are not diagnosed, and all diagnostics end in the existing fatal "declaration does not exist / not found in scope" error.

Action

If there are any reasons to keep these usage triggered diagnostics behind a flag, I would appreciate any input or feedback.

Asides

Flag Naming

These diagnostics are new and untested enough to warrant the experimental label, but at a certain point it feels odd to have a feature labelled experimental enabled by default. Perhaps, a name change is in order.

3 Likes

Awesome, Nuri! I'm super excited for this. I think it's a great idea. This will really help people when they are using un-importable APIs. It's a much better user story than what we have today.

1 Like

Small update, this was landed this morning. If anyone has issues or questions, please let me know.

3 Likes