Skip to content

Commit 3a3f92a

Browse files
committed
Implement Richer Diagnostics for Cross-File Synthesis Failures
Mention the type, the requirement, and the extension in the error that follows. In editor mode, try to insert stubs for the missing requirement as well so the user isn't just left with a pile of unactionable errors.
1 parent 147274b commit 3a3f92a

File tree

10 files changed

+71
-15
lines changed

10 files changed

+71
-15
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,8 +2765,9 @@ ERROR(cannot_synthesize_init_in_extension_of_nonfinal,none,
27652765
"be satisfied by a 'required' initializer in the class definition",
27662766
(Type, DeclName))
27672767
ERROR(cannot_synthesize_in_crossfile_extension,none,
2768-
"implementation of %0 cannot be automatically synthesized in an extension "
2769-
"in a different file to the type", (Type))
2768+
"extension outside of file declaring %0 %1 prevents automatic synthesis "
2769+
"of %2 for protocol %3",
2770+
(DescriptiveDeclKind, DeclName, DeclName, Type))
27702771

27712772
ERROR(broken_additive_arithmetic_requirement,none,
27722773
"AdditiveArithmetic protocol is broken: unexpected requirement", ())

lib/Sema/DerivedConformances.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "TypeChecker.h"
14+
#include "swift/AST/ASTPrinter.h"
1415
#include "swift/AST/Decl.h"
1516
#include "swift/AST/Stmt.h"
1617
#include "swift/AST/Expr.h"
@@ -490,8 +491,27 @@ bool DerivedConformance::checkAndDiagnoseDisallowedContext(
490491
Nominal->getModuleScopeContext() !=
491492
getConformanceContext()->getModuleScopeContext()) {
492493
ConformanceDecl->diagnose(diag::cannot_synthesize_in_crossfile_extension,
494+
Nominal->getDescriptiveKind(), Nominal->getName(),
495+
synthesizing->getName(),
493496
getProtocolType());
494497
Nominal->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
498+
499+
// In editor mode, try to insert a stub.
500+
if (Context.LangOpts.DiagnosticsEditorMode) {
501+
auto Extension = cast<ExtensionDecl>(getConformanceContext());
502+
auto FixitLocation = Extension->getBraces().Start;
503+
llvm::SmallString<128> Text;
504+
{
505+
llvm::raw_svector_ostream SS(Text);
506+
swift::printRequirementStub(synthesizing, Nominal,
507+
Nominal->getDeclaredType(),
508+
Extension->getStartLoc(), SS);
509+
if (!Text.empty()) {
510+
ConformanceDecl->diagnose(diag::missing_witnesses_general)
511+
.fixItInsertAfter(FixitLocation, Text.str());
512+
}
513+
}
514+
}
495515
return true;
496516
}
497517

localization/diagnostics/en.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6556,8 +6556,8 @@
65566556
65576557
- id: cannot_synthesize_in_crossfile_extension
65586558
msg: >-
6559-
implementation of %0 cannot be automatically synthesized in an extension
6560-
in a different file to the type
6559+
extension outside of file declaring %0 %1 prevents automatic synthesis
6560+
of %2 for protocol %3
65616561
65626562
- id: broken_additive_arithmetic_requirement
65636563
msg: >-

test/AutoDiff/Sema/DerivedConformances/class_differentiable.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,10 @@ class WrappedProperties: Differentiable {
570570

571571
// Test derived conformances in disallowed contexts.
572572

573-
// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
574573
extension OtherFileNonconforming: Differentiable {}
574+
// expected-error @-1 {{extension outside of file declaring class 'OtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
575+
// expected-error @-2 {{extension outside of file declaring class 'OtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}
575576

576-
// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
577577
extension GenericOtherFileNonconforming: Differentiable {}
578+
// expected-error @-1 {{extension outside of file declaring generic class 'GenericOtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
579+
// expected-error @-2 {{extension outside of file declaring generic class 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}

test/AutoDiff/Sema/DerivedConformances/struct_additive_arithmetic.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,12 @@ where T: AdditiveArithmetic {}
113113

114114
// Test derived conformances in disallowed contexts.
115115

116-
// expected-error @+1 3 {{implementation of 'AdditiveArithmetic' cannot be automatically synthesized in an extension in a different file to the type}}
117116
extension OtherFileNonconforming: AdditiveArithmetic {}
117+
// expected-error @-1 {{extension outside of file declaring struct 'OtherFileNonconforming' prevents automatic synthesis of 'zero' for protocol 'AdditiveArithmetic'}}
118+
// expected-error @-2 {{extension outside of file declaring struct 'OtherFileNonconforming' prevents automatic synthesis of '+' for protocol 'AdditiveArithmetic'}}
119+
// expected-error @-3 {{extension outside of file declaring struct 'OtherFileNonconforming' prevents automatic synthesis of '-' for protocol 'AdditiveArithmetic'}}
118120

119-
// expected-error @+1 3 {{implementation of 'AdditiveArithmetic' cannot be automatically synthesized in an extension in a different file to the type}}
120121
extension GenericOtherFileNonconforming: AdditiveArithmetic {}
122+
// expected-error @-1 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zero' for protocol 'AdditiveArithmetic'}}
123+
// expected-error @-2 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '+' for protocol 'AdditiveArithmetic'}}
124+
// expected-error @-3 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '-' for protocol 'AdditiveArithmetic'}}

test/AutoDiff/Sema/DerivedConformances/struct_differentiable.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,10 @@ struct WrappedProperties: Differentiable {
383383

384384
// Verify that cross-file derived conformances are disallowed.
385385

386-
// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
387386
extension OtherFileNonconforming: Differentiable {}
387+
// expected-error @-1 {{extension outside of file declaring struct 'OtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
388+
// expected-error @-2 {{extension outside of file declaring struct 'OtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}
388389

389-
// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
390390
extension GenericOtherFileNonconforming: Differentiable {}
391+
// expected-error @-1 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
392+
// expected-error @-2 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public enum Enum { case one } // expected-note {{type declared here}}
2+
public enum GenericEnum<T> { case one(Int) } // expected-note {{type declared here}}
3+
4+
public struct Struct {} // expected-note {{type declared here}}
5+
public struct GenericStruct<T> {} // expected-note {{type declared here}}
6+

test/Sema/enum_conformance_synthesis.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ enum Complex2 {
224224
}
225225
extension Complex2 : Hashable {}
226226
extension Complex2 : CaseIterable {} // expected-error {{type 'Complex2' does not conform to protocol 'CaseIterable'}}
227-
extension FromOtherFile: CaseIterable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
227+
extension FromOtherFile: CaseIterable {} // expected-error {{extension outside of file declaring enum 'FromOtherFile' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}}
228228
extension CaseIterableAcrossFiles: CaseIterable {
229229
public static var allCases: [CaseIterableAcrossFiles] {
230230
return [ .A ]
@@ -248,7 +248,7 @@ extension OtherFileNonconforming: Hashable {
248248
func hash(into hasher: inout Hasher) {}
249249
}
250250
// ...but synthesis in a type defined in another file doesn't work yet.
251-
extension YetOtherFileNonconforming: Equatable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
251+
extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring enum 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}
252252
extension YetOtherFileNonconforming: CaseIterable {} // expected-error {{does not conform}}
253253

254254
// Verify that an indirect enum doesn't emit any errors as long as its "leaves"
@@ -319,7 +319,7 @@ extension UnusedGenericDeriveExtension: Hashable {}
319319
// Cross-file synthesis is disallowed for conditional cases just as it is for
320320
// non-conditional ones.
321321
extension GenericOtherFileNonconforming: Equatable where T: Equatable {}
322-
// expected-error@-1{{implementation of 'Equatable' cannot be automatically synthesized in an extension in a different file to the type}}
322+
// expected-error@-1{{extension outside of file declaring generic enum 'GenericOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}
323323

324324
// rdar://problem/41852654
325325

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -emit-module -emit-library -module-name Types %S/Inputs/fixits-derived-conformances-multifile.swift -o %t/%target-library-name(Types)
3+
// RUN: %swift -typecheck -target %target-triple -I %t -diagnostics-editor-mode -verify %s
4+
5+
import Types
6+
7+
extension GenericEnum: Equatable { }
8+
// expected-error@-1 {{extension outside of file declaring generic enum 'GenericEnum' prevents automatic synthesis of '==' for protocol 'Equatable'}}
9+
// expected-note@-2 {{do you want to add protocol stubs?}}{{35-35=\n public static func == (lhs: GenericEnum, rhs: GenericEnum) -> Bool {\n <#code#>\n \}\n}}
10+
11+
extension Struct: Equatable { }
12+
// expected-error@-1 {{extension outside of file declaring struct 'Struct' prevents automatic synthesis of '==' for protocol 'Equatable'}}
13+
// expected-note@-2 {{do you want to add protocol stubs?}}{{30-30=\n public static func == (lhs: Struct, rhs: Struct) -> Bool {\n <#code#>\n \}\n}}
14+
extension GenericStruct: Equatable { }
15+
// expected-error@-1 {{extension outside of file declaring generic struct 'GenericStruct' prevents automatic synthesis of '==' for protocol 'Equatable'}}
16+
// expected-note@-2 {{do you want to add protocol stubs?}}{{37-37=\n public static func == (lhs: GenericStruct, rhs: GenericStruct) -> Bool {\n <#code#>\n \}\n}}
17+
18+
extension Enum: CaseIterable { }
19+
// expected-error@-1 {{extension outside of file declaring enum 'Enum' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}}
20+
// expected-note@-2 {{do you want to add protocol stubs?}}{{31-31=\n public static var allCases: [Enum]\n}}
21+

test/Sema/struct_equatable_hashable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ extension OtherFileNonconforming: Hashable {
196196
func hash(into hasher: inout Hasher) {}
197197
}
198198
// ...but synthesis in a type defined in another file doesn't work yet.
199-
extension YetOtherFileNonconforming: Equatable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
199+
extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring struct 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}
200200

201201
// Verify that we can add Hashable conformance in an extension by only
202202
// implementing hash(into:)
@@ -253,7 +253,7 @@ extension UnusedGenericDeriveExtension: Hashable {}
253253

254254
// Cross-file synthesis is still disallowed for conditional cases
255255
extension GenericOtherFileNonconforming: Equatable where T: Equatable {}
256-
// expected-error@-1{{implementation of 'Equatable' cannot be automatically synthesized in an extension in a different file to the type}}
256+
// expected-error@-1{{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}
257257

258258
// rdar://problem/41852654
259259

0 commit comments

Comments
 (0)