From 267eedff4dbada66a784e12c578791f84c3cff65 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 22 Aug 2024 20:43:58 +0200 Subject: [PATCH 1/4] Fix the checking of synthesizable conformances under a where clause --- .../FrontEnd/TypeChecking/TypeChecker.swift | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 71454aa5d..ffe653813 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -421,49 +421,51 @@ struct TypeChecker { } } - /// Returns `true` if a conformance of `model` to `trait` is synthesizable in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. /// /// A conformance *M: T* is synthesizable iff *M* structurally conforms to *T*. private mutating func canSynthesizeConformance( - _ model: AnyType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: AnyType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { switch model.base { case let m as BoundGenericType: - return canSynthesizeConformance(m, to: trait, in: scopeOfUse) + return canSynthesizeConformance(m, to: trait, in: scopeOfImplementation) case let m as ProductType: - return canSynthesizeConformance(m, to: trait, in: scopeOfUse) + return canSynthesizeConformance(m, to: trait, in: scopeOfImplementation) default: - return structurallyConforms(model, to: trait, in: scopeOfUse) + return structurallyConforms(model, to: trait, in: scopeOfImplementation) } } - /// Returns `true` if `model` structurally conforms to `trait` in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. private mutating func canSynthesizeConformance( - _ model: BoundGenericType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: BoundGenericType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { - let base = canonical(model.base, in: scopeOfUse) + let base = canonical(model.base, in: scopeOfImplementation) let z = GenericArguments(model) // If the base is a product type, we specialize each stored part individually to check whether // the conformance holds for a specialized whole. Othwrwise, we specialize the base directly. if let b = ProductType(base) { - let s = AnyScopeID(b.decl) return program.storedParts(of: b.decl).allSatisfy { (p) in - let t = specialize(uncheckedType(of: p), for: z, in: s) - return conforms(t, to: trait, in: s) + let t = specialize(uncheckedType(of: p), for: z, in: scopeOfImplementation) + return conforms(t, to: trait, in: scopeOfImplementation) } } else { - let t = specialize(base, for: z, in: scopeOfUse) - return canSynthesizeConformance(t, to: trait, in: scopeOfUse) + let t = specialize(base, for: z, in: scopeOfImplementation) + return canSynthesizeConformance(t, to: trait, in: scopeOfImplementation) } } - /// Returns `true` if `model` structurally conforms to `trait` in `scopeOfUse`. + /// Returns `true` if implementations of `trait`'s requirements can be synthesized for `model` in + /// `scopeOfImplementation`. private mutating func canSynthesizeConformance( - _ model: ProductType, to trait: TraitType, in scopeOfUse: AnyScopeID + _ model: ProductType, to trait: TraitType, in scopeOfImplementation: AnyScopeID ) -> Bool { program.storedParts(of: model.decl).allSatisfy { (p) in - conforms(uncheckedType(of: p), to: trait, in: scopeOfUse) + conforms(uncheckedType(of: p), to: trait, in: scopeOfImplementation) } } @@ -1644,16 +1646,19 @@ struct TypeChecker { func syntheticImplementation( of requirement: AnyDeclID, withAPI expectedAPI: API ) -> SynthesizedFunctionDecl? { - guard + let scopeOfImplementation = AnyScopeID(origin.source)! + if let k = program.ast.synthesizedKind(of: requirement), - canSynthesizeConformance(model, to: trait, in: scopeOfDefinition) - else { return nil } - - let t = ArrowType(expectedAPI.type)! - let h = Array(t.environment.skolems) + canSynthesizeConformance(model, to: trait, in: scopeOfImplementation) + { + let t = ArrowType(expectedAPI.type)! + let h = Array(t.environment.skolems) - // Note: compiler-known requirement is assumed to be well-typed. - return .init(k, typed: t, parameterizedBy: h, in: AnyScopeID(origin.source)!) + // Note: compiler-known requirement is assumed to be well-typed. + return .init(k, typed: t, parameterizedBy: h, in: scopeOfImplementation) + } else { + return nil + } } /// Returns a concrete implementation of `requirement` for `model` with given `expectedAPI`, From 917ea304a12757deb26ea4aae67d29287a36f251 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 22 Aug 2024 23:34:00 +0200 Subject: [PATCH 2/4] Test the synthesization of conditional conformance --- .../TestCases/TypeChecking/ConditionalConformance.hylo | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo diff --git a/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo b/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo new file mode 100644 index 000000000..d0ab0f0b7 --- /dev/null +++ b/Tests/HyloTests/TestCases/TypeChecking/ConditionalConformance.hylo @@ -0,0 +1,8 @@ +//- typeCheck expecting: .success + +type Box { + public let contents: T + public memberwise init +} + +conformance Box: Equatable where T: Equatable {} From 5c61e65f74768d1508643c0d411e5c2135a5817d Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 22 Aug 2024 23:22:47 +0200 Subject: [PATCH 3/4] Declare conditional conformance of 'Optional' to 'Equatable' --- StandardLibrary/Sources/Core/Optional.hylo | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/StandardLibrary/Sources/Core/Optional.hylo b/StandardLibrary/Sources/Core/Optional.hylo index 1d9e04b0a..3df537677 100644 --- a/StandardLibrary/Sources/Core/Optional.hylo +++ b/StandardLibrary/Sources/Core/Optional.hylo @@ -19,3 +19,10 @@ public extension Optional { } } + +// Note: We can't declare confitional conformance of `Optional` to "umbrella traits" yet without +// causing ambiguities. See #1566 + +public conformance Optional: Deinitializable where T: Deinitializable {} + +public conformance Optional: Equatable where T: Equatable {} From fb2f60c650bef02f8bbf4fa2db7bf2dd592365d2 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 22 Aug 2024 23:24:53 +0200 Subject: [PATCH 4/4] Test equality of optionals --- Tests/LibraryTests/TestCases/OptionalTests.hylo | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/LibraryTests/TestCases/OptionalTests.hylo b/Tests/LibraryTests/TestCases/OptionalTests.hylo index c7cddb347..2c266c098 100644 --- a/Tests/LibraryTests/TestCases/OptionalTests.hylo +++ b/Tests/LibraryTests/TestCases/OptionalTests.hylo @@ -4,6 +4,8 @@ public fun main() { precondition(None() == None()) var x = 42 as Optional + precondition(x == x) + let y = if let i: Int = x { i.copy() } else { 0 } precondition(y == 42)