Boxy
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# The plan for `min_generic_const_args` ## Where does `min_const_generics` fall short The `min_const_generics` feature was stabilized [at the end of 2020](https://github.com/rust-lang/rust/pull/79135) with a few significant limitations. These limitations are explained nicely in WithoutBoats' blog post titled [Shipping Const Generics in 2020](https://without.boats/blog/shipping-const-generics/) and is reccomended reading before continuing. The focus of this document is on allowing more complex expressions involving generic parameters to be used as const arguments, "No complex expressions based on generic types or consts" as titled in boats' blog post. This document intends to explain: - Why it is hard to allow arbitrary expressions involving generic parameters to be used in the type system - What design the const generics project group intends to pursue in order to allow more complex expressions involving generic parameters. - How the proposed design avoids pitfalls encountered by previous experimentation in this area. ## Why does `generic_const_exprs` not work Allowing complex expressions involving generic parameters has been experimented with under the `generic_const_exprs` feature gate for the past few years. In short it allows writing code such as `[u8; <T as Trait>::ASSOC]` or `where Foo<{ N + 1 }>: Other<M>`; arbitrary expressions are supported as const arguments regardless of whether they involve generic parameters or not. ### Background implementation details Currently const arguments (even under `min_const_generics`) are implemented by desugaring the const argument to a const item (called anonymous constants) and then referring to it. For example under `min_const_generics`: ```rust fn foo<T: Trait>(arg: T) -> [T; MY_CONST + 2] { /* ... */ } // gets desugared to: const ANON: usize = MY_CONST + 2; fn foo<T: Trait>(arg: T) -> [T; ANON] { /* ... */ } ``` The implementation strategy for `generic_const_exprs` is that the implicitly created `ANON` has the same generics and where clauses defined on it as `foo`. Additionally we also implicitly create `terminates { ... }` where clauses for any const arguments used in the signature (more on this later). The previous example with the `generic_const_exprs` feature gate enabled would be desugared like so: ```rust #![feature(generic_const_exprs)] fn foo<T: Trait>(arg: T) -> [T; MY_CONST + 2] { /* ... */ } // gets desugared to: const ANON<T: Trait>: usize = MY_CONST + 2 where terminates { ANON::<T> }; fn foo<T: Trait>(arg: T) -> [T; ANON] where terminates { ANON::<T> } { /* ... */ } ``` With all that covered we can begin to talk about the issues with the current design of `generic_const_exprs`. --- ### Const arguments that fail to evaluate In Rust not all expressions are guaranteed to terminate and return a value, they can loop forever, panic, encounter UB, etc. This introduces some complexity for const generics as we must decide on how to handle a const argument such as `Foo<{ usize::MAX + 1 }>` or `Foo<{ loop {} }>`. While the previous examples are relatively easy to determine that they will panic and loop forever respectively, it is not so easy in the general case. This is quite literally the halting problem. A conservative analysis is, in general, possible in order to guarantee termination of functions (a lot of functional programming languages implement this). Implementing this would require another function modifier, e.g. `const terminates fn foo() -> usize;` and generally complicate the language significantly. The (stabilized) `min_const_generics` feature handles this by requiring any complex expressions to not use generic parameters, this means it is always possible to *try* to evaluate the expression. If it fails to evaluate then we error, otherwise we have a value. As `generic_const_exprs` supports arbitrary expressions that *do* use generic parameters, such as `Foo<{ N + 1 }>`, it is no longer always possible to evaluate a const argument as it may involve generic parameters that are only known after monomorphization. Given the example of `Foo<{ N + 1 }>` it would panic when `usize::MAX` is provided as the value of `N`. Some ways this could potentially be handled, one of which was already mentioned, would be: - Introduce a `terminates` function modifier and have an addition function that is guaranteed to terminate if the type system can prove `N < usize::MAX` - Post monomorphization errors for when generic arguments are provided to `N` that would happen to cause it to fail to evaluate - Require the caller to prove that `{ N + 1 }` will terminate The last option is what `generic_const_exprs` implements, to give a concrete example: ```rust #![feature(generic_const_exprs)] fn foo<const N: usize>() -> [u8; N] { /* ... */ } fn bar<const N: usize>() where terminates { N + 1 }, { // fine, caller has checked that `{ N + 1}` will terminate foo::<{ N + 1 }>(); } fn baz() { // fine, `{ 10 + 1 }` terminates bar::<10>(); // ERROR: `{ { usize::MAX } + 1 }` will panic bar::<{ usize::MAX }>(); } fn bar_caller_2<const N: usize>() where terminates { { N * 2 } + 1 }, { // fine, caller has checked that `{ { N * 2 } + 1 }` will terminate bar::<{ N * 2 }>(); } fn main() { baz(); // fine, `{ { 10 * 2 } + 1 }` terminates bar_caller_2::<10>(); // ERROR: `{ { { usize::MAX - 100} * 2 } + 1 }` will panic bar_caller_2::<{ usize::MAX - 100 }>(); } ``` Sidenote: In the actual implementation these bounds are not written as they are in the example. I opted to use `terminates { ... }` syntax as it is significantly more readable than what is actually implemented. These `terminates` bounds are a huge design issue with the current feature as they require putting large amounts of implementation details of the function in its public API. I would not expect us to want to stabilize anything with this solution. ### Unrecoverable CTFE errors In general the trait system should be able to fail in arbitrary ways without causing compilation to fail. This requirement comes from a few places: - Coherence evaluates where clauses and if they fail to hold use that information to consider impls to not overlap. - During type checking/trait solving we often speculatively try some inference constraints and if they succeed great, otherwise we try other inference constraints In these cases we can wind up evaluating generic constants with generic arguments that would cause evaluation failures. For example the following example: ```rust trait Trait<const N: usize> { type Assoc; } impl<const N: usize> Trait<N> for [u8; N - 1] where terminates { N - 1 } { type Assoc = u32; } impl Trait<0> for [u8; 0] { type Assoc = u64; } ``` In this example coherence attempts to check whether the two impls are overlapping or not. In order to do this we wind up inferring `N=0` and then trying to prove all of the where clauses on the generic impl. The generic impl has only one where clause:`terminates { N - 1 }`, with generic arguments applied this is actually `terminates { 0 - 1 }`. Evaluating `0 - 1` panics which emits a *hard error* by the const eval machinery. As hard errors are unrecoverable we wind up erroring on this code with the message that evaluating `0 - 1` failed even though in theory this ought to succeed compilation. While `terminates` bounds were used in this example as it is a very simple demonstration of the issue, they are not strictly required to demonstrate this and so removing `terminates` bounds would not solve this. Changing const eval to not emit a hard error when failing to evaluate constants is undesirable as the current setup prevents a lot of potential issues that could arise from forgetting to emit an error in other cases. Any design for generic const arguments needs to be able to avoid evaluating constants that might not succeed or else there will be weird behaviour and error messages in coherence and trait solving. ### Checking equality of generic consts causes query cycles The type system representation of constants must be able to represent the actual expressions written not just a usage of an anon const. This is so that two separate `{ N + 1 }` expressions are considered to be equal. For example the following code should compile even though `N + 1` is written in two different places and would result in two diferent anon consts: ```rust #![feature(generic_const_exprs)] fn foo<const N: usize>(a: Bar<{ N + 1 }>) where Bar<{ N + 1 }>: Trait, { baz(a) } fn baz<T: Trait>(_: T) { } ``` The way that `generic_const_exprs` handles this is by looking at the THIR (post-typeck hir) of anon consts and then lowering it to a new AST specifically for type level expressions. The requirement that we first type check the anon const before being able to represent its normalized form in the type system causes a lot of query cycle isues when anon consts are used in where clauses. These cycles tend to look something like the following: - Try to type check an anon const in a where clause - Type checking that anon const requires doing trait solving - Trait solving tries to use a where clause containing the original anon const - We try to type check the original anon const again A concrete example of this kind of thing would be the following code: ```rust #![feature(generic_const_exprs)] trait Trait<const N: usize> { const ASSOC: usize; } fn foo<T, U, V, const N: usize>() where T: Trait<{ N + 1 }>, T: Trait<{ <T as Trait<{ N + 1 }>>::ASSOC }>, { } ``` In this example we might encounter a cycle like so: - We try to type check `{ <T as Trait<{ N + 1 }>>::ASSOC }` - This requires proving `T: Trait<{ N + 1 }>` - There are 2 possible where clauses that from the trait solvers POV could be used: - `T: Trait<{ N + 1 }>` - `T: Trait<{ <T as Trait<{ N + 1 }>>::ASSOC }>` - From the trait solvers POV `{ <T as Trait<{ N + 1 }>>::ASSOC }` is potentially normalizeable to `{ N + 1 }` in which case this *would* be a valid way of proving `T: Trait<{ N + 1 }>` - Trying to determine if `{ N + 1 }` is equal to `{ <T as Trait<{ N + 1 }>>::ASSOC }` requires type checking both anon consts. As we are already type checking `{ N + 1 }` we have reached a cycle. Attempting to represent type system constants through a lowering step *after* type checking therefore seems like a dead end to me as this is code that we should support ([equivalent code written with associated types](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c15faa442ac33404cb63c1a84d272e45) compiles fine). ### Unused generic parameters The current desugaring can easily result in an anon const having more generics defined on it than are actually used by the body. Take the following example: ```rust struct Foo<T, U, const N: usize> { arr: [u64; N + 1], ... } // desugars to: const ANON<T, U, const N: usize>: usize = { N + 1 } where terminates { ANON::<T, U, N> }; struct Foo<T, U, const N: usize> where terminates { ANON::<T, U, N> } { arr: [u64; ANON::<T, U, N>], ... } ``` The type `[u64; ANON::<T, U, N>]` "uses" all of the generic parameters in scope which causes a number of issues: - All generic parameters on the struct are invariant due to them all being used in a const alias - Causes issues with object safety as an anon const would be considered to "use" the `Self` type - Breaks with defaulted generic parameters as the anon const is considered to use all parameters even forward declared parameters ### Evaluating constants with where clauses that do not hold Const eval should generally not be run on bodies that do not type check as doing so can easily cause ICEs. Attempting to change const eval to support running on bodies that do not type check would introduce a large amount of complexity to const eval machinery and is undesirable. Unfortunately the current implementation of `generic_const_exprs` does not check that where clauses on anon consts hold before evaluating them. This results in const eval ICEing sometimes when using complex expressions with generic parameters in where clauses. In theory it would be simple to ensure that the where clauses hold *before* evaluating a constant. In practice this would drastically worsen the previous issue about query cycles to the point where evaluating any anon const in a where clause would cause a query cycle. This is caused by the fact that anon consts are desugared to having all of the where clauses in scope (similar to how they have more generics than is strictly necessary). This means that an anon const inside of a where clause, would be desugared to having that same where clause. Example: ```rust fn foo<T, const N: usize>() where T: Trait<{ N + 1 }>, {} // desugars to: const ANON<T, const N: usize>: usize = { N + 1 } where T: Trait<ANON::<T, N>>, terminates { ANON::<T, N> }; fn foo<T, const N: usize>() where T: Trait<{ ANON::<T, N> }>, terminates { ANON::<T, N> }, ``` Attempting to evaluate `ANON::<concrete, concrete>` would first require proving `terminates { ANON::<concrete, concrete> }`, which is proven by evaluating `ANON::<concrete, concrete>`. Any design for supporting complex expressions involving generic parameters needs to be able to only evaluate constants when they are wellformed. ## Can we fix `generic_const_exprs`? Having highlighted the core issues with the current implementation of the feature it's worth trying to figure out if we can just solve these and then stabilize it. To summarize the requirements above we would wind up with: 1. How do we represent type system consts without requiring a hir body that first need to be type checked 2. How to make sure constants only use the "right" amount of generic parameters 3. How do we make sure its possible that constants be checked for wellformedness before evaluating them 4. How to avoid evaluating constants that might not terminate - I am combining the two headers of "how to handle `N + 1` where `N=usize::MAX`" and "how to avoid hitting unrecoverable CTFE errors" as these wind up being incredibly similar problems To start with we would have to stop using the current desugaring with creating const items- it's fundamentally incompatible with wanting to represent type system constants without requiring type checking a body first. Instead we need to have a new kind of AST for representing expressions in the type system. There are a number of concerns here: - We would have an entire second way of type checking expressions - How do we handle evaluating this AST - What expressions do we support? method calls such as `a.foo(N)` would add a significant amount of complexity to the type system which is undesirable, so we cant just say "all of them". Regardless, this *would* solve issues 1, 2, and 3. The new desugaring would result in only syntactically used generic parameters being present in the AST. Additionally the minimal required where clauses to hold for the const argument to be wellformed can be derived from the AST. For the next issue (4) I can see a couple possible solutions: - Simply accept that the type system will be buggy and error on your code for little to no reason, and you have to work around it - Introduce a simplistic termination checker to rust used by `generic_const_exprs` and only permit calls to functions that are statically known to terminate Simply accepting a buggy type system implementation seems out of the question to me. It is not a good look for the language to decide to stabilize something with bugs without any plan for how they could ever be resolved. As an additional point, if we accepted the buggy behaviour we would still need to decide how to handle `N + 1` as an argument when a concrete value of `usize::MAX` was provided for `N`. Post monomorphization errors are an "obvious" solution to this that *would* work from a technical standpoint. Introducing a termination checker is a *significant* undertaking that would take a long time to fully work out the design of- let alone have it in a stabilizable state. I imagine this would take many years and also significantly increase the complexity of the language. In conclusion while there is potentially a path here to having everything work, there is a large number of open questions and issues with those potential solutions. Regardless of what is decided it would potentially take years to get something into a stabilizable state, and that assumes that this will actually work out. While `generic_const_exprs` itself might be a rather long way off, it should be possible to carve out a subset of the feature that *is* possible to get working nicely in a much shorter timeframe, e.g. a `min_generic_const_args` feature. ## What is `min_generic_const_args` On stable there are limitations to what a const generic parameter can have as its argument, concretely this is: - A single parameter `{ N }` - An arbitrary expression that does not mention any generic parameters, e.g. `{ foo() + 10 / 2 }` The `min_generic_const_args` will extend this to also support: - A usage of an associated constant with arbitrary generic parameters, e.g. `<T as Trait<N>>::ASSOC` so long as the definition is marked `#[type_system_constant]`. Similar to how uses of const parameters does not require explicit braces, the same is true of uses of associated constants. `Foo<<T as Trait>::ASSOC>` is legal without requiring it to be written as `FOO<{ <T as Trait>::ASSOC }>`. (maybe, might get cut, but it seems nice) In order for an associated constant to be usable with generic parameters as an argument to a const generic parameter it must be marked with `#[type_system_constant]` on the trait definition. This attribute will require all trait implementations to specify a valid const generic argument as the value of the associated constant instead of an arbitrary unrestricted expression. Example: ```rust trait Trait { const ASSOC: usize; } impl<const N: usize> Trait for Foo<N> { const ASSOC: usize = N; } fn foo<const N: usize>() {} fn bar<T: Trait>() { // ERROR: `Trait::ASSOC` is not marked as `#[type_system_constant]` and so // cannot be used in the type system as a const argument. foo::<<T as Trait>::ASSOC>(); } ``` If trait definition was rewritten to to use the `#[type_system_constant]` attribute then this would compile: ```rust trait Trait { #[type_system_constant] const ASSOC: usize; } ``` This restriction is required to ensure that implementors of `Trait` are not able to write arbitrary expressions as the value of the associated constant, instead a valid const generic argument must be written. For example: ```rust impl<const N: usize> Trait for Foo<N> { // ERROR: `N + 1` is not a valid expression in the type system const ASSOC: usize = N + 1; } ``` ## Does this actually fix the original problems? Reusing the list of issues from earlier: 1. How do we represent type system consts without requiring a hir body that first need to be type checked 2. How to make sure constants only use the "right" amount of generic parameters 3. How do we make sure its possible that constants be checked for wellformedness before evaluating them 4. How to avoid evaluating constants that might not terminate The first problem is easy to solve with associated constants as they have an obvious representation, a `DefId` + list of generic args (the same as we do for associated types). This also does not introduce any of the complexities with trying to represent arbitrary expressions: - Evaluating associated constants is trivial as the compiler already has the machinery for this - There is no need to figure out how to type check or borrow check arbitrary type system expressions as associated constants have simple rules around wellformedness already present in the compiler, and they just inherently don't need borrow checking. It is also trivial to tell what generic parameters a const argument uses as they are explicitly defined on the trait, the same as how associated types are handled in the compiler which has been shown to work adequately. The requirement that all associated constants in the type system are defined as being equal to a valid const argument means that ensuring all constants in the type system terminate is as easy as it is under `min_const_generics`. The value for an associated constant can either be assumed to terminate or simply evaluated and then error if they do not. This also means that it is trivial to avoid evaluating constants that might not terminate as every const generic argument will have already been checked somewhere that it can be evaluated without issues, e.g. in the caller or in an associated const's definition. Determining that const arguments are well formed before evaluating them is *also* trivial as we do not wind up with such recursive where clauses as all where clauses are explicitly written by the user on the `trait` or the associated const itself. ## Future extensions to `min_generic_const_args` - Support struct/enum construction for when arbitrary types are allowed as const generics. - Special case `size_of`/`align_of` to be permitted too? - Experiment with permitting arbitrary expressions as described in [Can we fix generic_const_exprs](https://hackmd.io/@BoxyUwU/BJ6_bfmD0#Can-we-fix-generic_const_exprs) or by allowing arbitrary expressions to be used for associated consts in the type system - Introducing where clauses that allow bounding generic parameters, e.g. a hypothetical `where N is 1..`?

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully