Skip to content

Commit e0bbcef

Browse files
committed
[AutoDiff] [Sema] Limit implicit @differentiable attribute creation.
During protocol witness matching for a protocol requirement with `@differentiable` attributes, implicit `@differentiable` attributes may be created for the witness under specific conditions (when the witness has a `@differentiable` attribute with superset differentiability parameters, or when the witness has less-than-public visibility). Do not generate implicit `@differentiable` attributes for protocol witnesses when the protocol conformance is declared from a separate file from the witness. Otherwise, compilation of the file containing the conformance creates references to external symbols for the implicit `@differentiable` attributes, even though no such symbols exist. Resolves SR-13455.
1 parent f898747 commit e0bbcef

File tree

6 files changed

+146
-24
lines changed

6 files changed

+146
-24
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3112,10 +3112,10 @@ ERROR(overriding_decl_missing_differentiable_attr,none,
31123112
"overriding declaration is missing attribute '%0'", (StringRef))
31133113
NOTE(protocol_witness_missing_differentiable_attr,none,
31143114
"candidate is missing attribute '%0'", (StringRef))
3115-
NOTE(protocol_witness_missing_differentiable_attr_nonpublic_other_file,none,
3116-
"non-public %1 %2 must have explicit '%0' attribute to satisfy "
3117-
"requirement %3 %4 (in protocol %6) because it is declared in a different "
3118-
"file than the conformance of %5 to %6",
3115+
NOTE(protocol_witness_missing_differentiable_attr_other_file,none,
3116+
"%1 %2 must have explicit '%0' attribute to satisfy requirement %3 %4 (in "
3117+
"protocol %6) because it is declared in a different file than the "
3118+
"conformance of %5 to %6",
31193119
(StringRef, DescriptiveDeclKind, DeclName, DescriptiveDeclKind, DeclName,
31203120
Type, Type))
31213121

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -384,23 +384,15 @@ matchWitnessDifferentiableAttr(DeclContext *dc, ValueDecl *req,
384384
supersetConfig = witnessConfig;
385385
}
386386
if (!foundExactConfig) {
387-
bool success = false;
388387
// If no exact witness derivative configuration was found, check
389388
// conditions for creating an implicit witness `@differentiable` attribute
390-
// with the exact derivative configuration:
391-
// - If the witness has a "superset" derivative configuration.
392-
// - If the witness is less than public and is declared in the same file
393-
// as the conformance.
394-
// - `@differentiable` attributes are really only significant for public
395-
// declarations: it improves usability to not require explicit
396-
// `@differentiable` attributes for less-visible declarations.
397-
bool createImplicitWitnessAttribute =
398-
supersetConfig || witness->getFormalAccess() < AccessLevel::Public;
399-
// If the witness has less-than-public visibility and is declared in a
400-
// different file than the conformance, produce an error.
401-
if (!supersetConfig && witness->getFormalAccess() < AccessLevel::Public &&
402-
dc->getModuleScopeContext() !=
403-
witness->getDeclContext()->getModuleScopeContext()) {
389+
// with the exact derivative configuration.
390+
391+
// If witness is declared in a different file as the conformance, we
392+
// should not create an implicit `@differentiable` attribute on the
393+
// witness. Produce an error.
394+
if (dc->getModuleScopeContext() !=
395+
witness->getDeclContext()->getModuleScopeContext()) {
404396
// FIXME(TF-1014): `@differentiable` attribute diagnostic does not
405397
// appear if associated type inference is involved.
406398
if (auto *vdWitness = dyn_cast<VarDecl>(witness)) {
@@ -412,6 +404,20 @@ matchWitnessDifferentiableAttr(DeclContext *dc, ValueDecl *req,
412404
reqDiffAttr);
413405
}
414406
}
407+
408+
// Otherwise, the witness must:
409+
// - Have a "superset" derivative configuration.
410+
// - Have less than public visibility.
411+
// - `@differentiable` attributes are really only significant for
412+
// public declarations: it improves usability to not require
413+
// explicit `@differentiable` attributes for less-visible
414+
// declarations.
415+
//
416+
// If these conditions are met, an implicit `@differentiable` attribute
417+
// with the exact derivative configuration can be created.
418+
bool success = false;
419+
bool createImplicitWitnessAttribute =
420+
supersetConfig || witness->getFormalAccess() < AccessLevel::Public;
415421
if (createImplicitWitnessAttribute) {
416422
auto derivativeGenSig = witnessAFD->getGenericSignature();
417423
if (supersetConfig)
@@ -2448,16 +2454,15 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
24482454
llvm::raw_string_ostream os(reqDiffAttrString);
24492455
reqAttr->print(os, req, omitWrtClause);
24502456
os.flush();
2451-
// If the witness has less-than-public visibility and is declared in a
2452-
// different file than the conformance, emit a specialized diagnostic.
2453-
if (witness->getFormalAccess() < AccessLevel::Public &&
2454-
conformance->getDeclContext()->getModuleScopeContext() !=
2457+
// If the witness is declared in a different file than the conformance, emit
2458+
// a specialized diagnostic.
2459+
if (conformance->getDeclContext()->getModuleScopeContext() !=
24552460
witness->getDeclContext()->getModuleScopeContext()) {
24562461
diags
24572462
.diagnose(
24582463
witness,
24592464
diag::
2460-
protocol_witness_missing_differentiable_attr_nonpublic_other_file,
2465+
protocol_witness_missing_differentiable_attr_other_file,
24612466
reqDiffAttrString, witness->getDescriptiveKind(),
24622467
witness->getName(), req->getDescriptiveKind(),
24632468
req->getName(), conformance->getType(),
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import _Differentiation
2+
3+
protocol Protocol1: Differentiable {
4+
// expected-note @+2 {{protocol requires function 'internalMethod1' with type '(Float) -> Float'}}
5+
@differentiable(wrt: (self, x))
6+
func internalMethod1(_ x: Float) -> Float
7+
8+
// expected-note @+3 {{protocol requires function 'internalMethod2' with type '(Float) -> Float'}}
9+
@differentiable(wrt: x)
10+
@differentiable(wrt: (self, x))
11+
func internalMethod2(_ x: Float) -> Float
12+
13+
// expected-note @+3 {{protocol requires function 'internalMethod3' with type '(Float) -> Float'}}
14+
@differentiable(wrt: x)
15+
@differentiable(wrt: (self, x))
16+
func internalMethod3(_ x: Float) -> Float
17+
}
18+
19+
protocol Protocol2: Differentiable {
20+
@differentiable(wrt: (self, x))
21+
func internalMethod4(_ x: Float) -> Float
22+
}
23+
24+
// Note:
25+
// - No `ConformingStruct: Protocol1` conformance exists in this file, so this
26+
// file should compile just file.
27+
// - A `ConformingStruct: Protocol1` conformance in a different file should be
28+
// diagnosed to prevent linker errors. Without a diagnostic, compilation of
29+
// the other file creates external references to symbols for implicit
30+
// `@differentiable` attributes, even though no such symbols exist.
31+
// Context: https://github.com/apple/swift/pull/29771#issuecomment-585059721
32+
33+
struct ConformingStruct: Differentiable {
34+
// Error for missing `@differentiable` attribute.
35+
// expected-note @+1 {{instance method 'internalMethod1' must have explicit '@differentiable' attribute to satisfy requirement instance method 'internalMethod1' (in protocol 'Protocol1') because it is declared in a different file than the conformance of 'ConformingStruct' to 'Protocol1'}} {{3-3=@differentiable }}
36+
func internalMethod1(_ x: Float) -> Float {
37+
x
38+
}
39+
40+
// Error for missing `@differentiable` superset attribute.
41+
// expected-note @+2 {{instance method 'internalMethod2' must have explicit '@differentiable' attribute to satisfy requirement instance method 'internalMethod2' (in protocol 'Protocol1') because it is declared in a different file than the conformance of 'ConformingStruct' to 'Protocol1'}} {{3-3=@differentiable }}
42+
@differentiable(wrt: x)
43+
func internalMethod2(_ x: Float) -> Float {
44+
x
45+
}
46+
47+
// Error for missing `@differentiable` subset attribute.
48+
// expected-note @+2 {{instance method 'internalMethod3' must have explicit '@differentiable(wrt: x)' attribute to satisfy requirement instance method 'internalMethod3' (in protocol 'Protocol1') because it is declared in a different file than the conformance of 'ConformingStruct' to 'Protocol1'}} {{3-3=@differentiable(wrt: x) }}
49+
@differentiable(wrt: (self, x))
50+
func internalMethod3(_ x: Float) -> Float {
51+
x
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import _Differentiation
2+
3+
protocol P1: Differentiable {
4+
@differentiable(wrt: self)
5+
// expected-note @+1 {{protocol requires function 'callAsFunction' with type '(Float) -> Float'}}
6+
func callAsFunction(_ input: Float) -> Float
7+
}
8+
9+
protocol P2: P1 {}
10+
11+
extension P2 {
12+
@differentiable(wrt: (self, input))
13+
// expected-note @+1 {{instance method 'callAsFunction' must have explicit '@differentiable(wrt: self)' attribute to satisfy requirement instance method 'callAsFunction' (in protocol 'P1') because it is declared in a different file than the conformance of 'ConformingStruct' to 'P1'}}
14+
public func callAsFunction(_ input: Float) -> Float {
15+
return input
16+
}
17+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Test missing protocol requirement `@differentiable` attribute errors for
2+
// non-public protocol witnesses, when the protocol conformance is declared in a
3+
// separate file from witnesses.
4+
//
5+
// Implicit `@differentiable` attributes cannot be generated for protocol
6+
// witnesses when the conformance is declared from a separate file from the
7+
// witness. Otherwise, compilation of the file containing the conformance
8+
// creates references to external symbols for implicit `@differentiable`
9+
// attributes, even though no such symbols exist.
10+
//
11+
// Context: https://github.com/apple/swift/pull/29771#issuecomment-585059721
12+
13+
// Note: `swiftc main.swift other_file.swift` runs three commands:
14+
// - `swiftc -frontend -primary-file main.swift other_file.swift -o ...`
15+
// - `swiftc -frontend main.swift -primary-file other_file.swift -o ...`
16+
// - `/usr/bin/ld ...`
17+
//
18+
// `%target-build-swift` performs `swiftc main.swift other_file.swift`, so it is expected to fail (hence `not`).
19+
// `swiftc -frontend -primary-file main.swift other_file.swift` should fail, so `-verify` is needed.
20+
// `swiftc -frontend main.swift -primary-file other_file.swift` should succeed, so no need for `-verify`.
21+
22+
// RUN: %target-swift-frontend -c -verify -primary-file %s %S/Inputs/other_file.swift
23+
// RUN: %target-swift-frontend -c %s -primary-file %S/Inputs/other_file.swift
24+
// RUN: not %target-build-swift %s %S/Inputs/other_file.swift
25+
26+
// Error: conformance is in different file than witnesses.
27+
// expected-error @+1 {{type 'ConformingStruct' does not conform to protocol 'Protocol1'}}
28+
extension ConformingStruct: Protocol1 {}
29+
30+
// No error: conformance is in same file as witnesses.
31+
extension ConformingStruct: Protocol2 {
32+
func internalMethod4(_ x: Float) -> Float {
33+
x
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-swift-frontend -c -verify -primary-file %s %S/Inputs/other_file_protocol_default_implementation_witness.swift
2+
3+
// SR-13455: Test missing protocol requirement `@differentiable` attribute
4+
// errors for protocol witnesses declared in a different file than the protocol
5+
// conformance.
6+
//
7+
// This test case specifically tests protocol extension method witnesses.
8+
9+
import _Differentiation
10+
11+
// expected-error @+1 {{type 'ConformingStruct' does not conform to protocol 'P1'}}
12+
struct ConformingStruct: P2 {}

0 commit comments

Comments
 (0)