diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index 577455ca1..fbd2abf4d 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -6,7 +6,7 @@ public type Array: Deinitializable { /// The header of the buffer indicates the number of elements contained in the array. var storage: DynamicBuffer - /// Creates a new, empty array. + /// Creates an empty array. public init() { &storage = .new() } @@ -15,22 +15,27 @@ public type Array: Deinitializable { public fun deinit() sink { var i = 0 while i < count() { - &pointer_to_element(at: i).unsafe_pointee().deinit() + &pointer_to_element[at: i].unsafe_pointee().deinit() &i += 1 } } - /// The number of elements in the array. + /// Returns the number of elements in `self`. public fun count() -> Int { if storage.capacity() == 0 { 0 } else { storage.header.copy() } } + /// Returns `true` if `self` is empty. + public fun is_empty() -> Bool { + count() == 0 + } + /// The number of elements that can be stored in the array before new storage must be allocated. public fun capacity() -> Int { return storage.capacity() } - /// Reserves enough space to store `n` elements + /// Reserves enough space to store `n` elements in `self`. public fun reserve_capacity(_ n: Int) inout { if n < capacity() { return } @@ -60,31 +65,70 @@ public type Array: Deinitializable { &storage = new_storage } + /// Projects a pointer to the start of the array's contiguous storage. + /// + /// The projected pointer is valid only for the duration of the projection and can be advanced up + /// to `count()`. It may be null if `self` is empty. + public property contiguous_storage: Pointer { + yield if capacity() == 0 { .null() } else { .new(pointer_to_element[at: 0]) } + } + + /// Calls `action` with a pointer to the start of the array's mutable contiguous storage. + /// + /// The projected pointer is valid only for the duration of the projection and can be advanced up + /// to `count()`. It may be null if `self` is empty. + public fun with_mutable_contiguous_storage( + _ action: inout [E](PointerToMutable) inout -> T + ) inout -> T { + if capacity() == 0 { &action(.null()) } else { &action(pointer_to_element[at: 0]) } + } + /// Adds a new element at the end of the array. public fun append(_ source: sink Element) inout { &reserve_capacity(count() + 1) - pointer_to_element(at: count()).unsafe_initialize_pointee(source) + pointer_to_element[at: count()].unsafe_initialize_pointee(source) &storage.header += 1 } + /// Exchanges the values at the given positions in `self`. + public fun swap_at(_ i: Int, _ j: Int) inout { + // precondition(i >= 0 && i < count()) + // precondition(j >= 0 && j < count()) + if i == j { return } + swap(&pointer_to_element[at: i].unsafe[], &pointer_to_element[at: j].unsafe[]) + } + + /// Reverses the elements of `self` in place. + /// + /// - Complexity: O(n), where n is the number of elements in `self`. + public fun reverse() inout { + var i = count() - 1 + var j = 0 + while i > j { + swap_at(i, j) + &i -= 1 + &j += 1 + } + } + /// Accesses the element at `position`. /// /// - Requires: `position` is in the range `0 ..< count()`. public subscript(_ position: Int): Element { let { // precondition(position >= 0 && position < count()) - pointer_to_element(at: position).unsafe[] + pointer_to_element[at: position].unsafe[] } inout { // precondition(position >= 0 && position < count()) - pointer_to_element(at: position).unsafe[] + pointer_to_element[at: position].unsafe[] } } - /// Returns the address of the element at `position`. + /// Projects the address of the element at `position`. /// /// - Requires: `position` is in the range `0 ..< capacity()`. - fun pointer_to_element(at position: Int) -> PointerToMutable { + subscript pointer_to_element(at position: Int): PointerToMutable { storage.first_element_address().advance(by: position) } diff --git a/Library/Hylo/Core/Bitcast.hylo b/Library/Hylo/Core/Bitcast.hylo index 8f6589a0c..fd46d8acb 100644 --- a/Library/Hylo/Core/Bitcast.hylo +++ b/Library/Hylo/Core/Bitcast.hylo @@ -5,7 +5,13 @@ public subscript unsafe_bitcast(_ value: T): U { yield p.unsafe[] } inout { - sink let p: PointerToMutable = PointerToMutable(type_punning: pointerToMutable[&value]) + sink let p: PointerToMutable = PointerToMutable(type_punning: mutable_pointer[to: &value]) yield &p.unsafe[] } } + +/// Returns `value` with its memory representation reinterpreted as a value of type `U`. +public fun unsafe_bitcast(consuming value: T) -> U { + sink let p: PointerToMutable = PointerToMutable(type_punning: mutable_pointer[to: &value]) + return p.unsafe_pointee() +} diff --git a/Library/Hylo/Core/Movable.hylo b/Library/Hylo/Core/Movable.hylo index 6be7a1cf6..7bcceb159 100644 --- a/Library/Hylo/Core/Movable.hylo +++ b/Library/Hylo/Core/Movable.hylo @@ -8,3 +8,21 @@ public trait Movable { } } + +public extension Movable { + + /// Exchanges the value of `self` with that of `other`. + public fun exchange(with other: inout Self) inout { + sink let x = self + &self = other + &other = x + } + +} + +// TODO: Remove in favor of `Movable.exchange(with:)` when #1064 is fixed +public fun swap(_ a: inout T, _ b: inout T) { + sink let x = a + &a = b + &b = x +} diff --git a/Library/Hylo/Core/PointerToMutable.hylo b/Library/Hylo/Core/PointerToMutable.hylo index cca288971..e31bfb88e 100644 --- a/Library/Hylo/Core/PointerToMutable.hylo +++ b/Library/Hylo/Core/PointerToMutable.hylo @@ -60,13 +60,6 @@ public type PointerToMutable: Regular { } - -// FIXME: rename pointer[toMutable:] -/// The address of `x`. -public subscript pointerToMutable(_ x: inout T): PointerToMutable { - let { yield PointerToMutable(base: Builtin.address(of: x)) } -} - public conformance PointerToMutable: Copyable { /// Returns an equivalent instance. @@ -113,12 +106,12 @@ public extension PointerToMutable where Pointee: Movable { } +// TODO: Rename to `pointer[to_mutable:]` /// The address of `x`. public subscript mutable_pointer(to x: inout T): PointerToMutable { let { yield PointerToMutable(base: Builtin.address(of: x)) } } - /// Initializes `x` to `y`. /// /// - Note: This function is a workaround for the lack of `set` bindings (see #925). diff --git a/Library/Hylo/Pointers+DynamicAllocation.hylo b/Library/Hylo/Pointers+DynamicAllocation.hylo index 841d3dc76..29fe31d64 100644 --- a/Library/Hylo/Pointers+DynamicAllocation.hylo +++ b/Library/Hylo/Pointers+DynamicAllocation.hylo @@ -2,7 +2,7 @@ public extension PointerToMutable where Pointee == Never { /// Allocates memory for `count` bytes at given `alignment`. public static fun allocate_bytes(count: Int, aligned_at alignment: Int) -> Self { - Self.new(base: aligned_alloc(alignment, count).base) + Self.new(base: hylo_aligned_alloc(alignment, count).base) } } @@ -23,12 +23,14 @@ public extension PointerToMutable { /// Allocates memory for `count` instances of `Pointee`. public static fun allocate(count: Int) -> Self { - Self.new(base: aligned_alloc(MemoryLayout.alignment(), MemoryLayout.stride() * count).base) + return Self.new(base: hylo_aligned_alloc( + MemoryLayout.alignment(), + MemoryLayout.stride() * count).base) } /// Deallocates the memory previously allocated at `self`. public fun deallocate() { - free(CVoidPointer(base: base)) + hylo_aligned_free(MemoryAddress(base: base)) } } diff --git a/Library/Hylo/Print.hylo b/Library/Hylo/Print.hylo index fefe5f60f..5f1278fb2 100644 --- a/Library/Hylo/Print.hylo +++ b/Library/Hylo/Print.hylo @@ -5,6 +5,33 @@ public fun print(_ item: String, terminator: String = "\n") { _ = fwrite(CVoidPointer(base: terminator.utf8.base), 1, 1, CVoidPointer(base: stream.base)) } +/// Writes the textual representation of `item` to the standard output. +public fun print(_ item: Int, radix: Int = 10, terminator: String = "\n") { + if item == 0 { + print("0") + return + } + + var a = Array() + var v = item.abs() + + while v != 0 { + let i = v % radix + &v /= radix + // Note: 48 = "0" and 97 = "a" + &a.append((Int8(truncating_or_extending: i + if i < 10 { 48 } else { 87 }))) + } + + // Note: 45 = "-" + if item < 0 { &a.append(45) } + &a.reverse() + + let stream = stdout() + let buffer = a.contiguous_storage.base + _ = fwrite(CVoidPointer(base: buffer), 1, a.count(), CVoidPointer(base: stream.base)) + _ = fwrite(CVoidPointer(base: terminator.utf8.base), 1, 1, CVoidPointer(base: stream.base)) +} + /// The standard output of the current process. fun stdout() -> MemoryAddress { .new(base: fdopen(1, CVoidPointer(base: "w".utf8.base)).base) diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 2ce52c4fd..d49970bdc 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -246,6 +246,15 @@ struct TypeChecker { for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type { let e = environment(of: s)! result.formUnion(e.conformedTraits(of: ^t)) + + // Note: `s` might be extending the type whose declaration introduced the generic environment + // that declared `t`. + if s.kind.value is TypeExtendingDecl.Type { + let d = AnyDeclID(s)! + if let g = environment(introducedByDeclOf: uncheckedType(of: d)) { + result.formUnion(g.conformedTraits(of: ^t)) + } + } } return result } @@ -1359,6 +1368,20 @@ struct TypeChecker { return result } + /// Returns the generic environment introduced by the declaration of `t`, if any. + private mutating func environment(introducedByDeclOf t: AnyType) -> GenericEnvironment? { + switch t.base { + case let u as ProductType: + return environment(of: u.decl) + case let u as TraitType: + return environment(of: u.decl) + case let u as TypeAliasType: + return environment(of: u.decl) + default: + return nil + } + } + /// Insert's `d`'s constraints in `e`. private mutating func insertConstraints( of d: AssociatedTypeDecl.ID, in e: inout GenericEnvironment @@ -2978,6 +3001,7 @@ struct TypeChecker { // The specialization of the match includes that of context in which it was looked up. var specialization = genericArguments(inScopeIntroducing: m, resolvedIn: context) + candidateType = specialize(candidateType, for: specialization, in: scopeOfUse) // Keep track of generic arguments that should be captured later on. let candidateSpecialization = genericArguments( @@ -3008,6 +3032,12 @@ struct TypeChecker { } } + // Re-specialize the candidate's type now that the substitution map is complete. + // + // The specialization map now contains the substitutions accumulated from the candidate's + // qualification as well as the ones related to the resolution of the candidate itself. For + // example, if we resolved `A.f`, we'd get `X` from the resolution of the qualification + // and `Y` from the resolution of the candidate. candidateType = specialize(candidateType, for: specialization, in: scopeOfUse) let r = program.makeReference( diff --git a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo index fc9958a6f..ab849921a 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo @@ -1,12 +1,24 @@ //- typeCheck expecting: success -type A { +type A: Deinitializable { public memberwise init public property x: Int { let { 0 } } } +type B: Deinitializable { + public memberwise init +} + +type C: Deinitializable { + public memberwise init + public property x: B { .new() } +} + fun check(_ x: T) {} public fun main() { check(A().x) + + let a = C().x + check>(a) } diff --git a/Tests/LibraryTests/TestCases/ArrayTests.hylo b/Tests/LibraryTests/TestCases/ArrayTests.hylo index a975a83b8..d18038b3c 100644 --- a/Tests/LibraryTests/TestCases/ArrayTests.hylo +++ b/Tests/LibraryTests/TestCases/ArrayTests.hylo @@ -16,7 +16,29 @@ fun test_append() { precondition(d[2] == 84) } +fun test_swap_at() { + var a = Array() + &a.append(false) + &a.append(true) + &a.swap_at(0, 1) + precondition(a[0]) +} + +fun test_reverse() { + var a = Array() + &a.append(21) + &a.append(42) + &a.append(84) + + &a.reverse() + precondition(a[0] == 84) + precondition(a[1] == 42) + precondition(a[2] == 21) +} + public fun main() { test_init_empty() test_append() + test_swap_at() + test_reverse() }