Skip to content
Merged
4 changes: 1 addition & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
name = "BorrowChecker"
uuid = "7bdcaa52-c310-4bb0-bf54-d941056ed284"
version = "0.4.6"
version = "0.5.0"
authors = ["MilesCranmer <miles.cranmer@gmail.com>"]

[deps]
DispatchDoctor = "8d63f2c5-f18a-4cf2-ba9d-b3f60fc568c8"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
DispatchDoctor = "0.4.19"
MacroTools = "0.5"
Preferences = "1.4"
Random = "1"
julia = "1.10"
597 changes: 27 additions & 570 deletions README.md

Large diffs are not rendered by default.

111 changes: 5 additions & 106 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,117 +4,16 @@
CurrentModule = BorrowChecker
```

## Ownership Macros

```@docs
@own
@move
@clone
@take
@take!
```

## References and Lifetimes

```@docs
@lifetime
@ref
Mutex
@ref_into
@bc
@mut
@&
```

## Validation

```@docs
@cc
BorrowChecker.@spawn
```

## IR Borrow Checker
## Automatic Checking

```@docs
BorrowChecker.Auto.@safe
BorrowChecker.Auto.@unsafe
BorrowChecker.Auto.BorrowCheckError
```

## Types

```@docs
BorrowChecker.TypesModule.AbstractOwned
BorrowChecker.TypesModule.AbstractBorrowed
Owned
OwnedMut
Borrowed
BorrowedMut
LazyAccessor
OrBorrowed
OrBorrowedMut
```

## Traits

```@docs
is_static
```

## Errors
## Preferences

```@docs
BorrowError
MovedError
BorrowRuleError
SymbolMismatchError
ExpiredError
BorrowChecker.PreferencesModule.disable_by_default!
```

## Internals

Normally, you should rely on `OrBorrowed` and `OrBorrowedMut` to work with borrowed values, or use `@take` and `@take!` to unwrap owned values. However, for convenience, it might be useful to define functions on `Owned` and `OwnedMut` types, if you are confident that your operation will not "move" the input or return a view of it.

Many functions in Base are already overloaded. But if you need to define your own, you can do so by using the `request_value` function and the `AllWrappers` type union.

### Core Types

- `AllWrappers{T}`: A type union that includes all wrapper types (`Owned{T}`, `OwnedMut{T}`, `Borrowed{T}`, `BorrowedMut{T}`, and `LazyAccessor{T}`). This is used to write generic methods that work with any wrapped value.

### Core Functions

- `request_value(x, Val(:read))`: Request read access to a wrapped value
- `request_value(x, Val(:write))`: Request write access to a wrapped value

### Examples

Here's how common operations are overloaded:

1. Binary operations (like `*`) that only need read access:

```julia
function Base.:(*)(l::AllWrappers{<:Number}, r::AllWrappers{<:Number})
return Base.:(*)(request_value(l, Val(:read)), request_value(r, Val(:read)))
end
```

2. Mutating operations (like `pop!`) that need write access:

```julia
function Base.pop!(r::AllWrappers)
return Base.pop!(request_value(r, Val(:write)))
end
```

The `request_value` function performs safety checks before allowing access:
- For read access: Verifies the value hasn't been moved
- For write access: Verifies the value is mutable and not borrowed

Note that for operations that need write access, and return a view of the input, it is wise to modify the standard output to return `nothing` instead, which is what we do for `push!`:

```julia
function Base.push!(r::AllWrappers, items...)
Base.push!(request_value(r, Val(:write)), items...)
return nothing
end
```

While this violates the expected return type, it is a necessary evil for safety. The `nothing` return will cause loud errors if you have code that relies on this design. This is good! Loud bugs are collaborators; silent bugs are saboteurs.
44 changes: 10 additions & 34 deletions src/BorrowChecker.jl
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
module BorrowChecker

using MacroTools
using MacroTools: rmlines
using DispatchDoctor: @stable
include("preferences.jl")
include("auto.jl")
Comment thread
MilesCranmerBot marked this conversation as resolved.

@stable default_mode = "disable" begin
include("utils.jl")
include("static_trait.jl")
include("errors.jl")
include("types.jl")
include("preferences.jl")
include("semantics.jl")
include("mutex.jl")
include("macros.jl")
include("overloads.jl")
include("type_overloads.jl")
include("disambiguations.jl")
include("auto.jl")
end

#! format: off
using .ErrorsModule: BorrowError, MovedError, BorrowRuleError, SymbolMismatchError, ExpiredError, AliasedReturnError
using .TypesModule: Owned, OwnedMut, Borrowed, BorrowedMut, LazyAccessor, OrBorrowed, OrBorrowedMut
using .MacrosModule: @own, @move, @ref, @ref_into, @take, @take!, @lifetime, @clone, @bc, @mut, @cc, @&
using .MutexModule: Mutex

export MovedError, BorrowError, BorrowRuleError, SymbolMismatchError, ExpiredError
export Owned, OwnedMut, Borrowed, BorrowedMut, LazyAccessor, OrBorrowed, OrBorrowedMut
export @own, @move, @ref, @ref_into, @take, @take!, @lifetime, @clone, @bc, @mut, @cc, @&
export @safe, @unsafe
export Mutex
export @safe, @unsafe, disable_by_default!

# Not exported but still available
using .Auto: @auto, @safe, @unsafe
using .PreferencesModule: disable_by_default!
using .StaticTraitModule: is_static
using .TypesModule: AsMutable, Lifetime, LazyAccessorOf, is_moved, get_owner, get_symbol, get_immutable_borrows, get_mutable_borrows
using .MacrosModule: @spawn
using .MutexModule: AbstractMutex, MutexGuard, LockNotHeldError, NoOwningMutexError, MutexGuardValueAccessError
#! format: on

# `BorrowCheckError` only exists where the automatic checker is supported
# (Julia >= 1.12 with `Base.code_ircode_by_type`); older versions get stubs.
if isdefined(Auto, :BorrowCheckError)
export BorrowCheckError
using .Auto: BorrowCheckError
end

end
5 changes: 3 additions & 2 deletions src/auto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ using DispatchDoctor: @unstable

export @auto, @safe, @unsafe

@static if isdefined(Base, :code_ircode_by_type) && v"1.12.0-" <= VERSION < v"1.15.0-"
@static if isdefined(Base, :code_ircode_by_type) && v"1.12.0-" <= VERSION < v"1.13.0-"
@unstable include("auto/auto_ir.jl")
else
# COV_EXCL_START
"""
Automatic compiler-IR borrow checker.

This feature requires `Base.code_ircode_by_type`.
This feature requires Julia 1.12.x with `Base.code_ircode_by_type`.
On other versions (including 1.13+) this is a warn-and-pass-through stub.
"""
"Unavailable `@auto` stub for unsupported Julia versions."
macro auto(args...)
Expand Down
8 changes: 8 additions & 0 deletions src/auto/diagnostics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ struct BorrowViolation
stmt::Any
end

"""
BorrowCheckError <: Exception

Thrown by [`BorrowChecker.Auto.@safe`](@ref) when a method specialization violates
borrow-checking rules. Carries the checked signature (`tt`) and the list of
individual `BorrowViolation`s; `showerror` renders a source-level diagnostic
for each violation.
"""
struct BorrowCheckError <: Exception
tt::Any
violations::Vector{BorrowViolation}
Expand Down
30 changes: 0 additions & 30 deletions src/disambiguations.jl

This file was deleted.

95 changes: 0 additions & 95 deletions src/errors.jl

This file was deleted.

Loading
Loading