From 274ed4887e9693630b809ff73631f57f2158703a Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sun, 15 Oct 2023 15:22:52 +0200 Subject: [PATCH 1/6] Define additional comparison operators as requirements Operators like `Comparable.infix>` should be defined as requirements with a default implementation to make them part of the witness table. --- Library/Hylo/Core/Comparable.hylo | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Library/Hylo/Core/Comparable.hylo b/Library/Hylo/Core/Comparable.hylo index fa84295d4..eb56beedd 100644 --- a/Library/Hylo/Core/Comparable.hylo +++ b/Library/Hylo/Core/Comparable.hylo @@ -6,18 +6,24 @@ public trait Comparable: Equatable { /// Returns `true` iff `self` is ordered before `other`. fun infix< (_ other: Self) -> Bool -} - -extension Comparable { - /// Returns `true` iff `self` is ordered after `other`. - fun infix> (_ other: Self) -> Bool { (other < self) } + fun infix> (_ other: Self) -> Bool /// Returns `false` iff `self` is ordered after `other`. - fun infix<= (_ other: Self) -> Bool { !(other < self) } + fun infix<= (_ other: Self) -> Bool /// Returns `false` iff `self` is ordered before `other`. - fun infix>= (_ other: Self) -> Bool { !(self < other) } + fun infix>= (_ other: Self) -> Bool + +} + +public extension Comparable { + + public fun infix> (_ other: Self) -> Bool { (other < self) } + + public fun infix<= (_ other: Self) -> Bool { !(other < self) } + + public fun infix>= (_ other: Self) -> Bool { !(self < other) } } From e4aeba6d05efeffd736a0f2113297471b5c91008 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sun, 15 Oct 2023 15:25:53 +0200 Subject: [PATCH 2/6] Add documentation to 'Comparable' --- Library/Hylo/Core/Comparable.hylo | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Library/Hylo/Core/Comparable.hylo b/Library/Hylo/Core/Comparable.hylo index eb56beedd..3edf16c7e 100644 --- a/Library/Hylo/Core/Comparable.hylo +++ b/Library/Hylo/Core/Comparable.hylo @@ -1,6 +1,16 @@ /// A type whose instances' values have a standard total ordering /// -/// `<` is a total ordering; `a == b` implies that `a < b` and `b < a` are both false. +/// Types conforming to `Comparable` implement `infix==` and `infix<`. These to operations describe +/// a **strict total order** all instances of the conforming type. A strict total order on a type +/// `T` is a binary relation that satisfies the following for all instances `a`, `b`, and `c`: +/// +/// - `a < a` is always `false` (irreflexivity) +/// - if `a < b` then `b < a` is `false` (asymmetry) +/// - if `a < b` and `b < b` then `a < c` (transitivity) +/// - if `a == b` is `false` then either `a < b` or `b < a` (totality) +/// +/// Making `T` conform to `Comparable` automatically equips `T` with members `infix>`, `infix>=`, +/// and `infix<=`, which may be overridden for `T`. public trait Comparable: Equatable { /// Returns `true` iff `self` is ordered before `other`. From 1fa8ad01284564e708d21aae7c30ed167ae561d1 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Mon, 16 Oct 2023 02:00:11 +0200 Subject: [PATCH 3/6] Remove code duplication --- Sources/Core/Program.swift | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/Sources/Core/Program.swift b/Sources/Core/Program.swift index 727a29252..103130810 100644 --- a/Sources/Core/Program.swift +++ b/Sources/Core/Program.swift @@ -377,34 +377,15 @@ extension Program { let qualification = debugDescription(nodeToScope[n]!) - switch n.kind { - case FunctionDecl.self: - let s = ast.name(of: FunctionDecl.ID(n)!) ?? "lambda" - return qualification + ".\(s)" - case InitializerDecl.self: - let s = ast.name(of: InitializerDecl.ID(n)!) - return qualification + ".\(s)" - case MethodDecl.self: - let s = ast.name(of: MethodDecl.ID(n)!) - return qualification + ".\(s)" - case MethodImpl.self: - let s = ast[MethodImpl.ID(n)!].introducer.value - return qualification + ".\(s)" - case SubscriptDecl.self: - let s = ast.name(of: SubscriptDecl.ID(n)!) - return qualification + ".\(s)" - case SubscriptImpl.self: - let s = ast[SubscriptImpl.ID(n)!].introducer.value - return qualification + ".\(s)" - default: - break + if let d = AnyDeclID(n) { + if let s = name(of: d) { + return qualification + ".\(s)" + } else if d.kind == FunctionDecl.self { + return qualification + ".lambda" + } } - if let e = ast[n] as? SingleEntityDecl { - return qualification + "." + e.baseName - } else { - return qualification - } + return qualification } } From 6ac13e0ef98dcc752cabb4e4dc6cddc3f2473711 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Mon, 16 Oct 2023 02:04:48 +0200 Subject: [PATCH 4/6] Fix monomorphization of trait members --- Sources/FrontEnd/TypedProgram.swift | 18 ++++++++++++++++++ .../IR/Analysis/Module+Depolymorphize.swift | 5 ++--- Sources/IR/TypedProgram+Extensions.swift | 13 +++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 2dc13ac3b..69af485ec 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -295,6 +295,24 @@ public struct TypedProgram { return checker.traitDefining(d) } + /// If `d` is member of a trait `c`, returns `(d, c)` if `d` is a requirement, or `(r, c)` if `d` + /// is a default implementation of a requirement `r`. Otherwise, returns `nil`. + public func requirementDeclaring(_ d: AnyDeclID) -> (decl: AnyDeclID, trait: TraitType)? { + guard let c = traitDefining(d) else { return nil } + + // `d` might be the default definition of itself. + if isRequirement(d) { return (d, c) } + + // `d` might be the default implementation of some requirement with the same type. + let s = nodeToScope[d]! + let n = name(of: d) + let t = canonical(declType[d]!, in: s) + let r = ast.requirements(of: c.decl).first { (m) in + n == name(of: m) && areEquivalent(t, declType[m]!, in: s) + } + return r.map({ ($0, c) }) + } + /// Returns `true` iff `model` conforms to `concept` in `scopeOfUse`. public func conforms( _ model: AnyType, to concept: TraitType, in scopeOfUse: AnyScopeID diff --git a/Sources/IR/Analysis/Module+Depolymorphize.swift b/Sources/IR/Analysis/Module+Depolymorphize.swift index a2fe26090..033c5bac7 100644 --- a/Sources/IR/Analysis/Module+Depolymorphize.swift +++ b/Sources/IR/Analysis/Module+Depolymorphize.swift @@ -505,9 +505,8 @@ extension Module { /// a monomorphized copy of `f`. func rewritten(_ f: Function.ID, specializedBy a: GenericArguments) -> Function.ID { let p = program.specialize(a, for: specialization, in: scopeOfUse) - if let m = program.traitMember(referredBy: f) { - return monomorphize( - requirement: m.declaration, of: m.trait, in: ir, for: p, in: scopeOfUse) + if let m = program.requirementDeclaring(memberReferredBy: f) { + return monomorphize(requirement: m.decl, of: m.trait, in: ir, for: p, in: scopeOfUse) } else { return monomorphize(f, in: ir, for: p, in: scopeOfUse) } diff --git a/Sources/IR/TypedProgram+Extensions.swift b/Sources/IR/TypedProgram+Extensions.swift index 48bf39cf0..defeb4e8e 100644 --- a/Sources/IR/TypedProgram+Extensions.swift +++ b/Sources/IR/TypedProgram+Extensions.swift @@ -16,4 +16,17 @@ extension TypedProgram { } } + /// If `f` refers to the member `d` of trait `c`, returns `(d, c)` if `d` is a requirement, or + /// `(r, c)` if `d` is a default implementation of a requirement `r`. Otherwise, returns `nil`. + func requirementDeclaring( + memberReferredBy f: Function.ID + ) -> (decl: AnyDeclID, trait: TraitType)? { + switch f.value { + case .lowered(let d): + return requirementDeclaring(d) + default: + return nil + } + } + } From 81361f5918b86ecf2a9e8615f1307788c90ba87d Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Mon, 16 Oct 2023 02:06:04 +0200 Subject: [PATCH 5/6] Rename 'traitDefining' for consistency with 'requirementDeclaring' --- Sources/FrontEnd/TypedProgram.swift | 4 ++-- Sources/IR/TypedProgram+Extensions.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 69af485ec..75a4dba86 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -290,7 +290,7 @@ public struct TypedProgram { } /// Returns the trait of which `d` is a member, or `nil` if `d` isn't member of a trait. - public func traitDefining(_ d: T) -> TraitType? { + public func traitDeclaring(_ d: T) -> TraitType? { var checker = TypeChecker(asContextFor: self) return checker.traitDefining(d) } @@ -298,7 +298,7 @@ public struct TypedProgram { /// If `d` is member of a trait `c`, returns `(d, c)` if `d` is a requirement, or `(r, c)` if `d` /// is a default implementation of a requirement `r`. Otherwise, returns `nil`. public func requirementDeclaring(_ d: AnyDeclID) -> (decl: AnyDeclID, trait: TraitType)? { - guard let c = traitDefining(d) else { return nil } + guard let c = traitDeclaring(d) else { return nil } // `d` might be the default definition of itself. if isRequirement(d) { return (d, c) } diff --git a/Sources/IR/TypedProgram+Extensions.swift b/Sources/IR/TypedProgram+Extensions.swift index defeb4e8e..67a742eac 100644 --- a/Sources/IR/TypedProgram+Extensions.swift +++ b/Sources/IR/TypedProgram+Extensions.swift @@ -8,7 +8,7 @@ extension TypedProgram { func traitMember(referredBy f: Function.ID) -> (declaration: AnyDeclID, trait: TraitType)? { switch f.value { case .lowered(let d): - guard let t = traitDefining(d) else { return nil } + guard let t = traitDeclaring(d) else { return nil } return (declaration: d, trait: t) default: From b41e00fd3308b0c28b60bf32fd6c1c59e88cb17f Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Mon, 16 Oct 2023 02:30:08 +0200 Subject: [PATCH 6/6] Test trait member dispatch --- .../TestCases/TraitMemberDispatch.hylo | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo diff --git a/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo b/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo new file mode 100644 index 000000000..a55697a19 --- /dev/null +++ b/Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo @@ -0,0 +1,30 @@ +//- compileAndRun expecting: success + +// This program tests the dispatch behavior of calls to trait members. `P` declares a single +// requirement `f` that is given a default implementation. Two conformances to `P` are defined: +// `Int: P` "overrides" the default implementation of `f` and `Bool: P` uses it. `call_f(on:)` +// accepts a generic parameter bound by `P` and calls `f`. Because it is generic, the compiler +// can't determine how to dispatch `f` before depolymoprhization. + +trait P { + fun f() -> Int +} + +extension P { + public fun f() -> Int { 0 } +} + +conformance Bool: P {} + +conformance Int: P { + public fun f() -> Int { self.copy() } +} + +fun call_f(on x: T) -> Int { + x.f() +} + +public fun main() { + precondition(call_f(on: 1234).f() == 1234) + precondition(call_f(on: true).f() == 0) +}