Skip to content

Commit

Permalink
Merge pull request #1090 from hylo-lang/fix-comparable
Browse files Browse the repository at this point in the history
Fix the definition of `Comparable`
  • Loading branch information
kyouko-taiga authored Oct 16, 2023
2 parents 0c8e332 + b41e00f commit 2b0d479
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 39 deletions.
32 changes: 24 additions & 8 deletions Library/Hylo/Core/Comparable.hylo
Original file line number Diff line number Diff line change
@@ -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) }

}

Expand Down
33 changes: 7 additions & 26 deletions Sources/Core/Program.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
20 changes: 19 additions & 1 deletion Sources/FrontEnd/TypedProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: DeclID>(_ d: T) -> TraitType? {
public func traitDeclaring<T: DeclID>(_ 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
Expand Down
5 changes: 2 additions & 3 deletions Sources/IR/Analysis/Module+Depolymorphize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
15 changes: 14 additions & 1 deletion Sources/IR/TypedProgram+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ 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:
return nil
}
}

/// 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
}
}

}
30 changes: 30 additions & 0 deletions Tests/EndToEndTests/TestCases/TraitMemberDispatch.hylo
Original file line number Diff line number Diff line change
@@ -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<T: P>(on x: T) -> Int {
x.f()
}

public fun main() {
precondition(call_f(on: 1234).f() == 1234)
precondition(call_f(on: true).f() == 0)
}

0 comments on commit 2b0d479

Please sign in to comment.