Skip to content

Commit d77d7fa

Browse files
Merge pull request #35305 from varungandhi-apple/vg-diag-clang-type-mismatch
[Sema] Diagnose type error on Clang type mismatch.
2 parents 571288f + 86b123a commit d77d7fa

17 files changed

+177
-46
lines changed

include/swift/AST/PrintOptions.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@ struct PrintOptions {
504504
}
505505

506506
/// Retrieve the set of options suitable for diagnostics printing.
507-
static PrintOptions printForDiagnostics(AccessLevel accessFilter) {
507+
static PrintOptions printForDiagnostics(AccessLevel accessFilter,
508+
bool printFullConvention) {
508509
PrintOptions result = printVerbose();
509510
result.PrintAccess = true;
510511
result.Indent = 4;
@@ -523,12 +524,16 @@ struct PrintOptions {
523524
QualifyNestedDeclarations::TypesOnly;
524525
result.PrintDocumentationComments = false;
525526
result.PrintCurrentMembersOnly = true;
527+
if (printFullConvention)
528+
result.PrintFunctionRepresentationAttrs =
529+
PrintOptions::FunctionRepresentationMode::Full;
526530
return result;
527531
}
528532

529533
/// Retrieve the set of options suitable for interface generation.
530-
static PrintOptions printInterface() {
531-
PrintOptions result = printForDiagnostics(AccessLevel::Public);
534+
static PrintOptions printInterface(bool printFullConvention) {
535+
PrintOptions result =
536+
printForDiagnostics(AccessLevel::Public, printFullConvention);
532537
result.SkipUnavailable = true;
533538
result.SkipImplicit = true;
534539
result.SkipSwiftPrivateClangDecls = true;
@@ -565,8 +570,8 @@ struct PrintOptions {
565570
/// Retrieve the set of options suitable for "Generated Interfaces", which
566571
/// are a prettified representation of the public API of a module, to be
567572
/// displayed to users in an editor.
568-
static PrintOptions printModuleInterface();
569-
static PrintOptions printTypeInterface(Type T);
573+
static PrintOptions printModuleInterface(bool printFullConvention);
574+
static PrintOptions printTypeInterface(Type T, bool printFullConvention);
570575

571576
void setBaseType(Type T);
572577

@@ -582,16 +587,16 @@ struct PrintOptions {
582587
}
583588

584589
/// Retrieve the print options that are suitable to print the testable interface.
585-
static PrintOptions printTestableInterface() {
586-
PrintOptions result = printInterface();
590+
static PrintOptions printTestableInterface(bool printFullConvention) {
591+
PrintOptions result = printInterface(printFullConvention);
587592
result.AccessFilter = AccessLevel::Internal;
588593
return result;
589594
}
590595

591596
/// Retrieve the print options that are suitable to print interface for a
592597
/// swift file.
593-
static PrintOptions printSwiftFileInterface() {
594-
PrintOptions result = printInterface();
598+
static PrintOptions printSwiftFileInterface(bool printFullConvention) {
599+
PrintOptions result = printInterface(printFullConvention);
595600
result.AccessFilter = AccessLevel::Internal;
596601
result.EmptyLineBetweenMembers = true;
597602
return result;

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,9 @@ namespace swift {
547547
/// Enable experimental support for one-way constraints for the
548548
/// parameters of closures.
549549
bool EnableOneWayClosureParameters = false;
550+
551+
/// See \ref FrontendOptions.PrintFullConvention
552+
bool PrintFullConvention = false;
550553
};
551554

552555
/// Options for controlling the behavior of the Clang importer.

include/swift/Sema/ConstraintSystem.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,17 @@ enum class ConstraintSystemFlags {
13751375
/// CodeCompletionExpr, and should continue in the presence of errors wherever
13761376
/// possible.
13771377
ForCodeCompletion = 0x40,
1378+
1379+
/// Include Clang function types when checking equality for function types.
1380+
///
1381+
/// When LangOptions.UseClangFunctionTypes is set, we can synthesize
1382+
/// different @convention(c) function types with the same parameter and result
1383+
/// types (similarly for blocks). This difference is reflected in the `cType:`
1384+
/// field (@convention(c, cType: ...)). When the cType is different, the types
1385+
/// should be treated as semantically different, as they may have different
1386+
/// calling conventions, say due to Clang attributes such as
1387+
/// `__attribute__((ns_consumed))`.
1388+
UseClangFunctionTypes = 0x80,
13781389
};
13791390

13801391
/// Options that affect the constraint system as a whole.

lib/AST/DiagnosticEngine.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -442,12 +442,13 @@ static bool shouldShowAKA(Type type, StringRef typeName) {
442442
/// with the same string representation, it should be qualified during
443443
/// formatting.
444444
static bool typeSpellingIsAmbiguous(Type type,
445-
ArrayRef<DiagnosticArgument> Args) {
445+
ArrayRef<DiagnosticArgument> Args,
446+
PrintOptions &PO) {
446447
for (auto arg : Args) {
447448
if (arg.getKind() == DiagnosticArgumentKind::Type) {
448449
auto argType = arg.getAsType();
449450
if (argType && !argType->isEqual(type) &&
450-
argType->getWithoutParens().getString() == type.getString()) {
451+
argType->getWithoutParens().getString(PO) == type.getString(PO)) {
451452
return true;
452453
}
453454
}
@@ -543,12 +544,22 @@ static void formatDiagnosticArgument(StringRef Modifier,
543544
Type type;
544545
bool needsQualification = false;
545546

547+
// TODO: We should use PrintOptions::printForDiagnostic here, or we should
548+
// rename that method.
549+
PrintOptions printOptions{};
550+
546551
if (Arg.getKind() == DiagnosticArgumentKind::Type) {
547552
type = Arg.getAsType()->getWithoutParens();
548-
needsQualification = typeSpellingIsAmbiguous(type, Args);
553+
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
554+
printOptions.PrintFunctionRepresentationAttrs =
555+
PrintOptions::FunctionRepresentationMode::Full;
556+
needsQualification = typeSpellingIsAmbiguous(type, Args, printOptions);
549557
} else {
550558
assert(Arg.getKind() == DiagnosticArgumentKind::FullyQualifiedType);
551559
type = Arg.getAsFullyQualifiedType().getType()->getWithoutParens();
560+
if (type->getASTContext().TypeCheckerOpts.PrintFullConvention)
561+
printOptions.PrintFunctionRepresentationAttrs =
562+
PrintOptions::FunctionRepresentationMode::Full;
552563
needsQualification = true;
553564
}
554565

@@ -574,12 +585,11 @@ static void formatDiagnosticArgument(StringRef Modifier,
574585
auto descriptiveKind = opaqueTypeDecl->getDescriptiveKind();
575586

576587
Out << llvm::format(FormatOpts.OpaqueResultFormatString.c_str(),
577-
type->getString().c_str(),
588+
type->getString(printOptions).c_str(),
578589
Decl::getDescriptiveKindName(descriptiveKind).data(),
579590
NamingDeclText.c_str());
580591

581592
} else {
582-
auto printOptions = PrintOptions();
583593
printOptions.FullyQualifiedTypes = needsQualification;
584594
std::string typeName = type->getString(printOptions);
585595

@@ -1016,8 +1026,11 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
10161026
// Pretty-print the declaration we've picked.
10171027
llvm::raw_svector_ostream out(buffer);
10181028
TrackingPrinter printer(entries, out, bufferAccessLevel);
1019-
ppDecl->print(printer,
1020-
PrintOptions::printForDiagnostics(bufferAccessLevel));
1029+
ppDecl->print(
1030+
printer,
1031+
PrintOptions::printForDiagnostics(
1032+
bufferAccessLevel,
1033+
decl->getASTContext().TypeCheckerOpts.PrintFullConvention));
10211034
}
10221035

10231036
// Build a buffer with the pretty-printed declaration.

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,9 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args,
770770
Opts.EnableOneWayClosureParameters |=
771771
Args.hasArg(OPT_experimental_one_way_closure_params);
772772

773+
Opts.PrintFullConvention |=
774+
Args.hasArg(OPT_experimental_print_full_convention);
775+
773776
Opts.DebugConstraintSolver |= Args.hasArg(OPT_debug_constraints);
774777
Opts.DebugGenericSignatures |= Args.hasArg(OPT_debug_generic_signatures);
775778

lib/FrontendTool/FrontendTool.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,8 @@ static void dumpAPIIfNeeded(const CompilerInstance &Instance) {
900900
SmallString<512> TempBuf;
901901
llvm::raw_svector_ostream TempOS(TempBuf);
902902

903-
PrintOptions PO = PrintOptions::printInterface();
903+
PrintOptions PO = PrintOptions::printInterface(
904+
Invocation.getFrontendOptions().PrintFullConvention);
904905
PO.PrintOriginalSourceText = true;
905906
PO.Indent = 2;
906907
PO.PrintAccess = false;

lib/IDE/CommentConversion.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,8 @@ visitDocComment(const DocComment *DC, TypeOrExtensionDecl SynthesizedTarget) {
359359
}
360360

361361
{
362-
PrintOptions PO = PrintOptions::printInterface();
362+
PrintOptions PO = PrintOptions::printInterface(
363+
D->getASTContext().TypeCheckerOpts.PrintFullConvention);
363364
PO.PrintAccess = false;
364365
PO.AccessFilter = AccessLevel::Private;
365366
PO.PrintDocumentationComments = false;

lib/IDE/IDETypeChecking.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ class ModulePrinterPrintableChecker: public ShouldPrintChecker {
5858
}
5959
};
6060

61-
PrintOptions PrintOptions::printModuleInterface() {
62-
PrintOptions result = printInterface();
61+
PrintOptions PrintOptions::printModuleInterface(bool printFullConvention) {
62+
PrintOptions result = printInterface(printFullConvention);
6363
result.CurrentPrintabilityChecker.reset(new ModulePrinterPrintableChecker());
6464
return result;
6565
}
6666

67-
PrintOptions PrintOptions::printTypeInterface(Type T) {
68-
PrintOptions result = printModuleInterface();
67+
PrintOptions PrintOptions::printTypeInterface(Type T,
68+
bool printFullConvention) {
69+
PrintOptions result = printModuleInterface(printFullConvention);
6970
result.PrintExtensionFromConformingProtocols = true;
7071
result.TransformContext = TypeTransformContext(T);
7172
result.printExtensionContentAsMembers = [T](const ExtensionDecl *ED) {
@@ -77,7 +78,8 @@ PrintOptions PrintOptions::printTypeInterface(Type T) {
7778
}
7879

7980
PrintOptions PrintOptions::printDocInterface() {
80-
PrintOptions result = PrintOptions::printModuleInterface();
81+
PrintOptions result =
82+
PrintOptions::printModuleInterface(/*printFullConvention*/ false);
8183
result.PrintAccess = false;
8284
result.SkipUnavailable = false;
8385
result.ExcludeAttrList.push_back(DAK_Available);

lib/IDE/ModuleInterfacePrinting.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ printTypeInterface(ModuleDecl *M, Type Ty, ASTPrinter &Printer,
172172
}
173173
Ty = Ty->getRValueType();
174174
if (auto ND = Ty->getNominalOrBoundGenericNominal()) {
175-
PrintOptions Options = PrintOptions::printTypeInterface(Ty.getPointer());
175+
PrintOptions Options = PrintOptions::printTypeInterface(
176+
Ty.getPointer(),
177+
Ty->getASTContext().TypeCheckerOpts.PrintFullConvention);
176178
ND->print(Printer, Options);
177179
printTypeNameToString(Ty, TypeName);
178180
return false;

lib/Sema/CSSimplify.cpp

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,19 +1557,38 @@ isSubtypeOf(FunctionTypeRepresentation potentialSubRepr,
15571557
|| isThickRepresentation(potentialSuperRepr);
15581558
}
15591559

1560-
/// Returns true if `constraint rep1 rep2` is satisfied.
1561-
static bool matchFunctionRepresentations(FunctionTypeRepresentation rep1,
1562-
FunctionTypeRepresentation rep2,
1563-
ConstraintKind kind) {
1560+
/// Returns true if `constraint extInfo1 extInfo2` is satisfied.
1561+
static bool matchFunctionRepresentations(FunctionType::ExtInfo einfo1,
1562+
FunctionType::ExtInfo einfo2,
1563+
ConstraintKind kind,
1564+
ConstraintSystemOptions options) {
1565+
auto rep1 = einfo1.getRepresentation();
1566+
auto rep2 = einfo2.getRepresentation();
1567+
bool clangTypeMismatch =
1568+
(options.contains(ConstraintSystemFlags::UseClangFunctionTypes) &&
1569+
(einfo1.getClangTypeInfo() != einfo2.getClangTypeInfo()));
15641570
switch (kind) {
15651571
case ConstraintKind::Bind:
15661572
case ConstraintKind::BindParam:
15671573
case ConstraintKind::BindToPointerType:
15681574
case ConstraintKind::Equal:
1569-
return rep1 == rep2;
1570-
1575+
return (rep1 == rep2) && !clangTypeMismatch;
1576+
15711577
case ConstraintKind::Subtype: {
1572-
return isSubtypeOf(rep1, rep2);
1578+
// Breakdown of cases:
1579+
// 1. isSubtypeOf(rep1, rep2) == false (hence rep1 != rep2):
1580+
// In this case, this function will return false, indicating that we
1581+
// can't convert. E.g. you can't convert from @convention(swift) to
1582+
// @convention(c).
1583+
// 2. isSubtypeOf(rep1, rep2) == true and rep1 != rep2:
1584+
// In this case, this function will return true, indicating that we
1585+
// can convert, because the Clang type doesn't matter when converting
1586+
// between different representations. E.g. it is okay to convert from
1587+
// @convention(c) (regardless of cType) to @convention(swift).
1588+
// 3. isSubtypeOf(rep1, rep2) == true and rep1 == rep2:
1589+
// In this case, the function returns !clangTypeMismatch, as we forbid
1590+
// conversions between @convention(c) functions with different cTypes.
1591+
return isSubtypeOf(rep1, rep2) && ((rep1 != rep2) || !clangTypeMismatch);
15731592
}
15741593

15751594
// [NOTE: diagnose-swift-to-c-convention-change]: @convention(swift) ->
@@ -1588,6 +1607,19 @@ static bool matchFunctionRepresentations(FunctionTypeRepresentation rep1,
15881607
case ConstraintKind::Conversion:
15891608
case ConstraintKind::ArgumentConversion:
15901609
case ConstraintKind::OperatorArgumentConversion:
1610+
// For now, forbid conversion if representations match but cTypes differ.
1611+
//
1612+
// let f : @convention(c, cType: "id (*)(void) __attribute__((ns_returns_retained))")
1613+
// () -> AnyObject = ...
1614+
// let _ : @convention(c, cType: "id (*)(void)")
1615+
// () -> AnyObject = f // error
1616+
// let g : @convention(c, cType: "void (*)(void *)")
1617+
// (OpaquePointer?) -> () = ...
1618+
// let _ : @convention(c, cType: "void (*)(MyCtx *)")
1619+
// (OpaquePointer?) -> () = g // error
1620+
if ((rep1 == rep2) && clangTypeMismatch) {
1621+
return false;
1622+
}
15911623
return true;
15921624

15931625
case ConstraintKind::OpaqueUnderlyingType:
@@ -1908,9 +1940,8 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
19081940
return getTypeMatchFailure(locator);
19091941
}
19101942

1911-
if (!matchFunctionRepresentations(func1->getExtInfo().getRepresentation(),
1912-
func2->getExtInfo().getRepresentation(),
1913-
kind)) {
1943+
if (!matchFunctionRepresentations(func1->getExtInfo(), func2->getExtInfo(),
1944+
kind, Options)) {
19141945
return getTypeMatchFailure(locator);
19151946
}
19161947

lib/Sema/ConstraintSystem.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ ConstraintSystem::ConstraintSystem(DeclContext *dc,
8686
DC->getParentModule()->isMainModule()) {
8787
Options |= ConstraintSystemFlags::DebugConstraints;
8888
}
89+
if (Context.LangOpts.UseClangFunctionTypes)
90+
Options |= ConstraintSystemFlags::UseClangFunctionTypes;
8991
}
9092

9193
ConstraintSystem::~ConstraintSystem() {

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3098,8 +3098,8 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
30983098
}
30993099
}
31003100

3101-
PrintOptions Options =
3102-
PrintOptions::printForDiagnostics(AccessLevel::Private);
3101+
PrintOptions Options = PrintOptions::printForDiagnostics(
3102+
AccessLevel::Private, Ctx.TypeCheckerOpts.PrintFullConvention);
31033103
Options.PrintDocumentationComments = false;
31043104
Options.PrintAccess = false;
31053105
Options.SkipAttributes = true;

lib/SymbolGraphGen/SymbolGraph.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,10 @@ void SymbolGraph::recordConformanceSynthesizedMemberRelationships(Symbol S) {
339339
return;
340340
}
341341

342-
SynthesizedExtensionAnalyzer
343-
ExtensionAnalyzer(const_cast<NominalTypeDecl*>(OwningNominal),
344-
PrintOptions::printModuleInterface());
342+
SynthesizedExtensionAnalyzer ExtensionAnalyzer(
343+
const_cast<NominalTypeDecl *>(OwningNominal),
344+
PrintOptions::printModuleInterface(
345+
OwningNominal->getASTContext().TypeCheckerOpts.PrintFullConvention));
345346
auto MergeGroupKind = SynthesizedExtensionAnalyzer::MergeGroupKind::All;
346347
ExtensionAnalyzer.forEachExtensionMergeGroup(MergeGroupKind,
347348
[&](ArrayRef<ExtensionInfo> ExtensionInfos){
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-typecheck-verify-swift -sdk %clang-importer-sdk -experimental-print-full-convention -use-clang-function-types
2+
3+
import ctypes
4+
5+
// Setting a C function type with the correct cType works.
6+
let f1 : (@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)? = getFunctionPointer_()
7+
8+
// However, trying to convert between @convention(c) functions
9+
// with differing cTypes doesn't work.
10+
11+
let _ : @convention(c) (Int) -> Int = f1!
12+
// expected-error@-1{{cannot convert value of type '@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int' to specified type '@convention(c) (Int) -> Int'}}
13+
14+
let _ : (@convention(c) (Int) -> Int)? = f1
15+
// expected-error@-1{{cannot convert value of type '(@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?' to specified type '(@convention(c) (Int) -> Int)?'}}
16+
17+
let _ : (@convention(c, cType: "void *(*)(void *)") (Int) -> Int)? = f1
18+
// expected-error@-1{{cannot convert value of type '(@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?' to specified type '(@convention(c, cType: "void *(*)(void *)") (Int) -> Int)?'}}
19+
20+
21+
// Converting from @convention(c) -> @convention(swift) works
22+
23+
let _ : (Int) -> Int = ({ x in x } as @convention(c) (Int) -> Int)
24+
let _ : (Int) -> Int = ({ x in x } as @convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)
25+
26+
27+
// Converting from @convention(swift) -> @convention(c) doesn't work.
28+
29+
let fs : (Int) -> Int = { x in x }
30+
31+
let _ : @convention(c) (Int) -> Int = fs
32+
// expected-error@-1{{a C function pointer can only be formed from a reference to a 'func' or a literal closure}}
33+
34+
let _ : @convention(c, cType: "size_t (*)(size_t)") (Int) -> Int = fs
35+
// expected-error@-1{{a C function pointer can only be formed from a reference to a 'func' or a literal closure}}
36+
37+
38+
// More complex examples.
39+
40+
let f2 : (@convention(c) ((@convention(c, cType: "size_t (*)(size_t)") (Swift.Int) -> Swift.Int)?) -> (@convention(c, cType: "size_t (*)(size_t)") (Swift.Int) -> Swift.Int)?)? = getHigherOrderFunctionPointer()!
41+
42+
let _ : (@convention(c) ((@convention(c) (Swift.Int) -> Swift.Int)?) -> (@convention(c, cType: "size_t (*)(size_t)") (Swift.Int) -> Swift.Int)?)? = f2!
43+
// expected-error@-1{{cannot convert value of type '@convention(c) ((@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?) -> (@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?' to specified type '(@convention(c) ((@convention(c) (Int) -> Int)?) -> (@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?)?'}}
44+
45+
let _ : (@convention(c) ((@convention(c) (Swift.Int) -> Swift.Int)?) -> (@convention(c) (Swift.Int) -> Swift.Int)?)? = f2!
46+
// expected-error@-1{{cannot convert value of type '@convention(c) ((@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?) -> (@convention(c, cType: "size_t (*)(size_t)") (Int) -> Int)?' to specified type '(@convention(c) ((@convention(c) (Int) -> Int)?) -> (@convention(c) (Int) -> Int)?)?'}}
47+
48+
let f3 = getFunctionPointer3
49+
50+
let _ : @convention(c) (UnsafeMutablePointer<ctypes.Dummy>?) -> UnsafeMutablePointer<ctypes.Dummy>? = f3()!

0 commit comments

Comments
 (0)