Full Code of jw3126/Setfield.jl for AI

master 7e87f1f040f3 cached
33 files
60.2 KB
21.2k tokens
1 requests
Download .txt
Repository: jw3126/Setfield.jl
Branch: master
Commit: 7e87f1f040f3
Files: 33
Total size: 60.2 KB

Directory structure:
gitextract_z43yv95v/

├── .codecov.yml
├── .github/
│   └── workflows/
│       ├── CI.yml
│       ├── CompatHelper.yml
│       ├── Invalidations.yml
│       └── TagBot.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── appveyor.yml
├── docs/
│   ├── .gitignore
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── examples/
│       │   └── .gitignore
│       ├── index.md
│       ├── internals.md
│       └── intro.md
├── examples/
│   └── custom_macros.jl
├── src/
│   ├── Setfield.jl
│   ├── functionlenses.jl
│   ├── lens.jl
│   ├── setindex.jl
│   └── sugar.jl
└── test/
    ├── dynamiclens_begin.jl
    ├── perf.jl
    ├── runtests.jl
    ├── test_core.jl
    ├── test_examples.jl
    ├── test_functionlenses.jl
    ├── test_quicktypes.jl
    ├── test_setindex.jl
    ├── test_setmacro.jl
    └── test_staticarrays.jl

================================================
FILE CONTENTS
================================================

================================================
FILE: .codecov.yml
================================================
comment: false


================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on:
  - push
  - pull_request
jobs:
  test:
    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        version:
          - '1.6'
          - '1'
          - 'nightly'
        os:
          - ubuntu-latest
        arch:
          - x64
        include:
          - os: macOS-latest
            version: '1'
            arch: x64
          - os: windows-latest
            version: '1'
            arch: x64
    steps:
      - uses: actions/checkout@v2
      - uses: julia-actions/setup-julia@v1
        with:
          version: ${{ matrix.version }}
          arch: ${{ matrix.arch }}
          show-versioninfo: true
      - uses: actions/cache@v1
        env:
          cache-name: cache-artifacts
        with:
          path: ~/.julia/artifacts
          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
          restore-keys: |
            ${{ runner.os }}-test-${{ env.cache-name }}-
            ${{ runner.os }}-test-
            ${{ runner.os }}-
      - uses: julia-actions/julia-buildpkg@latest
      - uses: julia-actions/julia-runtest@latest
  docs:
    name: Documentation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: julia-actions/setup-julia@v1
        with:
          version: 1
      - run: |
          julia --project=docs -e '
            using Pkg
            Pkg.develop(PackageSpec(path=pwd()))
            Pkg.instantiate()'
      - run: |
          julia --project=docs -e '
            using Documenter: doctest
            using Setfield
            doctest(Setfield)'
      - run: julia --project=docs docs/make.jl
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}


================================================
FILE: .github/workflows/CompatHelper.yml
================================================
name: CompatHelper

on:
  schedule:
    - cron: '00 * * * *'

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        julia-version: [1]
        julia-arch: [x86]
        os: [ubuntu-latest]
    steps:
      - uses: julia-actions/setup-julia@latest
        with:
          version: ${{ matrix.julia-version }}
      - name: Pkg.add("CompatHelper")
        run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
      - name: CompatHelper.main()
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: julia -e 'using CompatHelper; CompatHelper.main()'


================================================
FILE: .github/workflows/Invalidations.yml
================================================
name: Invalidations

on:
  pull_request:

concurrency:
  # Skip intermediate builds: always.
  # Cancel intermediate builds: always.
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  evaluate:
    # Only run on PRs to the default branch.
    # In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch
    if: github.base_ref == github.event.repository.default_branch
    runs-on: ubuntu-latest
    steps:
    - uses: julia-actions/setup-julia@v1
      with:
        version: '1'
    - uses: actions/checkout@v3
    - uses: julia-actions/julia-buildpkg@v1
    - uses: julia-actions/julia-invalidations@v1
      id: invs_pr

    - uses: actions/checkout@v3
      with:
        ref: ${{ github.event.repository.default_branch }}
    - uses: julia-actions/julia-buildpkg@v1
    - uses: julia-actions/julia-invalidations@v1
      id: invs_default
    
    - name: Report invalidation counts
      run: |
        echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
        echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
    - name: Check if the PR does increase number of invalidations
      if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total
      run: exit 1


================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
  issue_comment:
    types:
      - created
  workflow_dispatch:
jobs:
  TagBot:
    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
    runs-on: ubuntu-latest
    steps:
      - uses: JuliaRegistries/TagBot@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          ssh: ${{ secrets.DOCUMENTER_KEY }}


================================================
FILE: .gitignore
================================================
*.jl.cov
*.jl.*.cov
*.jl.mem
Manifest.toml


================================================
FILE: LICENSE.md
================================================
The Setfield.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2017: Jan Weidner.
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
>


================================================
FILE: Project.toml
================================================
name = "Setfield"
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
version = "1.1.2"

[deps]
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
Future = "9fa8497b-333b-5362-9e8d-4d0656e87820"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"

[compat]
ConstructionBase = "0.1, 1.0"
StaticArraysCore = "1"
MacroTools = "0.4.4, 0.5"
julia = "1.6"

[extras]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
PerformanceTestTools = "dc46b164-d16f-48ec-a853-60448fc869fe"
QuickTypes = "ae2dfa86-617c-530c-b392-ef20fdad97bb"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StaticNumbers = "c5e4b96a-f99f-5557-8ed2-dc63ef9b5131"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Documenter", "PerformanceTestTools", "QuickTypes", "StaticArrays", "BenchmarkTools", "InteractiveUtils", "StaticNumbers"]


================================================
FILE: README.md
================================================
# Setfield

[![DocStable](https://img.shields.io/badge/docs-stable-blue.svg)](https://jw3126.github.io/Setfield.jl/stable/intro)
[![DocDev](https://img.shields.io/badge/docs-dev-blue.svg)](https://jw3126.github.io/Setfield.jl/dev/intro)
![CI](https://github.com/jw3126/Setfield.jl/workflows/CI/badge.svg)

Update deeply nested immutable structs.

# Lifecycle

We plan to maintain `Setfield.jl` for a long time (written 2020-09-21, reinforced 2021-08-01, 2022-09-08, 2024-02-15). We will however not add new features. For a successor, see [Accessors.jl](https://github.com/JuliaObjects/Accessors.jl).

# Usage
Updating deeply nested immutable structs was never easier:
```julia
using Setfield
@set obj.a.b.c = d
```
For more information, see [the documentation](https://jw3126.github.io/Setfield.jl/latest/intro/) and/or watch this video:

[![JuliaCon2020 Changing the immutable](https://img.youtube.com/vi/vkAOYeTpLg0/0.jpg)](https://youtu.be/vkAOYeTpLg0 "Changing the immutable")

# Some creative usages of Setfield

* [VegaLite.jl](https://github.com/queryverse/VegaLite.jl) overloads
  `getproperty` and lens API to manipulate JSON-based nested objects.

* [Kaleido.jl](https://github.com/tkf/Kaleido.jl) is a library of
  additional lenses.

* [PhaseSpaceIO.jl](https://github.com/jw3126/PhaseSpaceIO.jl) overloads
  `getproperty` and `setproperties` to get/set values from/in packed bits.


================================================
FILE: appveyor.yml
================================================
environment:
  matrix:
  - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
  - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
  - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
  - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"

## uncomment the following lines to allow failures on nightly julia
## (tests will run but not make your overall status red)
#matrix:
#  allow_failures:
#  - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
#  - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"

branches:
  only:
    - master
    - /release-.*/

notifications:
  - provider: Email
    on_build_success: false
    on_build_failure: false
    on_build_status_changed: false

install:
  - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12"
# If there's a newer build queued for the same PR, cancel this one
  - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
        https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
        Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
        throw "There are newer queued builds for this pull request, failing early." }
# Download most recent Julia Windows binary
  - ps: (new-object net.webclient).DownloadFile(
        $env:JULIA_URL,
        "C:\projects\julia-binary.exe")
# Run installer silently, output to C:\projects\julia
  - C:\projects\julia-binary.exe /S /D=C:\projects\julia

build_script:
# Need to convert from shallow to complete for Pkg.clone to work
  - IF EXIST .git\shallow (git fetch --unshallow)
  - C:\projects\julia\bin\julia -e "versioninfo();
      Pkg.clone(pwd(), \"Setfield\"); Pkg.build(\"Setfield\")"

test_script:
  - C:\projects\julia\bin\julia -e "Pkg.test(\"Setfield\")"


================================================
FILE: docs/.gitignore
================================================
build
site


================================================
FILE: docs/Project.toml
================================================
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"


================================================
FILE: docs/make.jl
================================================
using Setfield, Documenter, Literate

inputdir = joinpath(@__DIR__, "..", "examples")
outputdir = joinpath(@__DIR__, "src", "examples")
mkpath(outputdir)
for filename in readdir(inputdir)
    inpath = joinpath(inputdir, filename)
    Literate.markdown(inpath, outputdir; documenter=true)
end

makedocs(
         modules = [Setfield],
         sitename = "Setfield.jl",
         pages = [
            "Introduction" => "intro.md",
            "Docstrings" => "index.md",
            "Custom Macros" => "examples/custom_macros.md",
             hide("internals.md"),
            ],
        strict = true,  # to exit with non-zero code on error
        )

deploydocs(
    repo = "github.com/jw3126/Setfield.jl.git",
)


================================================
FILE: docs/src/examples/.gitignore
================================================
*.md


================================================
FILE: docs/src/index.md
================================================
## Docstrings

```@autodocs
Modules = [Setfield]
Private = false
```


================================================
FILE: docs/src/internals.md
================================================
# Internals

```@autodocs
Modules = [Setfield]
Public = false
```


================================================
FILE: docs/src/intro.md
================================================
## Usage

Say we have a deeply nested struct:

```jldoctest spaceship
julia> using StaticArrays;

julia> struct Person
           name::Symbol
           age::Int
       end;

julia> struct SpaceShip
           captain::Person
           velocity::SVector{3, Float64}
           position::SVector{3, Float64}
       end;

julia> s = SpaceShip(Person(:julia, 2009), [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
SpaceShip(Person(:julia, 2009), [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
```
Lets update the captains name:
```jldoctest spaceship; filter = r" .*$"
julia> s.captain.name = :JULIA
ERROR: type Person is immutable
```
It's a bit cryptic but what it means that Julia tried very hard to set the field but gave it up since the struct is immutable.  So we have to do:
```jldoctest spaceship
julia> SpaceShip(Person(:JULIA, s.captain.age), s.velocity, s.position)
SpaceShip(Person(:JULIA, 2009), [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
```
This is messy and things get worse, if the structs are bigger. `Setfields` to the rescue!

```jldoctest spaceship
julia> using Setfield

julia> s = @set s.captain.name = :JULIA
SpaceShip(Person(:JULIA, 2009), [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> s = @set s.velocity[1] += 999999
SpaceShip(Person(:JULIA, 2009), [999999.0, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> s = @set s.velocity[1] += 1000001
SpaceShip(Person(:JULIA, 2009), [2.0e6, 0.0, 0.0], [0.0, 0.0, 0.0])

julia> @set s.position[2] = 20
SpaceShip(Person(:JULIA, 2009), [2.0e6, 0.0, 0.0], [0.0, 20.0, 0.0])
```

## Under the hood

Under the hood this package implements a simple [lens](https://hackage.haskell.org/package/lens) api.
This api may be useful in its own right and works as follows:

```jldoctest
julia> using Setfield

julia> l = @lens _.a.b
(@lens _.a.b)

julia> struct AB;a;b;end

julia> obj = AB(AB(1,2),3)
AB(AB(1, 2), 3)

julia> set(obj, l, 42)
AB(AB(1, 42), 3)

julia> obj
AB(AB(1, 2), 3)

julia> get(obj, l)
2

julia> modify(x->10x, obj, l)
AB(AB(1, 20), 3)
```

Now the `@set` macro simply provides sugar for creating a `lens` and applying it.
For instance
```julia
@set obj.a.b = 42
```
expands roughly to
```julia
l = @lens _.a.b
set(obj, l, 42)
```


================================================
FILE: examples/custom_macros.jl
================================================
# # Extending `@set` and `@lens`
# This code demonstrates how to extend the `@set` and `@lens` mechanism with custom
# lenses.
# As a demo, we want to implement `@mylens!` and `@myset!`, which work much like 
# `@lens` and `@set`, but mutate objects instead of returning modified copies.

using Setfield
using Setfield: IndexLens, PropertyLens, ComposedLens

struct Lens!{L <:Lens} <: Lens
    pure::L
end

Setfield.get(o, l::Lens!) = Setfield.get(o, l.pure)
function Setfield.set(o, l::Lens!{<: ComposedLens}, val)
    o_inner = get(o, l.pure.outer)
    set(o_inner, Lens!(l.pure.inner), val)
end
function Setfield.set(o, l::Lens!{PropertyLens{prop}}, val) where {prop}
    setproperty!(o, prop, val)
    o
end
function Setfield.set(o, l::Lens!{<:IndexLens}, val) where {prop}
    o[l.pure.indices...] = val
    o
end

# Now this implements the kind of `lens` the new macros should use.
# Of course there are more variants like `Lens!(<:DynamicIndexLens)`, for which we might
# want to overload `set`, but lets ignore that. Instead we want to check, that everything works so far:

using Test
mutable struct M
    a
    b
end

o = M(1,2)
l = Lens!(@lens _.b)
set(o, l, 20)
@test o.b == 20

l = Lens!(@lens _.foo[1])
o = (foo=[1,2,3], bar=:bar)
set(o, l, 100)
@test o == (foo=[100,2,3], bar=:bar)

# Now we can implement the syntax macros

using Setfield: setmacro, lensmacro

macro myset!(ex)
    setmacro(Lens!, ex)
end

macro mylens!(ex)
    lensmacro(Lens!, ex)
end

o = M(1,2)
@myset! o.a = :hi
@myset! o.b += 98
@test o.a == :hi
@test o.b == 100

deep = [[[[1]]]]
@myset! deep[1][1][1][1] = 2
@test deep[1][1][1][1] === 2

l = @mylens! _.foo[1]
o = (foo=[1,2,3], bar=:bar)
set(o, l, 100)
@test o == (foo=[100,2,3], bar=:bar)

# Everything works, we can do arbitrary nesting and also use `+=` syntax etc.


================================================
FILE: src/Setfield.jl
================================================
__precompile__(true)
module Setfield
using MacroTools
using MacroTools: isstructdef, splitstructdef, postwalk
using StaticArraysCore

if VERSION < v"1.1-"
    using Future: copy!
end

include("setindex.jl")
include("lens.jl")
include("sugar.jl")
include("functionlenses.jl")

# To correctly dispatch to `show(::IO, ::CustomLens)` when it is defined by a
# user, we avoid defining the generic `show(::IO, ::Lens)`.  This way, we can
# safely call `show` inside `ComposedLens` without worrying about the
# `StackOverflowError` that can be easily triggered in the previous approach.
# See also:
# * https://github.com/jw3126/Setfield.jl/pull/86
# * https://github.com/jw3126/Setfield.jl/pull/88
for n in names(Setfield, all=true)
    T = getproperty(Setfield, n)
    if T isa Type && T <: Lens && (T === ComposedLens || has_atlens_support(T))
        @eval Base.show(io::IO, l::$T) = _show(io, nothing, l)
    end
end
end


================================================
FILE: src/functionlenses.jl
================================================
set(obj, ::typeof(@lens last(_)), val) = @set obj[lastindex(obj)] = val
set(obj, ::typeof(@lens first(_)), val) = @set obj[firstindex(obj)] = val

################################################################################
##### eltype
################################################################################
function set(obj, ::typeof(@lens eltype(_)), ::Type{T}) where {T}
    return set_eltype(obj, T)
end

set_eltype(obj::Array,  T::Type) = collect(T, obj)
set_eltype(obj::Number, T::Type) = T(obj)
set_eltype(::Type{<:Number}, ::Type{T}) where {T} = T
set_eltype(::Type{<:Array{<:Any, N}}, ::Type{T}) where {N, T} = Array{T, N}
set_eltype(::Type{<:Dict}, ::Type{Pair{K, V}}) where {K, V} = Dict{K, V}
set_eltype(obj::Dict, ::Type{T}) where {T} = set_eltype(typeof(obj), T)(obj)

set(obj::Dict, l::Union{typeof(@lens keytype(_)), typeof(@lens valtype(_))},
    T::Type) = set(typeof(obj), l, T)(obj)
set(::Type{<:Dict{<:Any,V}}, ::typeof(@lens keytype(_)), ::Type{K}) where {K, V} =
    Dict{K, V}
set(::Type{<:Dict{K}}, ::typeof(@lens valtype(_)), ::Type{V}) where {K, V} =
    Dict{K, V}


================================================
FILE: src/lens.jl
================================================
export Lens, set, get, modify
export @lens
export set, get, modify
using ConstructionBase
export setproperties
export constructorof


import Base: get, hash, ==
using Base: getproperty


# used for hashing
function make_salt(s64::UInt64)::UInt
    if UInt === UInt64
        return s64
    else
        return UInt32(s64 >> 32) ^ UInt32(s64 & 0x00000000ffffffff)
    end
end


"""
    Lens

A `Lens` allows to access or replace deeply nested parts of complicated objects.

# Example
```jldoctest
julia> using Setfield

julia> struct T;a;b; end

julia> obj = T("AA", "BB")
T("AA", "BB")

julia> lens = @lens _.a
(@lens _.a)

julia> get(obj, lens)
"AA"

julia> set(obj, lens, 2)
T(2, "BB")

julia> obj
T("AA", "BB")

julia> modify(lowercase, obj, lens)
T("aa", "BB")
```

# Interface
Concrete subtypes of `Lens` have to implement
* `set(obj, lens, val)`
* `get(obj, lens)`

These must be pure functions, that satisfy the three lens laws:

```jldoctest; output = false, setup = :(using Setfield; (≅ = (==)); obj = (a="A", b="B"); lens = @lens _.a; val = 2; val1 = 10; val2 = 20)
@assert get(set(obj, lens, val), lens) ≅ val
        # You get what you set.
@assert set(obj, lens, get(obj, lens)) ≅ obj
        # Setting what was already there changes nothing.
@assert set(set(obj, lens, val1), lens, val2) ≅ set(obj, lens, val2)
        # The last set wins.

# output

```
Here `≅` is an appropriate notion of equality or an approximation of it. In most contexts
this is simply `==`. But in some contexts it might be `===`, `≈`, `isequal` or something
else instead. For instance `==` does not work in `Float64` context, because
`get(set(obj, lens, NaN), lens) == NaN` can never hold. Instead `isequal` or
`≅(x::Float64, y::Float64) = isequal(x,y) | x ≈ y` are possible alternatives.

See also [`@lens`](@ref), [`set`](@ref), [`get`](@ref), [`modify`](@ref).
"""
abstract type Lens end

"""
    modify(f, obj, l::Lens)

Replace a deeply nested part `x` of `obj` by `f(x)`. See also [`Lens`](@ref).
"""
function modify end


"""
    get(obj, l::Lens)

Access a deeply nested part of `obj`. See also [`Lens`](@ref).
"""
function get end

"""
    set(obj, l::Lens, val)

Replace a deeply nested part of `obj` by `val`. See also [`Lens`](@ref).
"""
function set end

@inline function modify(f, obj, l::Lens)
    old_val = get(obj, l)
    new_val = f(old_val)
    set(obj, l, new_val)
end

struct IdentityLens <: Lens end
get(obj, ::IdentityLens) = obj
set(obj, ::IdentityLens, val) = val


struct PropertyLens{fieldname} <: Lens end

function get(obj, l::PropertyLens{field}) where {field}
    getproperty(obj, field)
end

@inline function set(obj, l::PropertyLens{field}, val) where {field}
    patch = (;field => val)
    setproperties(obj, patch)
end

struct ComposedLens{LO, LI} <: Lens
    outer::LO
    inner::LI
end

function ==(l1::ComposedLens, l2::ComposedLens)
    return l1.outer == l2.outer && l1.inner == l2.inner
end

const SALT_COMPOSEDLENS = make_salt(0xcf7322dcc2129a31)
hash(l::ComposedLens, h::UInt) = hash(l.outer, hash(l.inner, SALT_INDEXLENS + h))

"""
    compose([lens₁, [lens₂, [lens₃, ...]]])

Compose `lens₁`, `lens₂` etc. There is one subtle point here:
While the two composition orders `(lens₁ ∘ lens₂) ∘ lens₃` and `lens₁ ∘ (lens₂ ∘ lens₃)` have equivalent semantics,
their performance may not be the same. The compiler tends to optimize right associative composition
(second case) better then left associative composition.

The compose function tries to use a composition order, that the compiler likes. The composition order is therefore not part of the stable API.
"""
function compose end
compose() = IdentityLens()
compose(l::Lens) = l
compose(::IdentityLens, ::IdentityLens) = IdentityLens()
compose(::IdentityLens, l::Lens) = l
compose(l::Lens, ::IdentityLens) = l
compose(outer::Lens, inner::Lens) = ComposedLens(outer, inner)
function compose(l1::Lens, ls::Lens...)
    compose(l1, compose(ls...))
end

"""
    lens₁ ∘ lens₂

Compose lenses `lens₁`, `lens₂`, ..., `lensₙ` to access nested objects.

# Example
```jldoctest
julia> using Setfield

julia> obj = (a = (b = (c = 1,),),);

julia> la = @lens _.a
       lb = @lens _.b
       lc = @lens _.c
       lens = la ∘ lb ∘ lc
(@lens _.a.b.c)

julia> get(obj, lens)
1
```
"""
Base.:∘(l1::Lens, l2::Lens) = compose(l1, l2)

function get(obj, l::ComposedLens)
    inner_obj = get(obj, l.outer)
    get(inner_obj, l.inner)
end

function set(obj,l::ComposedLens, val)
    inner_obj = get(obj, l.outer)
    inner_val = set(inner_obj, l.inner, val)
    set(obj, l.outer, inner_val)
end

struct IndexLens{I <: Tuple} <: Lens
    indices::I
end

==(l1::IndexLens, l2::IndexLens) = l1.indices == l2.indices

const SALT_INDEXLENS = make_salt(0x8b4fd6f97c6aeed6)
hash(l::IndexLens, h::UInt) = hash(l.indices, SALT_INDEXLENS + h)

Base.@propagate_inbounds function get(obj, l::IndexLens)
    getindex(obj, l.indices...)
end
Base.@propagate_inbounds function set(obj, l::IndexLens, val)
    setindex(obj, val, l.indices...)
end

struct DynamicIndexLens{F} <: Lens
    f::F
end

Base.@propagate_inbounds get(obj, I::DynamicIndexLens) = obj[I.f(obj)...]

Base.@propagate_inbounds set(obj, I::DynamicIndexLens, val) =
    setindex(obj, val, I.f(obj)...)

"""
    FunctionLens(f)
    @lens f(_)

Lens with [`get`](@ref) method definition that simply calls `f`.
[`set`](@ref) method for each function `f` must be implemented manually.
Use `methods(set, (Any, Setfield.FunctionLens, Any))` to get a list of
supported functions.

Note that `FunctionLens` flips the order of composition; i.e.,
`(@lens f(_)) ∘ (@lens g(_)) == @lens g(f(_))`.

# Example
```jldoctest
julia> using Setfield

julia> obj = ((1, 2), (3, 4));

julia> lens = (@lens first(_)) ∘ (@lens last(_))
(@lens last(first(_)))

julia> get(obj, lens)
2

julia> set(obj, lens, '2')
((1, '2'), (3, 4))
```

# Implementation

To use `myfunction` as a lens, define a `set` method with the following
signature:

```julia
Setfield.set(obj, ::typeof(@lens myfunction(_)), val) = ...
```

`typeof` is used above instead of `FunctionLens` because how actual
type of `@lens myfunction(_)` is implemented is not the part of stable
API.
"""
struct FunctionLens{f} <: Lens end
FunctionLens(f) = FunctionLens{f}()

get(obj, ::FunctionLens{f}) where f = f(obj)


================================================
FILE: src/setindex.jl
================================================
Base.@propagate_inbounds function setindex(args...)
    Base.setindex(args...)
end

Base.@propagate_inbounds function setindex(xs::AbstractArray, v, I...)
    # we need to distinguish between scalar and sliced assignment
    I_normalized = Base.to_indices(xs, I)
    T = promote_type(eltype(xs), I_normalized isa Tuple{Vararg{Integer}} ? typeof(v) : eltype(v))
    ys = similar(xs, T)
    if eltype(xs) !== Union{}
        copy!(ys, xs)
    end
    ys[I_normalized...] = v
    return ys
end

Base.@propagate_inbounds function setindex(d0::AbstractDict, v, k)
    K = promote_type(keytype(d0), typeof(k))
    V = promote_type(valtype(d0), typeof(v))
    d = empty(d0, K, V)
    copy!(d, d0)
    d[k] = v
    return d
end

setindex(a::StaticArraysCore.StaticArray, args...) =
    Base.setindex(a, args...)


================================================
FILE: src/sugar.jl
================================================
export @set, @lens, @set!
using MacroTools

"""
    @set assignment

Return a modified copy of deeply nested objects.

# Example
```jldoctest
julia> using Setfield

julia> struct T;a;b end

julia> t = T(1,2)
T(1, 2)

julia> @set t.a = 5
T(5, 2)

julia> t
T(1, 2)

julia> t = @set t.a = T(2,2)
T(T(2, 2), 2)

julia> @set t.a.b = 3
T(T(2, 3), 2)
```
"""
macro set(ex)
    setmacro(identity, ex, overwrite=false)
end

"""
    @set! assignment

Shortcut for `obj = @set obj...`.

# Example
```jldoctest
julia> using Setfield

julia> t = (a=1,)
(a = 1,)

julia> @set! t.a=2
(a = 2,)

julia> t
(a = 2,)
```
"""
macro set!(ex)
    setmacro(identity, ex, overwrite=true)
end

is_interpolation(x) = x isa Expr && x.head == :$

foldtree(op, init, x) = op(init, x)
foldtree(op, init, ex::Expr) =
    op(foldl((acc, x) -> foldtree(op, acc, x), ex.args; init=init), ex)

const HAS_BEGIN_INDEXING = VERSION ≥ v"1.5.0-DEV.666"

function need_dynamic_lens(ex)
    return foldtree(false, ex) do yes, x
        (yes || x === :end || (HAS_BEGIN_INDEXING && x === :begin) ||
            x == Expr(:end) || (HAS_BEGIN_INDEXING && x == Expr(:begin)) || x === :_)
    end
end

function lower_index(collection::Symbol, index, dim)
    if isexpr(index, :call)
        return Expr(:call, lower_index.(collection, index.args, dim)...)
    elseif (index === :end || index == Expr(:end))
        if dim === nothing
            return :($(Base.lastindex)($collection))
        else
            return :($(Base.lastindex)($collection, $dim))
        end
    elseif HAS_BEGIN_INDEXING && (index === :begin || index == Expr(:begin))
        if dim === nothing
            return :($(Base.firstindex)($collection))
        else
            return :($(Base.firstindex)($collection, $dim))
        end
    end
    return index
end

replace_underscore(ex, to) = postwalk(x -> x === :_ ? to : x, ex)

function parse_obj_lenses_composite(lensexprs::Vector)
    if isempty(lensexprs)
        return esc(:_), ()
    else
        obj, outermostlens = parse_obj_lens(lensexprs[1])
        innerlenses = map(lensexprs[2:end]) do innerex
            o, lens = parse_obj_lens(innerex)
            @assert o == esc(:_)
            lens
        end
        return obj, (outermostlens, innerlenses...)
    end
end

function parse_obj_lenses(ex)
    if @capture(ex, ∘(lensexprs__))
        return parse_obj_lenses_composite(lensexprs)
    elseif is_interpolation(ex)
        @assert length(ex.args) == 1
        return esc(:_), (esc(ex.args[1]),)
    elseif @capture(ex, front_[indices__])
        obj, frontlens = parse_obj_lenses(front)
        if any(need_dynamic_lens, indices)
            @gensym collection
            indices = replace_underscore.(indices, collection)
            dims = length(indices) == 1 ? nothing : 1:length(indices)
            lindices = esc.(lower_index.(collection, indices, dims))
            lens = :($DynamicIndexLens($(esc(collection)) -> ($(lindices...),)))
        else
            index = esc(Expr(:tuple, indices...))
            lens = :($IndexLens($index))
        end
    elseif @capture(ex, front_.property_)
        obj, frontlens = parse_obj_lenses(front)
        if property isa Union{Symbol,String}
            lens = :($PropertyLens{$(QuoteNode(property))}())
        elseif is_interpolation(property)
            lens = :($PropertyLens{$(esc(property.args[1]))}())
        else
            throw(ArgumentError(
                string("Error while parsing :($ex). Second argument to `getproperty` can only be",
                       "a `Symbol` or `String` literal, received `$property` instead.")
            ))
        end
    elseif @capture(ex, f_(front_))
        obj, frontlens = parse_obj_lenses(front)
        lens = :($FunctionLens($(esc(f))))
    else
        obj = esc(ex)
        return obj, ()
    end
    obj, tuple(frontlens..., lens)
end

function parse_obj_lens(ex)
    obj, lenses = parse_obj_lenses(ex)
    lens = Expr(:call, compose, lenses...)
    obj, lens
end

function get_update_op(sym::Symbol)
    s = String(sym)
    if !endswith(s, '=') || isdefined(Base, sym)
        # 'x +=' etc. is actually 'x = x +', and so '+=' isn't defined in Base.
        # '>=' however is a function, and not an assignment operator.
        msg = "Operation $sym doesn't look like an assignment"
        throw(ArgumentError(msg))
    end
    Symbol(s[1:end-1])
end

struct _UpdateOp{OP,V}
    op::OP
    val::V
end
(u::_UpdateOp)(x) = u.op(x, u.val)

"""
    setmacro(lenstransform, ex::Expr; overwrite::Bool=false)

This function can be used to create a customized variant of [`@set`](@ref).
It works by applying `lenstransform` to the lens that is used in the customized `@set` macro
at runtime.
```julia
function mytransform(lens::Lens)::Lens
    ...
end
macro myset(ex)
    setmacro(mytransform, ex)
end
```
See also [`lensmacro`](@ref).
"""
function setmacro(lenstransform, ex::Expr; overwrite::Bool=false)
    @assert ex.head isa Symbol
    @assert length(ex.args) == 2
    ref, val = ex.args
    obj, lens = parse_obj_lens(ref)
    lenssym = gensym(:lens)
    dst = overwrite ? obj : gensym("_")
    val = esc(val)
    ret = if ex.head == :(=)
        quote
            $lenssym = ($lenstransform)($lens)
            $dst = $set($obj, $lenssym, $val)
        end
    else
        op = get_update_op(ex.head)
        f = :($_UpdateOp($op,$val))
        quote
            $lenssym = ($lenstransform)($lens)
            $dst = $modify($f, $obj, $lenssym)
        end
    end
    ret
end

"""
    @lens

Construct a lens from a field access.

# Example

```jldoctest
julia> using Setfield

julia> struct T;a;b;end

julia> t = T("A1", T(T("A3", "B3"), "B2"))
T("A1", T(T("A3", "B3"), "B2"))

julia> l = @lens _.b.a.b
(@lens _.b.a.b)

julia> get(t, l)
"B3"

julia> set(t, l, 100)
T("A1", T(T("A3", 100), "B2"))

julia> t = ("one", "two")
("one", "two")

julia> set(t, (@lens _[1]), "1")
("1", "two")

julia> # Indices are always evaluated in external scope; for properties, you can use interpolation:
       n, i = :a, 10
       @lens(_.\$n[i, i+1])
(@lens _.a[10, 11])
```

"""
macro lens(ex)
    lensmacro(identity, ex)
end


"""
    lensmacro(lenstransform, ex::Expr)

This function can be used to create a customized variant of [`@lens`](@ref).
It works by applying `lenstransform` to the created lens at runtime.
```julia
function mytransform(lens::Lens)::Lens
    ...
end
macro mylens(ex)
    lensmacro(mytransform, ex)
end
```
See also [`setmacro`](@ref).
"""
function lensmacro(lenstransform, ex)
    obj, lens = parse_obj_lens(ex)
    if obj != esc(:_)
        msg = """Cannot parse lens $ex. Lens expressions must start with _, got $obj instead."""
        throw(ArgumentError(msg))
    end
    :($(lenstransform)($lens))
end

has_atlens_support(l::Lens) = has_atlens_support(typeof(l))
has_atlens_support(::Type{<:Lens}) = false
has_atlens_support(::Type{<:Union{PropertyLens, IndexLens, FunctionLens, IdentityLens}}) =
    true
has_atlens_support(::Type{ComposedLens{LO, LI}}) where {LO, LI} =
    has_atlens_support(LO) && has_atlens_support(LI)

print_application(io::IO, l::PropertyLens{field}) where {field} = print(io, ".", field)
print_application(io::IO, l::IndexLens) = print(io, "[", join(repr.(l.indices), ", "), "]")
print_application(io::IO, l::IdentityLens) = print(io, "")

function print_application(io::IO, l::ComposedLens)
    print_application(io, l.outer)
    print_application(io, l.inner)
end

function print_application(printer, io, ::FunctionLens{f}) where f
    print(io, f, '(')
    printer(io)
    print(io, ')')
end

function print_application(printer, io, l)
    @assert has_atlens_support(l)
    printer(io)
    print_application(io, l)
end

function print_application(printer, io, l::ComposedLens)
    print_application(io, l.inner) do io
        print_application(printer, io, l.outer)
    end
end

# Since `show` of `ComposedLens` needs to call `show` of other lenses,
# we explicitly define text/plain `show` for `ComposedLens` to propagate
# the "context" (2-arg or 3-arg `show`) with which `show` has to be called.
# See: https://github.com/jw3126/Setfield.jl/pull/86
Base.show(io::IO, ::MIME"text/plain", l::ComposedLens) =
    _show(io, MIME("text/plain"), l)

function _show(io::IO, mime, l::Lens)
    if has_atlens_support(l)
        print_in_atlens(io, l)
    elseif mime === nothing
        show(io, l)
    else
        show(io, mime, l)
    end
end

function _show(io::IO, mime, l::ComposedLens)
    if has_atlens_support(l)
        print_in_atlens(io, l)
    else
        _show(io, mime, l.outer)
        print(io, " ∘ ")
        _show(io, mime, l.inner)
    end
end

function print_in_atlens(io, l)
    print(io, "(@lens ")
    print_application(io, l) do io
        print(io, '_')
    end
    print(io, ')')
end


================================================
FILE: test/dynamiclens_begin.jl
================================================
l = @lens _[begin]
@test l isa Setfield.DynamicIndexLens
obj = (1,2,3)
@test get(obj, l) == 1
@test set(obj, l, true) == (true,2,3)

l = @lens _[2*begin]
@test l isa Setfield.DynamicIndexLens
obj = (1,2,3)
@test get(obj, l) == 2
@test set(obj, l, true) == (1,true,3)

one = 1
plustwo(x) = x + 2
l = @lens _.a[plustwo(begin) - one].b
obj = (a=(1, (a=10, b=20), 3), b=4)
@test get(obj, l) == 20
@test set(obj, l, true) == (a=(1, (a=10, b=true), 3), b=4)


================================================
FILE: test/perf.jl
================================================
module Perf
using BenchmarkTools
using BenchmarkTools: Benchmark, TrialEstimate
using Setfield
using Test
using InteractiveUtils
using StaticArrays

struct AB{A,B}
    a::A
    b::B
end

function lens_set_a((obj, val))
    @set obj.a = val
end

function hand_set_a((obj, val))
    AB(val, obj.b)
end

function lens_set_ab((obj, val))
    @set obj.a.b = val
end

function hand_set_ab((obj, val))
    a = AB(obj.a.a, val)
    AB(a, obj.b)
end

function lens_set_a_and_b((obj, val))
    o1 = @set obj.a = val
    o2 = @set o1.b = val
end

function hand_set_a_and_b((obj, val))
    AB(val, val)
end

function lens_set_i((obj, val, i))
    @inbounds (@set obj[i] = val)
end

function hand_set_i((obj, val, i))
    @inbounds Base.setindex(obj, val, i)
end

function benchmark_lens_vs_hand(b_lens::Benchmark, b_hand::Benchmark)

    te_hand = minimum(run(b_lens))
    te_lens = minimum(run(b_hand))
    @show te_lens
    @show te_hand
    @test te_lens.memory == te_hand.memory
    @test te_lens.allocs == te_hand.allocs
    @test te_lens.time <= 2*te_hand.time
end

function uniquecounts(iter)
    ret = Dict{eltype(iter), Int}()
    for x in iter
        ret[x] = get!(ret, x, 0) + 1
    end
    ret
end

function test_ir_lens_vs_hand(info_lens::Core.CodeInfo,
                              info_hand::Core.CodeInfo)

    heads(info) = [ex.head for ex in info.code if ex isa Expr]

    # test no needless kinds of operations
    heads_lens = heads(info_lens)
    heads_hand = heads(info_hand)
    @test Set(heads_lens) == Set(heads_hand)

    # test no intermediate objects or lenses
    @test count(==(:new), heads_lens) == count(==(:new), heads_hand)

    # this test might be too strict
    @test uniquecounts(heads_lens) == uniquecounts(heads_hand)
end

let
    obj = AB(AB(1,2), :b)
    val = (1,2)
    @testset "$(setup.lens)" for setup in [
            (lens=lens_set_a,           hand=hand_set_a,       args=(obj, val)),
            (lens=lens_set_a,           hand=hand_set_a,       args=(obj, val)),
            (lens=lens_set_ab,          hand=hand_set_ab,      args=(obj, val)),
            (lens=lens_set_a_and_b,     hand=hand_set_a_and_b, args=(obj, val)),
            (lens=lens_set_i,           hand=hand_set_i,
             args=(@SVector[1,2], 10, 1))
            ]
        f_lens = setup.lens
        f_hand = setup.hand
        args = setup.args

        @assert f_hand(args) == f_lens(args)

        @testset "IR" begin
            info_lens, _ = @code_typed f_lens(args)
            info_hand, _ = @code_typed f_hand(args)
            test_ir_lens_vs_hand(info_lens, info_hand)
        end

        @testset "benchmark" begin
            b_lens = @benchmarkable $f_lens($args)
            b_hand = @benchmarkable $f_hand($args)
            benchmark_lens_vs_hand(b_lens, b_hand)
        end
    end
end

function compose_left_assoc(obj, val)
    l = @lens ((_.a∘_.b)∘_.c)∘_.d
    set(obj, l, val)
end

function compose_right_assoc(obj, val)
    l = @lens _.a∘(_.b∘(_.c∘_.d))
    set(obj, l, val)
end
function compose_default_assoc(obj, val)
    l = @lens _.a.b.c.d
    set(obj, l, val)
end
@testset "Lens composition compiler prefered associativity" begin

    obj = (a=(b=(c=(d=1,d2=2),c2=2),b2=3), a2=2)
    val = 2.2
    @test compose_left_assoc(obj, val) == compose_default_assoc(obj, val)
    @test compose_right_assoc(obj, val) == compose_default_assoc(obj, val)

    b_default = minimum(@benchmark compose_default_assoc($obj, $val))
    println("Default associative composition: $b_default")
    b_left    = minimum(@benchmark compose_left_assoc($obj, $val)   )
    println("Left associative composition: $b_left")
    b_right   = minimum(@benchmark compose_right_assoc($obj, $val)  )
    println("Right associative composition: $b_right")

    @test b_default.allocs == 0
    @test b_right.allocs == 0
    @test_broken b_left.allocs == 0

    @test b_left.time > 2b_default.time
    @test b_right.time ≈ b_default.time rtol=0.8
end

end


================================================
FILE: test/runtests.jl
================================================
module TestSetfield

import PerformanceTestTools
import Setfield
using Documenter: doctest

include("test_setindex.jl")
include("test_examples.jl")
include("test_setmacro.jl")
include("test_core.jl")
include("test_functionlenses.jl")
include("test_staticarrays.jl")
include("test_quicktypes.jl")
PerformanceTestTools.@include("perf.jl")

doctest(Setfield)

end  # module


================================================
FILE: test/test_core.jl
================================================
module TestCore
using Test
using Setfield
using Setfield: compose, get_update_op
using ConstructionBase: ConstructionBase
using StaticNumbers: static

struct T
    a
    b
end

struct TT{A,B}
    a::A
    b::B
end

@testset "get_update_op" begin
    @test get_update_op(:(&=)) === :(&)
    @test get_update_op(:(^=)) === :(^)
    @test get_update_op(:(-=)) === :(-)
    @test get_update_op(:(%=)) === :(%)
    @test_throws ArgumentError get_update_op(:(++))
    @test_throws ArgumentError get_update_op(:(<=))
end

@testset "@set!" begin
    a = 1
    @set a = 2
    @test a === 1
    @set! a = 2
    @test a === 2

    t = T(1, T(2,3))
    @set t.b.a = 20
    @test t === T(1, T(2,3))

    @set! t.b.a = 20
    @test t === T(1,T(20,3))

    a = 1
    @set! a += 10
    @test a === 11
    nt = (a=1,)
    @set! nt.a = 5
    @test nt === (a=5,)
end

@testset "@set" begin

    t = T(1, T(2, T(T(4,4),3)))
    s = @set t.b.b.a.a = 5
    @test t === T(1, T(2, T(T(4,4),3)))
    @test s === T(1, T(2, T(T(5, 4), 3)))
    @test_throws ArgumentError @set t.b.b.a.a.a = 3

    t = T(1,2)
    @test T(1, T(1,2)) === @set t.b = T(1,2)
    @test_throws ArgumentError @set t.c = 3

    t = T(T(2,2), 1)
    s = @set t.a.a = 3
    @test s === T(T(3, 2), 1)

    t = T(1, T(2, T(T(4,4),3)))
    s = @set t.b.b = 4
    @test s === T(1, T(2, 4))

    t = T(1,2)
    s = @set t.a += 1
    @test s === T(2,2)

    t = T(1,2)
    s = @set t.b -= 2
    @test s === T(1,0)

    t = T(10, 20)
    s = @set t.a *= 10
    @test s === T(100, 20)

    t = T(2,1)
    s = @set t.a /= 2
    @test s === T(1.0,1)

    t = T(1, 2)
    s = @set t.a <<= 2
    @test s === T(4, 2)

    t = T(8, 2)
    s = @set t.a >>= 2
    @test s === T(2, 2)

    t = T(1, 2)
    s = @set t.a &= 0
    @test s === T(0, 2)

    t = T(1, 2)
    s = @set t.a |= 2
    @test s === T(3, 2)

    t = T((1,2),(3,4))
    @set t.a[1] = 10
    s1 = @set t.a[1] = 10
    @test s1 === T((10,2),(3,4))
    i = 1
    si = @set t.a[i] = 10
    @test s1 === si
    se = @set t.a[end] = 20
    @test se === T((1,20),(3,4))
    se1 = @set t.a[end-1] = 10
    @test s1 === se1

    s1 = @set t.a[static(1)] = 10
    @test s1 === T((10,2),(3,4))
    i = 1
    si = @set t.a[static(i)] = 10
    @test s1 === si

    t = @set T(1,2).a = 2
    @test t === T(2,2)

    t = (1, 2, 3, 4)
    @test (@set t[length(t)] = 40) === (1, 2, 3, 40)
    @test (@set t[length(t) ÷ 2] = 20) === (1, 20, 3, 4)
end


struct UserDefinedLens <: Lens end

struct LensWithTextPlain <: Lens end
Base.show(io::IO, ::MIME"text/plain", ::LensWithTextPlain) =
    print(io, "I define text/plain.")


@testset "show it like you build it " begin
    i = 3
    @testset for item in [
            @lens _.a
            @lens _[1]
            @lens _[:a]
            @lens _["a"]
            @lens _[static(1)]
            @lens _[static(1), static(1 + 1)]
            @lens _.a.b[:c]["d"][2][static(3)]
            @lens _
            @lens first(_)
            @lens last(first(_))
            @lens last(first(_.a))[1]
            UserDefinedLens()
            (@lens _.a) ∘ UserDefinedLens()
            UserDefinedLens() ∘ (@lens _.b)
            (@lens _.a) ∘ UserDefinedLens() ∘ (@lens _.b)
            (@lens _.a) ∘ LensWithTextPlain() ∘ (@lens _.b)
        ]
        buf = IOBuffer()
        show(buf, item)
        item2 = eval(Meta.parse(String(take!(buf))))
        @test item === item2
    end
end

function test_getset_laws(lens, obj, val1, val2)

    # set ∘ get
    val = get(obj, lens)
    @test set(obj, lens, val) == obj

    # get ∘ set
    obj1 = set(obj, lens, val1)
    @test get(obj1, lens) == val1

    # set idempotent
    obj12 = set(obj1, lens, val2)
    obj2 = set(obj, lens, val2)
    @test obj12 == obj2
end

function test_modify_law(f, lens, obj)
    obj_modify = modify(f, obj, lens)
    old_val = get(obj, lens)
    val = f(old_val)
    obj_setfget = set(obj, lens, val)
    @test obj_modify == obj_setfget
end

@testset "lens laws" begin
    obj = T(2, T(T(3,(4,4)), 2))
    i = 2
    for lens ∈ [
            @lens _.a
            @lens _.b
            @lens _.b.a
            @lens _.b.a.b[2]
            @lens _.b.a.b[i]
            @lens _.b.a.b[static(2)]
            @lens _.b.a.b[static(i)]
            @lens _.b.a.b[end]
            @lens _.b.a.b[identity(end) - 1]
            @lens _
        ]
        val1, val2 = randn(2)
        f(x) = (x,x)
        test_getset_laws(lens, obj, val1, val2)
        test_modify_law(f, lens, obj)
    end
end

@testset "equality & hashing" begin
    # singletons (identity and property lens) are egal
    for (l1, l2) ∈ [
        @lens(_) => @lens(_),
        @lens(_.a) => @lens(_.a),
    ]
        @test l1 === l2
        @test l1 == l2
        @test hash(l1) == hash(l2)
    end

    # composite and index lenses are structurally equal
    for (l1, l2) ∈ [
        @lens(_[1]) => @lens(_[1]),
        @lens(_.a[2]) => @lens(_.a[2]),
        @lens(_.a.b[3]) => @lens(_.a.b[3]),
        @lens(_[1:10]) => @lens(_[1:10]),
        @lens(_.a[2:20]) => @lens(_.a[2:20]),
        @lens(_.a.b[3:30]) => @lens(_.a.b[3:30]),
    ]
        @test l1 == l2
        @test hash(l1) == hash(l2)
    end

    # inequality
    for (l1, l2) ∈ [
        @lens(_[1]) => @lens(_[2]),
        @lens(_.a[1]) => @lens(_.a[2]),
        @lens(_.a[1]) => @lens(_.b[1]),
        @lens(_[1:10]) => @lens(_[2:20]),
        @lens(_.a[1:10]) => @lens(_.a[2:20]),
        @lens(_.a[1:10]) => @lens(_.b[1:10]),
    ]
        @test l1 != l2
    end

    # equality with non-equal range types (#165)
    for (l1, l2) ∈ [
        @lens(_[1:10]) => @lens(_[Base.OneTo(10)]),
        @lens(_.a[1:10]) => @lens(_.a[Base.OneTo(10)]),
        @lens(_.a.b[1:10]) => @lens(_.a.b[Base.OneTo(10)]),
        @lens(_.a[Base.StepRange(1, 1, 5)].b[1:10]) => @lens(_.a[1:5].b[Base.OneTo(10)]),
        @lens(_.a.b[1:3]) => @lens(_.a.b[[1, 2, 3]]),
    ]
        @test l1 == l2
        @test hash(l1) == hash(l2)
    end

    # Hash property: equality implies equal hashes, or in other terms:
    # lenses either have equal hashes or are unequal
    # Because collisions can occur theoretically (though unlikely), this is a property test,
    # not a unit test.
    random_lenses = (@lens(_.a[rand(Int)]) for _ in 1:1000)
    @test all((hash(l2) == hash(l1)) || (l1 != l2)
              for (l1, l2) in zip(random_lenses, random_lenses))

    # Lenses should hash differently from the underlying tuples, to avoid confusion.
    # To account for potential collisions, we check that the property holds with high
    # probability.  
    @test count(hash(@lens(_[i])) != hash((i,)) for i = 1:1000) > 900

    # Same for tuples of tuples (√(1000) ≈ 32).
    @test count(hash(@lens(_[i][j])) != hash(((i,), (j,))) for i = 1:32, j = 1:32) > 900
end


@testset "type stability" begin
    o1 = 2
    o22 = 2
    o212 = (4,4)
    o211 = 3
    o21 = TT(o211, o212)
    o2 = TT(o21, o22)
    obj = TT(o1, o2)
    @assert obj === TT(2, TT(TT(3,(4,4)), 2))
    i = 1
    for (lens, val) ∈ [
          ((@lens _.a           ),   o1 ),
          ((@lens _.b           ),   o2 ),
          ((@lens _.b.a         ),   o21),
          ((@lens _.b.a.b[2]    ),   4  ),
          ((@lens _.b.a.b[i+1]  ),   4  ),
          ((@lens _.b.a.b[static(2)]   ),   4  ),
          ((@lens _.b.a.b[static((i+1))]),  4  ),
          ((@lens _.b.a.b[static(2)]   ),   4.0),
          ((@lens _.b.a.b[static((i+1))]),  4.0),
          ((@lens _.b.a.b[end]),     4.0),
          ((@lens _.b.a.b[end÷2+1]), 4.0),
          ((@lens _             ),   obj),
          ((@lens _             ),   :xy),
        ]
        @inferred get(obj, lens)
        @inferred set(obj, lens, val)
        @inferred modify(identity, obj, lens)
    end
end

@testset "IndexLens" begin
    l = @lens _[]
    @test l isa Setfield.IndexLens
    x = randn()
    obj = Ref(x)
    @test get(obj, l) == x

    l = @lens _[][]
    @test l.outer isa Setfield.IndexLens
    @test l.inner isa Setfield.IndexLens
    inner = Ref(x)
    obj = Base.RefValue{typeof(inner)}(inner)
    @test get(obj, l) == x

    obj = (1,2,3)
    l = @lens _[1]
    @test l isa Setfield.IndexLens
    @test get(obj, l) == 1
    @test set(obj, l, 6) == (6,2,3)


    l = @lens _[1:3]
    @test l isa Setfield.IndexLens
    @test get([4,5,6,7], l) == [4,5,6]
end

@testset "DynamicIndexLens" begin
    l = @lens _[end]
    @test l isa Setfield.DynamicIndexLens
    obj = (1,2,3)
    @test get(obj, l) == 3
    @test set(obj, l, true) == (1,2,true)

    l = @lens _[end÷2]
    @test l isa Setfield.DynamicIndexLens
    obj = (1,2,3)
    @test get(obj, l) == 1
    @test set(obj, l, true) == (true,2,3)

    two = 2
    plusone(x) = x + 1
    l = @lens _.a[plusone(end) - two].b
    obj = (a=(1, (a=10, b=20), 3), b=4)
    @test get(obj, l) == 20
    @test set(obj, l, true) == (a=(1, (a=10, b=true), 3), b=4)

    if Setfield.HAS_BEGIN_INDEXING
        # Need to keep this in a separate file since `begin` won't parse
        # on older Julia versions.
        include("dynamiclens_begin.jl")
    end
end

@testset "StaticNumbers" begin
    obj = (1, 2.0, '3')
    l = @lens _[static(1)]
    @test (@inferred get(obj, l)) === 1
    @test (@inferred set(obj, l, 6.0)) === (6.0, 2.0, '3')
    l = @lens _[static(1 + 1)]
    @test (@inferred get(obj, l)) === 2.0
    @test (@inferred set(obj, l, 6)) === (1, 6, '3')
    n = 1
    l = @lens _[static(3n)]
    @test (@inferred get(obj, l)) === '3'
    @test (@inferred set(obj, l, 6)) === (1, 2.0, 6)

    l = @lens _[static(1):static(3)]
    @test get([4,5,6,7], l) == [4,5,6]

    @testset "complex example (sweeper)" begin
        sweeper_with_const = (
            model = (1, 2.0, 3im),
            axis = (@lens _[static(2)]),
        )

        sweeper_with_noconst = @set sweeper_with_const.axis = @lens _[2]

        function f(s)
            a = sum(set(s.model, s.axis, 0))
            for i in 1:10
                a += sum(set(s.model, s.axis, i))
            end
            return a
        end

        @test (@inferred f(sweeper_with_const)) == 66 + 33im
        @test_broken (@inferred f(sweeper_with_noconst)) == 66 + 33im
    end
end

mutable struct M
    a
    b
end

@testset "IdentityLens" begin
    id = @lens _
    @test compose(id, id) === id
    obj1 = M(1,1)
    obj2 = M(2,2)
    @test obj2 === set(obj1, id, obj2)
    la = @lens _.a
    @test compose(id, la) === la
    @test compose(la, id) === la
end

struct ABC{A,B,C}
    a::A
    b::B
    c::C
end

@testset "type change during @set (default constructorof)" begin
    obj = TT(2,3)
    obj2 = @set obj.b = :three
    @test obj2 === TT(2, :three)
end

# https://github.com/tkf/Reconstructables.jl#how-to-use-type-parameters
struct B{T, X, Y}
    x::X
    y::Y
    B{T}(x::X, y::Y = 2) where {T, X, Y} = new{T, X, Y}(x, y)
end
ConstructionBase.constructorof(::Type{<: B{T}}) where T = B{T}

@testset "type change during @set (custom constructorof)" begin
    obj = B{1}(2,3)
    obj2 = @set obj.y = :three
    @test obj2 === B{1}(2, :three)
end

@testset "text/plain show" begin
    @testset for lens in [
        LensWithTextPlain()
        (@lens _.a) ∘ LensWithTextPlain()
        LensWithTextPlain() ∘ (@lens _.b)
        (@lens _.a) ∘ LensWithTextPlain() ∘ (@lens _.b)
    ]
        @test occursin("I define text/plain.", sprint(show, "text/plain", lens))
    end

    @testset for lens in [
        UserDefinedLens()
        (@lens _.a) ∘ UserDefinedLens()
        UserDefinedLens() ∘ (@lens _.b)
        (@lens _.a) ∘ UserDefinedLens() ∘ (@lens _.b)
    ]
        @test sprint(show, lens) == sprint(show, "text/plain", lens)
    end
end

@testset "Named Tuples" begin
    t = (x=1, y=2)
    @test (@set t.x =2) === (x=2, y=2)
    @test (@set t.x += 2) === (x=3, y=2)
    @test (@set t.x =:hello) === (x=:hello, y=2)
    l = @lens _.x
    @test get(t, l) === 1

    # do we want this to throw an error?
    @test_throws ArgumentError (@set t.z = 3)
end

struct CustomProperties
    _a
    _b
end

function ConstructionBase.setproperties(o::CustomProperties, patch::NamedTuple)
    CustomProperties(get(patch, :a, getfield(o, :_a)),
                     get(patch, :b, getfield(o, :_b)))

end

ConstructionBase.constructorof(::Type{CustomProperties}) = error()

@testset "setproperties overloading" begin
    o = CustomProperties("A", "B")
    o2 = @set o.a = :A
    @test o2 == CustomProperties(:A, "B")
    o3 = @set o.b = :B
    @test o3 == CustomProperties("A", :B)
end

@testset "issue #83" begin
    @test_throws ArgumentError Setfield.lensmacro(identity, :(_.[:a]))
end

@testset "@lens and ∘" begin
    @test @lens(∘()) === @lens(_)
    @test @lens(∘(_.a)) === @lens(_.a)
    @test @lens(∘(_.a, _.b)) === @lens(_.a) ∘ @lens(_.b)
    @test @lens(∘(_.a, _.b, _.c)) === Setfield.compose(@lens(_.a), @lens(_.b), @lens(_.c))

    @test @lens(∘(_[1])) === @lens(_[1])
    @test @lens(∘(_[1], _[2])) === @lens(_[1]) ∘ @lens(_[2])
    @test @lens(∘(_[1], _[2], _[3])) === Setfield.compose(@lens(_[1]), @lens(_[2]), @lens(_[3]))

    @test @lens(_ ∘ (_[1] ∘ _.a) ∘ first(_)) == @lens(_) ∘ (@lens(_[1]) ∘ @lens(_.a)) ∘ @lens(first(_))
end

@testset "@lens ∘ and \$" begin
    lbc = @lens _.b.c
    @test @lens($lbc)== lbc
    @test @lens(_.a ∘ $lbc) == @lens(_.a) ∘ lbc
    @test @lens(_.a ∘ $lbc ∘ _[1] ∘ $lbc) == @lens(_.a) ∘ lbc ∘ @lens(_[1]) ∘ lbc

    # property interpolation
    name = :a
    fancy(name, suffix) = Symbol("fancy_", name, suffix)
    @test @lens(_.$name) == @lens(_.a)
    @test @lens(_.x[1, :].$name) == @lens(_.x[1, :].a)
    @test @lens(_.x[1, :].$(fancy(name, "✨"))) == @lens(_.x[1, :].fancy_a✨)
end

end


================================================
FILE: test/test_examples.jl
================================================
module TestExamples
using Test
dir = joinpath("..", "examples")
@testset "example $filename" for filename in readdir(dir)
    path = joinpath(dir, filename)
    include(path)
end
end#module


================================================
FILE: test/test_functionlenses.jl
================================================
module TestFunctionLenses
using Test
using Setfield

@testset "first" begin
    obj = (1, 2.0, '3')
    l = @lens first(_)
    @test get(obj, l) === 1
    @test set(obj, l, "1") === ("1", 2.0, '3')
    @test (@set first(obj) = "1") === ("1", 2.0, '3')

    obj2 = (a=((b=1,), 2), c=3)
    @test (@set first(obj2.a).b = '1') === (a=((b='1',), 2), c=3)
end

@testset "last" begin
    obj = (1, 2.0, '3')
    l = @lens last(_)
    @test get(obj, l) === '3'
    @test set(obj, l, '4') === (1, 2.0, '4')
    @test (@set last(obj) = '4') === (1, 2.0, '4')

    obj2 = (a=(1, (b=2,)), c=3)
    @test (@set last(obj2.a).b = '2') === (a=(1, (b='2',)), c=3)
end

@testset "eltype on Number" begin
    @test @set(eltype(Int) = Float32) === Float32
    @test @set(eltype(1.0) = UInt8)   === UInt8(1)

    @inferred set(Int, @lens(eltype(_)), Float32)
    @inferred set(1.2, @lens(eltype(_)), Float32)

end

@testset "eltype(::Type{<:Array})" begin
    obj = Vector{Int}
    @inferred set(obj, @lens(eltype(_)), Float32)
    obj2 = @set eltype(obj) = Float64
    @test obj2 === Vector{Float64}
end

@testset "eltype(::Array)" begin
    obj = [1, 2, 3]
    @inferred set(obj, @lens(eltype(_)), Float32)
    obj2 = @set eltype(obj) = Float64
    @test eltype(obj2) == Float64
    @test obj == obj2
end

@testset "(key|val|el)type(::Type{<:Dict})" begin
    obj = Dict{Symbol, Int}
    @test (@set keytype(obj) = String) === Dict{String, Int}
    @test (@set valtype(obj) = String) === Dict{Symbol, String}
    @test (@set eltype(obj) = Pair{String, Any}) === Dict{String, Any}

    obj2 = Dict{Symbol, Dict{Int, Float64}}
    @test (@set keytype(valtype(obj2)) = String) === Dict{Symbol, Dict{String, Float64}}
    @test (@set valtype(valtype(obj2)) = String) === Dict{Symbol, Dict{Int, String}}
end

@testset "(key|val|el)type(::Dict)" begin
    obj = Dict(1 => 2)
    @test typeof(@set keytype(obj) = Float64) === Dict{Float64, Int}
    @test typeof(@set valtype(obj) = Float64) === Dict{Int, Float64}
    @test typeof(@set eltype(obj) = Pair{UInt, Float64}) === Dict{UInt, Float64}
end

end  # module


================================================
FILE: test/test_quicktypes.jl
================================================
module TestQuicktypes
using Test

import Base: ==
import MacroTools

using QuickTypes
using Setfield
import ConstructionBase

# this is a limitation in `MacroTools.splitarg`. If it is fixed
# this test can be removed and our custom splitarg removed.
try
    MacroTools.splitarg(:(x=$nothing))
    println("MacroTools.splitarg can now handle literal nothing in AST.
            Revisit splitarg_no_default workaround")
catch e
    @assert e isa AssertionError
end

# Examples are taken from https://github.com/cstjean/QuickTypes.jl
# Strings are replaced with symbols so that `===` and `==` works
# nicely.

@qstruct Wall(width, height)

@testset "Wall" begin
    x0 = Wall(400, 600)
    x1 = @set x0.width = 300
    @test x1 === Wall(300, 600)
end

abstract type Vehicle end

@qstruct Car{T<:Number, U}(size::T, nwheels::Int=4; manufacturer::U=nothing,
                           brand::String="off-brand") <: Vehicle

@testset "Car" begin
    c = Car(10; manufacturer=("Danone", "Hershey"))
    @test c isa Car
    @test Car <: Vehicle
    c2 = @set c.size = 10
    @test c2.size === 10
    c3 = @set c.manufacturer = 100
    @test c3 === Car(10;manufacturer =100)
end

@qstruct Empty()
@qstruct Cat(name, age::Int, nlegs=4; species=:Siamese)

@testset "Cat" begin
    x0 = Cat(:Tama, 1)


    x1 = @set x0.nlegs = 8
    @test x1 === Cat(:Tama, 1, 8)

    x2 = @set x0.species = :Singapura
    @test x2 === Cat(:Tama, 1, species=:Singapura)
end


@qstruct Pack{T, N}(animals::NTuple{N, T})

@testset "Pack" begin
    x = Pack((Cat(:Tama, 1), Cat(:Pochi, 2)))

    x = @set x.animals[2].nlegs = 5
    @test x.animals == (Cat(:Tama, 1), Cat(:Pochi, 2, 5))
end


abstract type Tree end
@qstruct Maple(qty_syrup::Float64) <: Tree

@testset "Maple" begin
    x0 = Maple(1)
    x1 = @set x0.qty_syrup = 2
    @test x1 === Maple(2)
end


@qmutable Window(height::Float64, width::Float64)
==(x::Window, y::Window) = x.height == y.height && x.width == y.width

@testset "Window" begin
    x0 = Window(1, 2)

    x1 = @set x0.width = 3.0
    @test isequal(x1, Window(1, 3))
    @test x1 == Window(1, 3)
    x2 = @set x0.width = 3
    @test x1 == x2
    @test !(x1 === x2)
end


@qstruct Human(; name=:Alice, height::Float64=170) do
    @assert height > 0    # arbitrary code, executed in the constructor
end

@testset "Human" begin
    x0 = Human()

    x1 = @set x0.name = :Bob
    @test x1 === Human(name=:Bob)

    @test_throws AssertionError @set x0.height = -10
end


@qstruct Group{x}(members::x; _concise_show=true)

@testset "Group" begin
    x = Group((0, 1))
    x = @set x.members[2] = 111
    @test x.members == (0, 111)
end

@qstruct_fp Plane1(nwheels, weight::Number; brand=:zoomba)

@testset "Plane1" begin
    x0 = Plane1(3, 100)
    x1 = @set x0.nwheels = 5
    @test x1 == Plane1(5, 100)
    @test (@set x0.brand = 31).brand === 31
end

# Another way to "support" QuickTypes with type parameters is to use
# QuickTypes.construct.
@qstruct_fp Plane2(nwheels, weight::Number; brand=:zoomba)
ConstructionBase.constructorof(::Type{<: Plane2}) =
    (args...) -> QuickTypes.construct(Plane2, args...)

@testset "Plane2" begin
    x0 = Plane2(3, 100)

    x1 = @set x0.brand = 31
    @test typeof(x1) != typeof(x0)
    @test x1 == Plane2(3, 100, brand=31)
end

end  # module


================================================
FILE: test/test_setindex.jl
================================================
module TestSetindex
using Setfield
using Test

"""
    ==ₜ(x, y)

Check that _type_ and value of `x` and `y` are equal.
"""
==ₜ(_, _) = false
==ₜ(x::T, y::T) where T = x == y

@testset "==ₜ" begin
    @test 1 ==ₜ 1
    @test !(1.0 ==ₜ 1)
end

@testset "setindex" begin
    arr = [1,2,3]
    @test_throws MethodError Base.setindex(arr, 10, 1)
    @test Setfield.setindex(arr, 10, 1) == [10, 2, 3]
    @test arr == [1,2,3]
    @test @set(arr[1] = 10) == [10, 2, 3]
    @test arr == [1,2,3]
    @test Setfield.setindex(arr, 10.0, 1) ==ₜ Float64[10.0, 2.0, 3.0]
    @test Setfield.setindex(ones(2, 2), zeros(2), 1, :) ==ₜ Float64[0.0 0.0; 1.0 1.0]
    @test Setfield.setindex(ones(BigInt, 2, 2), zeros(Float32, 2), 1, :) ==ₜ BigFloat[0.0 0.0; 1.0 1.0]
    @test Setfield.setindex(fill(ones(1), 2, 2), [im, im], :, 1) ==ₜ hcat([im, im], [[1.0], [1.0]])

    d = Dict(:a => 1, :b => 2)
    @test_throws MethodError Base.setindex(d, 10, :a)
    @test Setfield.setindex(d, 10, :a) == Dict(:a=>10, :b=>2)
    @test d == Dict(:a => 1, :b => 2)
    @test @set(d[:a] = 10) == Dict(:a=>10, :b=>2)
    @test d == Dict(:a => 1, :b => 2)
    @test Setfield.setindex(d, 30, "c") ==ₜ Dict(:a=>1, :b=>2, "c"=>30)
    @test Setfield.setindex(d, 10.0, :a) ==ₜ Dict(:a=>10.0, :b=>2.0)
end

end


================================================
FILE: test/test_setmacro.jl
================================================
module TestSetMacro

module Clone
using Setfield: setmacro, lensmacro

macro lens(ex)
    lensmacro(identity, ex)
end

macro set(ex)
    setmacro(identity, ex)
end

end#module Clone

using Setfield: Setfield
using Test
using .Clone: Clone

using StaticArrays: @SMatrix
using StaticNumbers

@testset "setmacro, lensmacro isolation" begin

    # test that no symbols like `IndexLens` are needed:
    @test Clone.@lens(_                                   ) isa Setfield.Lens
    @test Clone.@lens(_.a                                 ) isa Setfield.Lens
    @test Clone.@lens(_[1]                                ) isa Setfield.Lens
    @test Clone.@lens(first(_)                            ) isa Setfield.Lens
    @test Clone.@lens(_[end]                              ) isa Setfield.Lens
    @test Clone.@lens(_[static(1)]                           ) isa Setfield.Lens
    @test Clone.@lens(_.a[1][end, end-2].b[static(1), static(1)]) isa Setfield.Lens

    @test Setfield.@lens(_.a) === Clone.@lens(_.a)
    @test Setfield.@lens(_.a.b) === Clone.@lens(_.a.b)
    @test Setfield.@lens(_.a.b[1,2]) === Clone.@lens(_.a.b[1,2])

    o = (a=1, b=2)
    @test Clone.@set(o.a = 2) === Setfield.@set(o.a = 2)
    @test Clone.@set(o.a += 2) === Setfield.@set(o.a += 2)

    m = @SMatrix [0 0; 0 0]
    m2 = Clone.@set m[end-1, end] = 1
    @test m2 === @SMatrix [0 1; 0 0]
    m3 = Clone.@set(first(m) = 1)
    @test m3 === @SMatrix[1 0; 0 0]
end

function test_all_inferrable(f, argtypes)
    typed = first(code_typed(f, argtypes))
    code = typed.first
    @test all(T -> !(T isa UnionAll || T === Any), code.slottypes)
end

# Example of macro that caused inference issues before.
macro test_macro(expr)
    quote
        function f($(esc(:x)))
            $(Setfield.setmacro(identity, expr, overwrite=true))
            $(Setfield.setmacro(identity, expr, overwrite=true))
            $(Setfield.setmacro(identity, expr, overwrite=true))
            $(Setfield.setmacro(identity, expr, overwrite=true))
            $(Setfield.setmacro(identity, expr, overwrite=true))
            return $(esc(:x))
        end
    end
end

if VERSION >= v"1.3"
    @testset "setmacro multiple usage" begin
        let f = @test_macro(x[end] = 1)
            test_all_inferrable(f, (Vector{Float64}, ))
        end
    end
end

end#module



================================================
FILE: test/test_staticarrays.jl
================================================
module TestStaticArrays
using Test
using Setfield
using StaticArrays
using StaticNumbers

@testset "StaticArrays" begin
    obj = StaticArrays.@SMatrix [1 2; 3 4]
    @testset for l in [
            (@lens _[2,1]),
        ]
        @test get(obj, l) == 3
        @test set(obj, l, 5) == StaticArrays.@SMatrix [1 2; 5 4]
        @test setindex(obj, 5, 2, 1) == StaticArrays.@SMatrix [1 2; 5 4]
    end

    v = @SVector [1,2,3]
    @test (@set v[1] = 10) === @SVector [10,2,3]
    @test_broken (@set v[1] = π) === @SVector [π,2,3]

    @testset "Multi-dynamic indexing" begin
        two = 2
        plusone(x) = x + 1
        l1 = @lens _.a[2, 1].b
        l2 = @lens _.a[plusone(end) - two, end÷2].b
        m_orig = @SMatrix [
            (a=1, b=10) (a=2, b=20)
            (a=3, b=30) (a=4, b=40)
            (a=5, b=50) (a=6, b=60)
        ]
        m_mod = @SMatrix [
            (a=1, b=10) (a=2, b=20)
            (a=3, b=3000) (a=4, b=40)
            (a=5, b=50) (a=6, b=60)
        ]
        obj = (a=m_orig, b=4)
        @test get(obj, l1) === get(obj, l2) === 30
        @test set(obj, l1, 3000) === set(obj, l2, 3000) === (a=m_mod, b=4)
    end
end
end
Download .txt
gitextract_z43yv95v/

├── .codecov.yml
├── .github/
│   └── workflows/
│       ├── CI.yml
│       ├── CompatHelper.yml
│       ├── Invalidations.yml
│       └── TagBot.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── appveyor.yml
├── docs/
│   ├── .gitignore
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── examples/
│       │   └── .gitignore
│       ├── index.md
│       ├── internals.md
│       └── intro.md
├── examples/
│   └── custom_macros.jl
├── src/
│   ├── Setfield.jl
│   ├── functionlenses.jl
│   ├── lens.jl
│   ├── setindex.jl
│   └── sugar.jl
└── test/
    ├── dynamiclens_begin.jl
    ├── perf.jl
    ├── runtests.jl
    ├── test_core.jl
    ├── test_examples.jl
    ├── test_functionlenses.jl
    ├── test_quicktypes.jl
    ├── test_setindex.jl
    ├── test_setmacro.jl
    └── test_staticarrays.jl
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
  {
    "path": ".codecov.yml",
    "chars": 15,
    "preview": "comment: false\n"
  },
  {
    "path": ".github/workflows/CI.yml",
    "chars": 1885,
    "preview": "name: CI\non:\n  - push\n  - pull_request\njobs:\n  test:\n    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matr"
  },
  {
    "path": ".github/workflows/CompatHelper.yml",
    "chars": 595,
    "preview": "name: CompatHelper\n\non:\n  schedule:\n    - cron: '00 * * * *'\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:"
  },
  {
    "path": ".github/workflows/Invalidations.yml",
    "chars": 1472,
    "preview": "name: Invalidations\n\non:\n  pull_request:\n\nconcurrency:\n  # Skip intermediate builds: always.\n  # Cancel intermediate bui"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "chars": 362,
    "preview": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_n"
  },
  {
    "path": ".gitignore",
    "chars": 43,
    "preview": "*.jl.cov\n*.jl.*.cov\n*.jl.mem\nManifest.toml\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1162,
    "preview": "The Setfield.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2017: Jan Weidner.\n>\n> Permission is"
  },
  {
    "path": "Project.toml",
    "chars": 1004,
    "preview": "name = \"Setfield\"\nuuid = \"efcf1570-3423-57d1-acb7-fd33fddbac46\"\nversion = \"1.1.2\"\n\n[deps]\nConstructionBase = \"187b0558-2"
  },
  {
    "path": "README.md",
    "chars": 1394,
    "preview": "# Setfield\n\n[![DocStable](https://img.shields.io/badge/docs-stable-blue.svg)](https://jw3126.github.io/Setfield.jl/stabl"
  },
  {
    "path": "appveyor.yml",
    "chars": 2130,
    "preview": "environment:\n  matrix:\n  - JULIA_URL: \"https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe\"\n"
  },
  {
    "path": "docs/.gitignore",
    "chars": 11,
    "preview": "build\nsite\n"
  },
  {
    "path": "docs/Project.toml",
    "chars": 163,
    "preview": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nLiterate = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nStaticArray"
  },
  {
    "path": "docs/make.jl",
    "chars": 715,
    "preview": "using Setfield, Documenter, Literate\n\ninputdir = joinpath(@__DIR__, \"..\", \"examples\")\noutputdir = joinpath(@__DIR__, \"sr"
  },
  {
    "path": "docs/src/examples/.gitignore",
    "chars": 5,
    "preview": "*.md\n"
  },
  {
    "path": "docs/src/index.md",
    "chars": 69,
    "preview": "## Docstrings\n\n```@autodocs\nModules = [Setfield]\nPrivate = false\n```\n"
  },
  {
    "path": "docs/src/internals.md",
    "chars": 66,
    "preview": "# Internals\n\n```@autodocs\nModules = [Setfield]\nPublic = false\n```\n"
  },
  {
    "path": "docs/src/intro.md",
    "chars": 2151,
    "preview": "## Usage\n\nSay we have a deeply nested struct:\n\n```jldoctest spaceship\njulia> using StaticArrays;\n\njulia> struct Person\n "
  },
  {
    "path": "examples/custom_macros.jl",
    "chars": 1809,
    "preview": "# # Extending `@set` and `@lens`\n# This code demonstrates how to extend the `@set` and `@lens` mechanism with custom\n# l"
  },
  {
    "path": "src/Setfield.jl",
    "chars": 919,
    "preview": "__precompile__(true)\nmodule Setfield\nusing MacroTools\nusing MacroTools: isstructdef, splitstructdef, postwalk\nusing Stat"
  },
  {
    "path": "src/functionlenses.jl",
    "chars": 1107,
    "preview": "set(obj, ::typeof(@lens last(_)), val) = @set obj[lastindex(obj)] = val\nset(obj, ::typeof(@lens first(_)), val) = @set o"
  },
  {
    "path": "src/lens.jl",
    "chars": 6270,
    "preview": "export Lens, set, get, modify\nexport @lens\nexport set, get, modify\nusing ConstructionBase\nexport setproperties\nexport co"
  },
  {
    "path": "src/setindex.jl",
    "chars": 804,
    "preview": "Base.@propagate_inbounds function setindex(args...)\n    Base.setindex(args...)\nend\n\nBase.@propagate_inbounds function se"
  },
  {
    "path": "src/sugar.jl",
    "chars": 8777,
    "preview": "export @set, @lens, @set!\nusing MacroTools\n\n\"\"\"\n    @set assignment\n\nReturn a modified copy of deeply nested objects.\n\n#"
  },
  {
    "path": "test/dynamiclens_begin.jl",
    "chars": 452,
    "preview": "l = @lens _[begin]\n@test l isa Setfield.DynamicIndexLens\nobj = (1,2,3)\n@test get(obj, l) == 1\n@test set(obj, l, true) =="
  },
  {
    "path": "test/perf.jl",
    "chars": 3963,
    "preview": "module Perf\nusing BenchmarkTools\nusing BenchmarkTools: Benchmark, TrialEstimate\nusing Setfield\nusing Test\nusing Interact"
  },
  {
    "path": "test/runtests.jl",
    "chars": 371,
    "preview": "module TestSetfield\n\nimport PerformanceTestTools\nimport Setfield\nusing Documenter: doctest\n\ninclude(\"test_setindex.jl\")\n"
  },
  {
    "path": "test/test_core.jl",
    "chars": 13606,
    "preview": "module TestCore\nusing Test\nusing Setfield\nusing Setfield: compose, get_update_op\nusing ConstructionBase: ConstructionBas"
  },
  {
    "path": "test/test_examples.jl",
    "chars": 190,
    "preview": "module TestExamples\nusing Test\ndir = joinpath(\"..\", \"examples\")\n@testset \"example $filename\" for filename in readdir(dir"
  },
  {
    "path": "test/test_functionlenses.jl",
    "chars": 2089,
    "preview": "module TestFunctionLenses\nusing Test\nusing Setfield\n\n@testset \"first\" begin\n    obj = (1, 2.0, '3')\n    l = @lens first("
  },
  {
    "path": "test/test_quicktypes.jl",
    "chars": 3278,
    "preview": "module TestQuicktypes\nusing Test\n\nimport Base: ==\nimport MacroTools\n\nusing QuickTypes\nusing Setfield\nimport Construction"
  },
  {
    "path": "test/test_setindex.jl",
    "chars": 1272,
    "preview": "module TestSetindex\nusing Setfield\nusing Test\n\n\"\"\"\n    ==ₜ(x, y)\n\nCheck that _type_ and value of `x` and `y` are equal.\n"
  },
  {
    "path": "test/test_setmacro.jl",
    "chars": 2315,
    "preview": "module TestSetMacro\n\nmodule Clone\nusing Setfield: setmacro, lensmacro\n\nmacro lens(ex)\n    lensmacro(identity, ex)\nend\n\nm"
  },
  {
    "path": "test/test_staticarrays.jl",
    "chars": 1167,
    "preview": "module TestStaticArrays\nusing Test\nusing Setfield\nusing StaticArrays\nusing StaticNumbers\n\n@testset \"StaticArrays\" begin\n"
  }
]

About this extraction

This page contains the full source code of the jw3126/Setfield.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (60.2 KB), approximately 21.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!