From 5523ca7b9ff87b5ad308e4508dc72b9fc137919d Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 26 May 2017 00:53:28 -0700 Subject: [PATCH] [ConstraintSystem] Strip labels from enum elements when passed as values Enum elements have to be treated the same way as regular functions when passed as values, which means labels have to be stripped from their argument types. Resolves: rdar://problem/32300339. --- lib/Sema/ConstraintSystem.cpp | 39 ++++++++++--- test/Compatibility/enum_cases.swift | 91 +++++++++++++++++++++++++++++ test/Constraints/enum_cases.swift | 88 ++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 test/Compatibility/enum_cases.swift create mode 100644 test/Constraints/enum_cases.swift diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 4a07f2edb3858..f503e1a2b5330 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -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(decl)) { + numParameterLists = EED->hasAssociatedValues() ? 2 : 1; + } + } + // Only applicable to functions. Nothing else should have argument labels in // the type. - auto func = dyn_cast(decl); - if (!func) return 0; + if (auto func = dyn_cast(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. @@ -796,7 +819,7 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value, auto openedType = openFunctionType( funcType, - getNumRemovedArgumentLabels(TC.Context, funcDecl, + getNumRemovedArgumentLabels(TC, funcDecl, /*isCurriedInstanceReference=*/false, functionRefKind), locator, replacements, @@ -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; diff --git a/test/Compatibility/enum_cases.swift b/test/Compatibility/enum_cases.swift new file mode 100644 index 0000000000000..25cbfaf321a03 --- /dev/null +++ b/test/Compatibility/enum_cases.swift @@ -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 { + 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.foo) // Ok +let _ = arr.map(G_E.bar) // Ok +let _ = arr.map(G_E.two) // expected-error {{cannot convert value of type '(String, String) -> G_E' to expected argument type '(String) -> _'}} +let _ = arr.map(G_E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E' 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.foo("hello") // expected-error {{missing argument label 'bar:' in call}} +let _ = G_E.bar("hello") // Ok + +// Passing enum case as an argument to generic function + +func bar_1(_: (T) -> E) {} +func bar_2(_: (T) -> G_E) {} +func bar_3(_: (T) -> G_E) {} + +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.foo) // Ok +bar_2(G_E.bar) // Ok +bar_2(G_E.two) // expected-error {{cannot convert value of type '(Int, Int) -> G_E' to expected argument type '(_) -> G_E<_>'}} +bar_2(G_E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E' to expected argument type '(_) -> G_E<_>'}} +bar_3(G_E.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 = G_E.foo // Ok +let _ = g_foo("hello") // Ok + +let g_bar: (String) -> G_E = G_E.bar // Ok +let _ = g_bar("hello") // Ok + +let g_two: (Int, Int) -> G_E = G_E.two // Ok +let _ = g_two(0, 42) // Ok + +let g_tuple: ((x: Int, y: Int)) -> G_E = G_E.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) {} +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)'}} diff --git a/test/Constraints/enum_cases.swift b/test/Constraints/enum_cases.swift new file mode 100644 index 0000000000000..6ba2924e4a72a --- /dev/null +++ b/test/Constraints/enum_cases.swift @@ -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 { + 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.foo) // Ok +let _ = arr.map(G_E.bar) // Ok +let _ = arr.map(G_E.two) // expected-error {{cannot convert value of type '(String, String) -> G_E' to expected argument type '(String) -> _'}} +let _ = arr.map(G_E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E' 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.foo("hello") // expected-error {{missing argument label 'bar:' in call}} +let _ = G_E.bar("hello") // Ok + +// Passing enum case as an argument to generic function + +func bar_1(_: (T) -> E) {} +func bar_2(_: (T) -> G_E) {} +func bar_3(_: (T) -> G_E) {} + +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.foo) // Ok +bar_2(G_E.bar) // Ok +bar_2(G_E.two) // expected-error {{cannot convert value of type '(Int, Int) -> G_E' to expected argument type '(_) -> G_E<_>'}} +bar_2(G_E.tuple) // expected-error {{cannot convert value of type '((x: Int, y: Int)) -> G_E' to expected argument type '(_) -> G_E<_>'}} +bar_3(G_E.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 = G_E.foo // Ok +let _ = g_foo("hello") // Ok + +let g_bar: (String) -> G_E = G_E.bar // Ok +let _ = g_bar("hello") // Ok + +let g_two: (Int, Int) -> G_E = G_E.two // Ok +let _ = g_two(0, 42) // Ok + +let g_tuple: ((x: Int, y: Int)) -> G_E = G_E.tuple // Ok +let _ = g_tuple((x: 0, y: 42)) // Ok + + +enum Foo { + case a(x: Int) + case b(y: Int) +} + +func foo(_: T, _: T) {} +foo(Foo.a, Foo.b) // Ok in Swift 4 because we strip labels from the arguments