Skip to content
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
39 changes: 31 additions & 8 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,30 +712,53 @@ void ConstraintSystem::recordOpenedTypes(

/// Determine how many levels of argument labels should be removed from the
/// function type when referencing the given declaration.
static unsigned getNumRemovedArgumentLabels(ASTContext &ctx, ValueDecl *decl,
static unsigned getNumRemovedArgumentLabels(TypeChecker &TC, ValueDecl *decl,
bool isCurriedInstanceReference,
FunctionRefKind functionRefKind) {
unsigned numParameterLists = 0;

// Enum element with associated value has to be treated
// as regular function value and all of the labels have to be
// stripped from its parameters.
//
// enum E {
// case foo(a: Int)
// }
// let bar: [Int] = []
// bar.map(E.foo)
//
// `E.foo` has to act as a regular function type passed as a value.
if (!TC.getLangOpts().isSwiftVersion3()) {
if (auto *EED = dyn_cast<EnumElementDecl>(decl)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safer to have this under the version check, but is it actually necessary?

What I'm wondering is if there might be cases where the Swift 3 behavior is also broken.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Swift 3 behavior is broken if your enum argument type has multiple elements.

numParameterLists = EED->hasAssociatedValues() ? 2 : 1;
}
}

// Only applicable to functions. Nothing else should have argument labels in
// the type.
auto func = dyn_cast<AbstractFunctionDecl>(decl);
if (!func) return 0;
if (auto func = dyn_cast<AbstractFunctionDecl>(decl))
numParameterLists = func->getNumParameterLists();

if (numParameterLists == 0)
return 0;

switch (functionRefKind) {
case FunctionRefKind::Unapplied:
case FunctionRefKind::Compound:
// Always remove argument labels from unapplied references and references
// that use a compound name.
return func->getNumParameterLists();
return numParameterLists;

case FunctionRefKind::SingleApply:
// If we have fewer than two parameter lists, leave the labels.
if (func->getNumParameterLists() < 2) return 0;
if (numParameterLists < 2)
return 0;

// If this is a curried reference to an instance method, where 'self' is
// being applied, e.g., "ClassName.instanceMethod(self)", remove the
// argument labels from the resulting function type. The 'self' parameter is
// always unlabeled, so this operation is a no-op for the actual application.
return isCurriedInstanceReference ? func->getNumParameterLists() : 1;
return isCurriedInstanceReference ? numParameterLists : 1;

case FunctionRefKind::DoubleApply:
// Never remove argument labels from a double application.
Expand Down Expand Up @@ -796,7 +819,7 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
auto openedType =
openFunctionType(
funcType,
getNumRemovedArgumentLabels(TC.Context, funcDecl,
getNumRemovedArgumentLabels(TC, funcDecl,
/*isCurriedInstanceReference=*/false,
functionRefKind),
locator, replacements,
Expand Down Expand Up @@ -1113,7 +1136,7 @@ ConstraintSystem::getTypeOfMemberReference(
auto &replacements = replacementsPtr ? *replacementsPtr : localReplacements;
bool isCurriedInstanceReference = value->isInstanceMember() && !isInstance;
unsigned numRemovedArgumentLabels =
getNumRemovedArgumentLabels(TC.Context, value, isCurriedInstanceReference,
getNumRemovedArgumentLabels(TC, value, isCurriedInstanceReference,
functionRefKind);

AnyFunctionType *funcType;
Expand Down
91 changes: 91 additions & 0 deletions test/Compatibility/enum_cases.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// RUN: %target-typecheck-verify-swift -swift-version 3

// Tests for enum case behavior in Swift 3

enum E {
case foo(bar: String)
case bar(_: String)
case two(x: Int, y: Int)
case tuple((x: Int, y: Int))
}

enum G_E<T> {
case foo(bar: T)
case bar(_: T)
case two(x: T, y: T)
case tuple((x: T, y: T))
}

let arr: [String] = []
let _ = arr.map(E.foo) // Ok
let _ = arr.map(E.bar) // Ok
let _ = arr.map(E.two) // expected-error {{cannot convert value of type '(Int, Int) -> E' to expected argument type '(String) -> _'}}
let _ = arr.map(E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> E' to expected argument type '(String) -> _'}}

let _ = arr.map(G_E<String>.foo) // Ok
let _ = arr.map(G_E<String>.bar) // Ok
let _ = arr.map(G_E<String>.two) // expected-error {{cannot convert value of type '(String, String) -> G_E<String>' to expected argument type '(String) -> _'}}
let _ = arr.map(G_E<Int>.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E<Int>' to expected argument type '(String) -> _'}}

let _ = E.foo("hello") // expected-error {{missing argument label 'bar:' in call}}
let _ = E.bar("hello") // Ok

let _ = G_E<String>.foo("hello") // expected-error {{missing argument label 'bar:' in call}}
let _ = G_E<String>.bar("hello") // Ok

// Passing enum case as an argument to generic function

func bar_1<T>(_: (T) -> E) {}
func bar_2<T>(_: (T) -> G_E<T>) {}
func bar_3<T, U>(_: (T) -> G_E<U>) {}

bar_1(E.foo) // Ok
bar_1(E.bar) // Ok
bar_1(E.two) // Ok in Swift 3
bar_1(E.tuple) // Ok - it's going to be ((x: Int, y: Int))

bar_2(G_E<String>.foo) // Ok
bar_2(G_E<Int>.bar) // Ok
bar_2(G_E<Int>.two) // expected-error {{cannot convert value of type '(Int, Int) -> G_E<Int>' to expected argument type '(_) -> G_E<_>'}}
bar_2(G_E<Int>.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E<Int>' to expected argument type '(_) -> G_E<_>'}}
bar_3(G_E<Int>.tuple) // Ok

// Regular enum case assigned as a value

let foo: (String) -> E = E.foo // Ok
let _ = foo("hello") // Ok

let bar: (String) -> E = E.bar // Ok
let _ = bar("hello") // Ok

let two: (Int, Int) -> E = E.two // Ok
let _ = two(0, 42) // Ok

let tuple: ((x: Int, y: Int)) -> E = E.tuple // Ok
let _ = tuple((x: 0, y: 42)) // Ok

// Generic enum case assigned as a value

let g_foo: (String) -> G_E<String> = G_E<String>.foo // Ok
let _ = g_foo("hello") // Ok

let g_bar: (String) -> G_E<String> = G_E<String>.bar // Ok
let _ = g_bar("hello") // Ok

let g_two: (Int, Int) -> G_E<Int> = G_E<Int>.two // Ok
let _ = g_two(0, 42) // Ok

let g_tuple: ((x: Int, y: Int)) -> G_E<Int> = G_E<Int>.tuple // Ok
let _ = g_tuple((x: 0, y: 42)) // Ok

// Following example doesn't work in Swift 3 mode because Foo.a and Foo.b have different argument types
// since the labels are not striped from them.

enum Foo {
case a(x: Int)
case b(y: Int)
}

func foo<T>(_: T, _: T) {}
foo(Foo.a, Foo.b) // expected-error {{cannot invoke 'foo' with an argument list of type '((Int) -> Foo, (Int) -> Foo)}}
// expected-note@-1 {{expected an argument list of type '(T, T)'}}
88 changes: 88 additions & 0 deletions test/Constraints/enum_cases.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// RUN: %target-typecheck-verify-swift -swift-version 4

// See test/Compatibility/enum_cases.swift for Swift 3 behavior

enum E {
case foo(bar: String)
case bar(_: String)
case two(x: Int, y: Int)
case tuple((x: Int, y: Int))
}

enum G_E<T> {
case foo(bar: T)
case bar(_: T)
case two(x: T, y: T)
case tuple((x: T, y: T))
}

let arr: [String] = []
let _ = arr.map(E.foo) // Ok
let _ = arr.map(E.bar) // Ok
let _ = arr.map(E.two) // expected-error {{cannot convert value of type '(Int, Int) -> E' to expected argument type '(String) -> _'}}
let _ = arr.map(E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> E' to expected argument type '(String) -> _'}}

let _ = arr.map(G_E<String>.foo) // Ok
let _ = arr.map(G_E<String>.bar) // Ok
let _ = arr.map(G_E<String>.two) // expected-error {{cannot convert value of type '(String, String) -> G_E<String>' to expected argument type '(String) -> _'}}
let _ = arr.map(G_E<Int>.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E<Int>' to expected argument type '(String) -> _'}}

let _ = E.foo("hello") // expected-error {{missing argument label 'bar:' in call}}
let _ = E.bar("hello") // Ok

let _ = G_E<String>.foo("hello") // expected-error {{missing argument label 'bar:' in call}}
let _ = G_E<String>.bar("hello") // Ok

// Passing enum case as an argument to generic function

func bar_1<T>(_: (T) -> E) {}
func bar_2<T>(_: (T) -> G_E<T>) {}
func bar_3<T, U>(_: (T) -> G_E<U>) {}

bar_1(E.foo) // Ok
bar_1(E.bar) // Ok
bar_1(E.two) // expected-error {{cannot convert value of type '(Int, Int) -> E' to expected argument type '(_) -> E'}}
bar_1(E.tuple) // Ok - it's going to be ((x: Int, y: Int))

bar_2(G_E<String>.foo) // Ok
bar_2(G_E<Int>.bar) // Ok
bar_2(G_E<Int>.two) // expected-error {{cannot convert value of type '(Int, Int) -> G_E<Int>' to expected argument type '(_) -> G_E<_>'}}
bar_2(G_E<Int>.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E<Int>' to expected argument type '(_) -> G_E<_>'}}
bar_3(G_E<Int>.tuple) // Ok

// Regular enum case assigned as a value

let foo: (String) -> E = E.foo // Ok
let _ = foo("hello") // Ok

let bar: (String) -> E = E.bar // Ok
let _ = bar("hello") // Ok

let two: (Int, Int) -> E = E.two // Ok
let _ = two(0, 42) // Ok

let tuple: ((x: Int, y: Int)) -> E = E.tuple // Ok
let _ = tuple((x: 0, y: 42)) // Ok

// Generic enum case assigned as a value

let g_foo: (String) -> G_E<String> = G_E<String>.foo // Ok
let _ = g_foo("hello") // Ok

let g_bar: (String) -> G_E<String> = G_E<String>.bar // Ok
let _ = g_bar("hello") // Ok

let g_two: (Int, Int) -> G_E<Int> = G_E<Int>.two // Ok
let _ = g_two(0, 42) // Ok

let g_tuple: ((x: Int, y: Int)) -> G_E<Int> = G_E<Int>.tuple // Ok
let _ = g_tuple((x: 0, y: 42)) // Ok


enum Foo {
case a(x: Int)
case b(y: Int)
}

func foo<T>(_: T, _: T) {}
foo(Foo.a, Foo.b) // Ok in Swift 4 because we strip labels from the arguments