Skip to content

Implement Richer Diagnostics for Cross-File Synthesis Failures #33410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2765,8 +2765,9 @@ ERROR(cannot_synthesize_init_in_extension_of_nonfinal,none,
"be satisfied by a 'required' initializer in the class definition",
(Type, DeclName))
ERROR(cannot_synthesize_in_crossfile_extension,none,
"implementation of %0 cannot be automatically synthesized in an extension "
"in a different file to the type", (Type))
"extension outside of file declaring %0 %1 prevents automatic synthesis "
"of %2 for protocol %3",
(DescriptiveDeclKind, DeclName, DeclName, Type))

ERROR(broken_additive_arithmetic_requirement,none,
"AdditiveArithmetic protocol is broken: unexpected requirement", ())
Expand Down
20 changes: 20 additions & 0 deletions lib/Sema/DerivedConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

#include "TypeChecker.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/Expr.h"
Expand Down Expand Up @@ -490,8 +491,27 @@ bool DerivedConformance::checkAndDiagnoseDisallowedContext(
Nominal->getModuleScopeContext() !=
getConformanceContext()->getModuleScopeContext()) {
ConformanceDecl->diagnose(diag::cannot_synthesize_in_crossfile_extension,
Nominal->getDescriptiveKind(), Nominal->getName(),
synthesizing->getName(),
getProtocolType());
Nominal->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);

// In editor mode, try to insert a stub.
if (Context.LangOpts.DiagnosticsEditorMode) {
auto Extension = cast<ExtensionDecl>(getConformanceContext());
auto FixitLocation = Extension->getBraces().Start;
llvm::SmallString<128> Text;
{
llvm::raw_svector_ostream SS(Text);
swift::printRequirementStub(synthesizing, Nominal,
Nominal->getDeclaredType(),
Extension->getStartLoc(), SS);
if (!Text.empty()) {
ConformanceDecl->diagnose(diag::missing_witnesses_general)
.fixItInsertAfter(FixitLocation, Text.str());
}
}
}
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions localization/diagnostics/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6556,8 +6556,8 @@

- id: cannot_synthesize_in_crossfile_extension
msg: >-
implementation of %0 cannot be automatically synthesized in an extension
in a different file to the type
extension outside of file declaring %0 %1 prevents automatic synthesis
of %2 for protocol %3

- id: broken_additive_arithmetic_requirement
msg: >-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,10 @@ class WrappedProperties: Differentiable {

// Test derived conformances in disallowed contexts.

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

// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
extension GenericOtherFileNonconforming: Differentiable {}
// expected-error @-1 {{extension outside of file declaring generic class 'GenericOtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
// expected-error @-2 {{extension outside of file declaring generic class 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ where T: AdditiveArithmetic {}

// Test derived conformances in disallowed contexts.

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

// expected-error @+1 3 {{implementation of 'AdditiveArithmetic' cannot be automatically synthesized in an extension in a different file to the type}}
extension GenericOtherFileNonconforming: AdditiveArithmetic {}
// expected-error @-1 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zero' for protocol 'AdditiveArithmetic'}}
// expected-error @-2 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '+' for protocol 'AdditiveArithmetic'}}
// expected-error @-3 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of '-' for protocol 'AdditiveArithmetic'}}
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,10 @@ struct WrappedProperties: Differentiable {

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

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

// expected-error @+1 2 {{implementation of 'Differentiable' cannot be automatically synthesized in an extension in a different file to the type}}
extension GenericOtherFileNonconforming: Differentiable {}
// expected-error @-1 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'move(along:)' for protocol 'Differentiable'}}
// expected-error @-2 {{extension outside of file declaring generic struct 'GenericOtherFileNonconforming' prevents automatic synthesis of 'zeroTangentVectorInitializer' for protocol 'Differentiable'}}
6 changes: 6 additions & 0 deletions test/Sema/Inputs/fixits-derived-conformances-multifile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public enum Enum { case one } // expected-note {{type declared here}}
public enum GenericEnum<T> { case one(Int) } // expected-note {{type declared here}}

public struct Struct {} // expected-note {{type declared here}}
public struct GenericStruct<T> {} // expected-note {{type declared here}}

6 changes: 3 additions & 3 deletions test/Sema/enum_conformance_synthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ enum Complex2 {
}
extension Complex2 : Hashable {}
extension Complex2 : CaseIterable {} // expected-error {{type 'Complex2' does not conform to protocol 'CaseIterable'}}
extension FromOtherFile: CaseIterable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
extension FromOtherFile: CaseIterable {} // expected-error {{extension outside of file declaring enum 'FromOtherFile' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}}
extension CaseIterableAcrossFiles: CaseIterable {
public static var allCases: [CaseIterableAcrossFiles] {
return [ .A ]
Expand All @@ -248,7 +248,7 @@ extension OtherFileNonconforming: Hashable {
func hash(into hasher: inout Hasher) {}
}
// ...but synthesis in a type defined in another file doesn't work yet.
extension YetOtherFileNonconforming: Equatable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring enum 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}
extension YetOtherFileNonconforming: CaseIterable {} // expected-error {{does not conform}}

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

// rdar://problem/41852654

Expand Down
21 changes: 21 additions & 0 deletions test/Sema/fixits-derived-conformances.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift -emit-module -emit-library -module-name Types %S/Inputs/fixits-derived-conformances-multifile.swift -o %t/%target-library-name(Types)
// RUN: %swift -typecheck -target %target-triple -I %t -diagnostics-editor-mode -verify %s

import Types

extension GenericEnum: Equatable { }
// expected-error@-1 {{extension outside of file declaring generic enum 'GenericEnum' prevents automatic synthesis of '==' for protocol 'Equatable'}}
// 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}}

extension Struct: Equatable { }
// expected-error@-1 {{extension outside of file declaring struct 'Struct' prevents automatic synthesis of '==' for protocol 'Equatable'}}
// 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}}
extension GenericStruct: Equatable { }
// expected-error@-1 {{extension outside of file declaring generic struct 'GenericStruct' prevents automatic synthesis of '==' for protocol 'Equatable'}}
// 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}}

extension Enum: CaseIterable { }
// expected-error@-1 {{extension outside of file declaring enum 'Enum' prevents automatic synthesis of 'allCases' for protocol 'CaseIterable'}}
// expected-note@-2 {{do you want to add protocol stubs?}}{{31-31=\n public static var allCases: [Enum]\n}}

4 changes: 2 additions & 2 deletions test/Sema/struct_equatable_hashable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ extension OtherFileNonconforming: Hashable {
func hash(into hasher: inout Hasher) {}
}
// ...but synthesis in a type defined in another file doesn't work yet.
extension YetOtherFileNonconforming: Equatable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}}
extension YetOtherFileNonconforming: Equatable {} // expected-error {{extension outside of file declaring struct 'YetOtherFileNonconforming' prevents automatic synthesis of '==' for protocol 'Equatable'}}

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

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

// rdar://problem/41852654

Expand Down