Skip to content

Commit 8ea5759

Browse files
authored
Merge pull request #24155 from nathawes/module-interface-inherit-default-args-via-super
2 parents 8607ebb + 7df3e8a commit 8ea5759

File tree

11 files changed

+308
-11
lines changed

11 files changed

+308
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,19 @@ WARNING(expr_dynamic_lookup_swift3_objc_inference,none,
12881288
"deprecated in Swift 4",
12891289
(DescriptiveDeclKind, DeclName, Identifier))
12901290

1291+
ERROR(inherited_default_value_not_in_designated_constructor,none,
1292+
"default value inheritance via 'super' is only valid on the parameters of "
1293+
"designated initializers", ())
1294+
ERROR(inherited_default_value_used_in_non_overriding_constructor,none,
1295+
"default value inheritance via 'super' can only be used when "
1296+
"overriding a designated initializer", ())
1297+
ERROR(corresponding_param_not_defaulted,none,
1298+
"default value inheritance via 'super' requires that the corresponding "
1299+
"parameter of the overridden designated initializer has a default value",
1300+
())
1301+
NOTE(inherited_default_param_here,none,
1302+
"corresponding parameter declared here", ())
1303+
12911304
// Alignment attribute
12921305
ERROR(alignment_not_power_of_two,none,
12931306
"alignment value must be a power of two", ())

include/swift/Parse/Parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,9 @@ class Parser {
11441144

11451145
/// The default argument for this parameter.
11461146
Expr *DefaultArg = nullptr;
1147+
1148+
/// True if this parameter inherits a default argument via '= super'
1149+
bool hasInheritedDefaultArg = false;
11471150

11481151
/// True if we emitted a parse error about this parameter.
11491152
bool isInvalid = false;

lib/AST/Decl.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5361,10 +5361,7 @@ ParamDecl::getDefaultValueStringRepresentation(
53615361
var->getParentInitializer(),
53625362
scratch);
53635363
}
5364-
case DefaultArgumentKind::Inherited:
5365-
// FIXME: This needs /some/ kind of textual representation, but this isn't
5366-
// a great one.
5367-
return "super";
5364+
case DefaultArgumentKind::Inherited: return "super";
53685365
case DefaultArgumentKind::File: return "#file";
53695366
case DefaultArgumentKind::Line: return "#line";
53705367
case DefaultArgumentKind::Column: return "#column";

lib/Parse/ParsePattern.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,30 @@ void Parser::DefaultArgumentInfo::setFunctionContext(
6666

6767
static ParserStatus parseDefaultArgument(
6868
Parser &P, Parser::DefaultArgumentInfo *defaultArgs, unsigned argIndex,
69-
Expr *&init, Parser::ParameterContextKind paramContext) {
69+
Expr *&init, bool &hasInheritedDefaultArg,
70+
Parser::ParameterContextKind paramContext) {
7071
SyntaxParsingContext DefaultArgContext(P.SyntaxContext,
7172
SyntaxKind::InitializerClause);
7273
SourceLoc equalLoc = P.consumeToken(tok::equal);
7374

75+
if (P.SF.Kind == SourceFileKind::Interface) {
76+
// Swift module interfaces don't synthesize inherited intializers and
77+
// instead include them explicitly in subclasses. Since the
78+
// \c DefaultArgumentKind of these initializers is \c Inherited, this is
79+
// represented textually as `= super` in the interface.
80+
81+
// If we're in a module interface and the default argument is exactly
82+
// `super` (i.e. the token after that is `,` or `)` which end a parameter)
83+
// report an inherited default argument to the caller and return.
84+
if (P.Tok.is(tok::kw_super) && P.peekToken().isAny(tok::comma, tok::r_paren)) {
85+
hasInheritedDefaultArg = true;
86+
P.consumeToken(tok::kw_super);
87+
P.SyntaxContext->createNodeInPlace(SyntaxKind::SuperRefExpr);
88+
defaultArgs->HasDefaultArgument = true;
89+
return ParserStatus();
90+
}
91+
}
92+
7493
// Enter a fresh default-argument context with a meaningless parent.
7594
// We'll change the parent to the function later after we've created
7695
// that declaration.
@@ -367,7 +386,9 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc,
367386
if (Tok.is(tok::equal)) {
368387
SourceLoc EqualLoc = Tok.getLoc();
369388
status |= parseDefaultArgument(*this, defaultArgs, defaultArgIndex,
370-
param.DefaultArg, paramContext);
389+
param.DefaultArg,
390+
param.hasInheritedDefaultArg,
391+
paramContext);
371392

372393
if (param.EllipsisLoc.isValid() && param.DefaultArg) {
373394
// The range of the complete default argument.
@@ -605,15 +626,20 @@ mapParsedParameters(Parser &parser,
605626
}
606627
}
607628

629+
assert (((!param.DefaultArg &&
630+
!param.hasInheritedDefaultArg) ||
631+
paramContext == Parser::ParameterContextKind::Function ||
632+
paramContext == Parser::ParameterContextKind::Operator ||
633+
paramContext == Parser::ParameterContextKind::Initializer ||
634+
paramContext == Parser::ParameterContextKind::EnumElement) &&
635+
"Default arguments are only permitted on the first param clause");
636+
608637
if (param.DefaultArg) {
609-
assert((paramContext == Parser::ParameterContextKind::Function ||
610-
paramContext == Parser::ParameterContextKind::Operator ||
611-
paramContext == Parser::ParameterContextKind::Initializer ||
612-
paramContext == Parser::ParameterContextKind::EnumElement) &&
613-
"Default arguments are only permitted on the first param clause");
614638
DefaultArgumentKind kind = getDefaultArgKind(param.DefaultArg);
615639
result->setDefaultArgumentKind(kind);
616640
result->setDefaultValue(param.DefaultArg);
641+
} else if (param.hasInheritedDefaultArg) {
642+
result->setDefaultArgumentKind(DefaultArgumentKind::Inherited);
617643
}
618644

619645
elements.push_back(result);

lib/Sema/TypeCheckStmt.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,53 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
17311731
return BS;
17321732
}
17331733

1734+
static Optional<unsigned>
1735+
getParamIndex(const ParameterList *paramList, const ParamDecl *decl) {
1736+
ArrayRef<ParamDecl *> params = paramList->getArray();
1737+
for (unsigned i = 0; i < params.size(); ++i) {
1738+
if (params[i] == decl) return i;
1739+
}
1740+
return None;
1741+
}
1742+
1743+
static void
1744+
checkInheritedDefaultValueRestrictions(TypeChecker &TC, ParamDecl *PD) {
1745+
if (PD->getDefaultArgumentKind() != DefaultArgumentKind::Inherited)
1746+
return;
1747+
1748+
auto *DC = PD->getInnermostDeclContext();
1749+
const SourceFile *SF = DC->getParentSourceFile();
1750+
assert((SF && SF->Kind == SourceFileKind::Interface || PD->isImplicit()) &&
1751+
"explicit inherited default argument outside of a module interface?");
1752+
1753+
// The containing decl should be a designated initializer.
1754+
auto ctor = dyn_cast<ConstructorDecl>(DC);
1755+
if (!ctor || ctor->isConvenienceInit()) {
1756+
TC.diagnose(
1757+
PD, diag::inherited_default_value_not_in_designated_constructor);
1758+
return;
1759+
}
1760+
1761+
// The decl it overrides should also be a designated initializer.
1762+
auto overridden = ctor->getOverriddenDecl();
1763+
if (!overridden || overridden->isConvenienceInit()) {
1764+
TC.diagnose(
1765+
PD, diag::inherited_default_value_used_in_non_overriding_constructor);
1766+
if (overridden)
1767+
TC.diagnose(overridden, diag::overridden_here);
1768+
return;
1769+
}
1770+
1771+
// The corresponding parameter should have a default value.
1772+
Optional<unsigned> idx = getParamIndex(ctor->getParameters(), PD);
1773+
assert(idx && "containing decl does not contain param?");
1774+
ParamDecl *equivalentParam = overridden->getParameters()->get(*idx);
1775+
if (equivalentParam->getDefaultArgumentKind() == DefaultArgumentKind::None) {
1776+
TC.diagnose(PD, diag::corresponding_param_not_defaulted);
1777+
TC.diagnose(equivalentParam, diag::inherited_default_param_here);
1778+
}
1779+
}
1780+
17341781
/// Check the default arguments that occur within this pattern.
17351782
void TypeChecker::checkDefaultArguments(ParameterList *params,
17361783
ValueDecl *VD) {
@@ -1756,6 +1803,7 @@ void TypeChecker::checkDefaultArguments(ParameterList *params,
17561803
}
17571804

17581805
for (auto *param : *params) {
1806+
checkInheritedDefaultValueRestrictions(*this, param);
17591807
if (!param->getDefaultValue() ||
17601808
!param->hasInterfaceType() ||
17611809
param->getInterfaceType()->hasError())
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t/Test~partial.swiftmodule -module-name Test -primary-file %s
3+
// RUN: %target-swift-frontend -merge-modules -emit-module -o %t/Test.swiftmodule %t/Test~partial.swiftmodule
4+
// RUN: %target-swift-ide-test -print-module -module-to-print=Test -source-filename=x -I %t | %FileCheck %s
5+
6+
// RUN: %target-swift-frontend -typecheck -emit-parseable-module-interface-path %t/Test.swiftinterface -enable-library-evolution %s
7+
// RUN: rm %t/Test.swiftmodule
8+
// RUN: echo "import Test" > %t/test-client.swift
9+
// RUN: %target-swift-frontend -typecheck -I%t %t/test-client.swift
10+
// RUN: %FileCheck %s < %t/Test.swiftinterface
11+
12+
// CHECK: class Base {
13+
public class Base {
14+
// CHECK: init(x: Int = 3)
15+
public init(x: Int = 3) {}
16+
public convenience init(convInit: Int) {
17+
self.init(x: convInit)
18+
}
19+
// CHECK: foo(y: Int = 42)
20+
public func foo(y: Int = 42) {}
21+
}
22+
23+
// CHECK: class Derived : Base {
24+
public class Derived: Base {
25+
// CHECK: init(y: Int)
26+
public convenience init(y: Int) {
27+
self.init()
28+
}
29+
30+
// CHECK-NOT: init(convInit: Int = super)
31+
// CHECK: override {{(public )?}}init(x: Int = super)
32+
// CHECK-NOT: init(convInit: Int = super)
33+
}
34+
35+
public enum Enum {
36+
// CHECK: case pie(filling: String = "apple")
37+
case pie(filling: String = "apple")
38+
}
39+
40+
// CHECK: func hasClosureDefaultArg(_ x: () -> Void = {
41+
// CHECK-NEXT: })
42+
public func hasClosureDefaultArg(_ x: () -> Void = {
43+
}) {
44+
}
45+
46+
// CHECK: func hasMagicDefaultArgs(_ f: String = #file, _ fu: String = #function, _ l: Int = #line)
47+
public func hasMagicDefaultArgs(_ f: String = #file, _ fu: String = #function, _ l: Int = #line) {}
48+
49+
// CHECK: func hasSimpleDefaultArgs(_ x: Int = 0, b: Int = 1)
50+
public func hasSimpleDefaultArgs(_ x: Int = 0, b: Int = 1) {
51+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name InheritedDefaults
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-typecheck-verify-swift
6+
7+
import Swift
8+
9+
public class Bar {
10+
// note associated with the expected error in (F) below
11+
public init(x: Int = 24, y: Int, z: Int = 42) // expected-note {{corresponding parameter declared here}}
12+
13+
public init(a: Int, b: Int = 99)
14+
public convenience init(convInit: Int = 45) {}
15+
16+
// note associated with the expected error in (D) below
17+
public convenience init(first: Int, second: Int = 88, third: Int, fourth: Int) // expected-note {{overridden declaration is here}}
18+
}
19+
20+
public class Foo: Bar {
21+
22+
// A) designated overriding designated - valid
23+
public override init(x: Int = super, y: Int, z: Int = super)
24+
25+
// B) convenience shadowing convenience
26+
public convenience init(convInit: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
27+
28+
// C) convenience overriding designated
29+
public override convenience init(a: Int, b: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
30+
31+
// D) designated shadowing convenience
32+
public init(first: Int, second: Int = super, third: Int, fourth: Int) // expected-error {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
33+
34+
// E) not in initializer
35+
public func foo(z: Int = super) // expected-error {{default value inheritance via 'super' is only valid on the parameters of designated initializers}}
36+
}
37+
38+
public class Baz: Bar {
39+
40+
// F) Matching param not defaulted
41+
public override init(x: Int = super, y: Int = super, z: Int = super) // expected-error {{default value inheritance via 'super' requires that the corresponding parameter of the overridden designated initializer has a default value}}
42+
}
43+
44+
public class Direct: Bar {
45+
public override init(x: Int = super, y: Int, z: Int = super)
46+
47+
// G) Doesn't override anything
48+
public override init(other: Int = super, value: Int) // expected-error {{argument labels for initializer 'init(other:value:)' do not match those of overridden initializer 'init(a:b:)'}}
49+
// expected-error@-1 {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
50+
}
51+
52+
public class Indirect: Direct {
53+
54+
// H) Chain of inherited defaults - valid all the way down
55+
public override init(x: Int = super, y: Int, z: Int = super)
56+
57+
// I) Chain of inherited defaults - invalid further down (and diagnosed there)
58+
public override init(other: Int = super, value: Int)
59+
}
60+
61+
public enum Bob {
62+
case bar(p: Int)
63+
public init(foo: String = super /*comment*/) // expected-error {{default value inheritance via 'super' can only be used when overriding a designated initializer}}
64+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// REQUIRES: executable_test
2+
// RUN: %empty-directory(%t)
3+
4+
// 1) Build the 'Inherited' library and its interface from this file
5+
//
6+
// RUN: %target-build-swift-dylib(%t/%target-library-name(Inherited)) -emit-module-path %t/Inherited.swiftmodule -emit-parseable-module-interface-path %t/Inherited.swiftinterface -module-name Inherited %s
7+
// RUN: rm %t/Inherited.swiftmodule
8+
9+
// 2) Check the interface includes the synthesized initializers of the base
10+
// class in the derived class explicitly and uses the '= super' syntax to
11+
// inherit its default arguments.
12+
//
13+
// RUN: cat %t/Inherited.swiftinterface | %FileCheck --check-prefix=INTERFACE %s
14+
//
15+
// INTERFACE: public class Base {
16+
// INTERFACE: public init(x: Swift.Int = 45, y: Swift.Int = 98)
17+
// INTERFACE: }
18+
// INTERFACE: public class Derived : Inherited.Base {
19+
// INTERFACE: override public init(x: Swift.Int = super, y: Swift.Int = super)
20+
// INTERFACE: }
21+
22+
// 4) Generate a main.swift file that uses the 'Inherited' library and makes use
23+
// of the inherited default arguments
24+
//
25+
// RUN: echo "import Inherited" > %t/main.swift
26+
// RUN: echo "print(Derived().x)" >> %t/main.swift
27+
// RUN: echo "print(Derived().y)" >> %t/main.swift
28+
29+
// 5) Build and run the executable, checking the defaulted arguments resulted in
30+
// the correct values being stored
31+
//
32+
// RUN: %target-build-swift -I%t -L%t -lInherited -o %t/main %target-rpath(%t) %t/main.swift -swift-version 5
33+
// RUN: %target-codesign %t/main %t/%target-library-name(Inherited)
34+
// RUN: %target-run %t/main %t/%target-library-name(Inherited) | %FileCheck --check-prefix=OUTPUT %s
35+
//
36+
// OUTPUT: 45
37+
// OUTPUT-NEXT: 98
38+
39+
public class Base {
40+
public let x: Int
41+
public let y: Int
42+
public init(x: Int = 45, y: Int = 98) {
43+
self.x = x
44+
self.y = y
45+
}
46+
}
47+
public class Derived: Base {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<ImportDecl>// RUN: rm -rf %t
2+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen > %t
3+
// RUN: diff -u %s %t
4+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen -print-node-kind > %t.withkinds
5+
// RUN: diff -u %S/Outputs/round_trip_module_interface.swiftinterface.withkinds %t.withkinds
6+
// RUN: %swift-syntax-test -input-source-filename %s -eof > %t
7+
// RUN: diff -u %s %t
8+
// RUN: %swift-syntax-test -serialize-raw-tree -input-source-filename %s > %t.dump
9+
// RUN: %swift-syntax-test -deserialize-raw-tree -input-source-filename %t.dump -output-filename %t
10+
// RUN: diff -u %s %t
11+
12+
import <AccessPathComponent>Swift</AccessPathComponent></ImportDecl><ClassDecl><DeclModifier>
13+
14+
public </DeclModifier>class Bar <MemberDeclBlock>{<MemberDeclListItem><InitializerDecl><DeclModifier>
15+
public </DeclModifier>init<ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <IntegerLiteralExpr>24</IntegerLiteralExpr></InitializerClause>, </FunctionParameter><FunctionParameter>y: <SimpleTypeIdentifier>Int</SimpleTypeIdentifier></FunctionParameter>)</ParameterClause></InitializerDecl></MemberDeclListItem>
16+
}</MemberDeclBlock></ClassDecl><ClassDecl><DeclModifier>
17+
18+
public </DeclModifier>class Foo<TypeInheritanceClause>: <InheritedType><SimpleTypeIdentifier>Bar </SimpleTypeIdentifier></InheritedType></TypeInheritanceClause><MemberDeclBlock>{<MemberDeclListItem><InitializerDecl><DeclModifier>
19+
public </DeclModifier><DeclModifier>override </DeclModifier>init<ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SuperRefExpr>super</SuperRefExpr></InitializerClause>, </FunctionParameter><FunctionParameter>y: <SimpleTypeIdentifier>Int</SimpleTypeIdentifier></FunctionParameter>)</ParameterClause></InitializerDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
20+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>x: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SuperRefExpr>super</SuperRefExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
21+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>y: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <MemberAccessExpr><SuperRefExpr>super</SuperRefExpr>.init</MemberAccessExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem><MemberDeclListItem><FunctionDecl><DeclModifier>
22+
public </DeclModifier>func foo<FunctionSignature><ParameterClause>(<FunctionParameter>z: <SimpleTypeIdentifier>Int </SimpleTypeIdentifier><InitializerClause>= <SubscriptExpr><SuperRefExpr>super</SuperRefExpr>[<FunctionCallArgument><IntegerLiteralExpr>1</IntegerLiteralExpr></FunctionCallArgument>]</SubscriptExpr></InitializerClause></FunctionParameter>)</ParameterClause></FunctionSignature></FunctionDecl></MemberDeclListItem>
23+
}</MemberDeclBlock></ClassDecl>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: rm -rf %t
2+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen > %t
3+
// RUN: diff -u %s %t
4+
// RUN: %swift-syntax-test -input-source-filename %s -parse-gen -print-node-kind > %t.withkinds
5+
// RUN: diff -u %S/Outputs/round_trip_module_interface.swiftinterface.withkinds %t.withkinds
6+
// RUN: %swift-syntax-test -input-source-filename %s -eof > %t
7+
// RUN: diff -u %s %t
8+
// RUN: %swift-syntax-test -serialize-raw-tree -input-source-filename %s > %t.dump
9+
// RUN: %swift-syntax-test -deserialize-raw-tree -input-source-filename %t.dump -output-filename %t
10+
// RUN: diff -u %s %t
11+
12+
import Swift
13+
14+
public class Bar {
15+
public init(x: Int = 24, y: Int)
16+
}
17+
18+
public class Foo: Bar {
19+
public override init(x: Int = super, y: Int)
20+
public func foo(x: Int = super)
21+
public func foo(y: Int = super.init)
22+
public func foo(z: Int = super[1])
23+
}

tools/swift-syntax-test/swift-syntax-test.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ int parseFile(
600600
Invocation.getLangOptions().VerifySyntaxTree = options::VerifySyntaxTree;
601601
Invocation.getLangOptions().RequestEvaluatorGraphVizPath = options::GraphVisPath;
602602
Invocation.getFrontendOptions().InputsAndOutputs.addInputFile(InputFileName);
603+
if (InputFileName.endswith(".swiftinterface"))
604+
Invocation.setInputKind(InputFileKind::SwiftModuleInterface);
603605
Invocation.setMainExecutablePath(
604606
llvm::sys::fs::getMainExecutable(MainExecutablePath,
605607
reinterpret_cast<void *>(&anchorForGetMainExecutable)));

0 commit comments

Comments
 (0)