diff --git a/Library/Hylo/Core/Comparable.hylo b/Library/Hylo/Core/Comparable.hylo index fa84295d4..3edf16c7e 100644 --- a/Library/Hylo/Core/Comparable.hylo +++ b/Library/Hylo/Core/Comparable.hylo @@ -1,23 +1,39 @@ /// 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`. 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) } } 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 } } diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 2dc13ac3b..75a4dba86 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -290,11 +290,29 @@ 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) } + /// 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 = traitDeclaring(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..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: @@ -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 + } + } + } 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) +}