It is similar to the existing behavior for duplicate methods in embedded structs, but not duplicate methods in embedded interfaces. (I filed #45405 to consider making those two cases more uniform.)

@henryas Please take into account the discussion so far. I've collected some of the main arguments in favor of having ~ in this comment and @rogpeppe has mentioned two cases of where identical type matching is preferable a couple comments before that.

My point is that approximation should be the default behavior. There is no need for an extra ~ keyword. I can't think of any realistic use cases where approximation as the default behavior would cause any harm.
If the purpose of this thread is to collect arguments in favor of having ~, instead of judging the merits of the new ~ keyword and whether it is a worthy inclusion into the language, then I suppose there is no place for me here.
I am not against approximation. I am against adding unnecessary new keywords into the language. Throwing more wrenches does not fix the car.

Yes, constructive criticism is welcomed. However, the point that you mention has been brought up several times. The comments that @Merovius linked explain why the new keyword is needed, and why it makes more sense for exact type-matching to be default. It’s my personal belief that underlying type-matching will actually be used very rarely outside of a provided constraints package, where exact type-matching is extremely useful for defining sum-types.

@Merovius

That doesn't help limit the computational complexity of checking if the type set is empty though.

Sure, not worst-case complexity -- but anything that can make a SAT instance more tractable is helpful :). I digress though, we already agree on the usefulness of emptiness checking.

@smasher164, more to the point: an emptiness checker would be a substantial amount of non-trivial code that isn't otherwise needed in the compiler, in order to diagnose a condition that will be diagnosed at some other point in real-world code anyway.

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

type SignedInteger = int | int8 | int16 | int32 | int64

type StringableSignedInteger = (int | int8 | int16 | int32 | int64) & { String() string }

And to make progress, we need new arguments and new reasoning, or evidence that the existing one is flawed.
@Merovius I don't like the idea of including symbols with specific meaning in general and ~ in particular for this case.

First of all, ~ is used as NOT operation in many languages so while reading proposal I subconsciously interpreted this as negation of following type.
Go has a very limited set of operators compared to other languages. It makes language a little too expressive sometimes but you don't have to guess what this abomination of an operator means in that context. Like in rust, for example.
And only ... and <- have meaning unique to Go, if I'm not wrong. Arguably, meaning of these operators is quite obvious within context but I can be biased. Also ^ used instead of ~ in similar way. That's it.
But without diving into documentation or inside details of this proposal there's no way to know what ~ means.
I think reflect.Kind is a good example. Many encoders and decoders use  switch (Value).Kind() instead of switch .(type) because it is a lot more convenient.
But if there is no way that int constraint can also satisfy type Type int then I think there should be some meaningful keyword like any int or something that doesn't force you to make guesses.

It's not universal, but it's far from the first time I've seen ~ used to mean "approximately".

It's not universal, but it's far from the first time I've seen ~ used to mean "approximately".

This causes more confusion because "approximation" is "something like X but not exactly X".
What does that even mean in terms of types? Approximately how?
Both float64 and int64 are 8-byte types for representing numbers. But not exactly same kind of numbers. Is this approximation correct?
OK, that's weird one. But int64 and uint64 are 8-byte types for representing natural numbers. But not in exactly equal ranges. Is this approximation correct? You can even exchange between them without conversion for positive 63-bit values.
Well, signed and unsigned integers are still not the same since you interpret underlying data differently. But string and byte slice only different in a way that internal representation of string doesn't have cap field but you still can access same data in a same way. Does this make []byte and string approximately same types?
I understand that this example is heavily exaggerated but term "approximation" is way too vague for language with static types.
Is something like this:
type AnyInt interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

really has better readability than something like this:
type AnyInt interface {
	type any int, any int8, any int16, any int32, any int64
	// or this type int any, int8 any, int16 any, int32 any, int64 any
}

Yes, you still have to know what is any. But it's already present in type parameters proposal and meaning "something that can satisfy interface{}" and this basically an expansion for this like any int = interface{ int } which makes sense for type Type int.

From wikipedia:

This symbol (in US English) informally means "approximately", "about", or "around", such as "~30 minutes before", meaning "approximately 30 minutes before". It can mean "similar to", including "of the same order of magnitude as", such as: "x ~ y" meaning that x and y are of the same order of magnitude.

A tilde in front of a single quantity can mean "approximately", "about" or "of the same order of magnitude as."

Also, in contrast:

In written mathematical logic, the tilde represents negation: "~p" means "not p", where "p" is a proposition. Modern use often replaces the tilde with the negation symbol (¬) for this purpose, to avoid confusion with equivalence relations.

The tilde as negation seems like a secondary and perhaps unfortunate usage.  I have seen ! as negation in the perhaps dozen languages I'm familiar with.

Is something like this: really has better readability than something like this:

Subjectively, absolutely.

It's not worth belaboring in the issues, but yes, I am another vote for "~ meaning 'approximately' is a familiar usage to me." In the context of Go types, type MyInt int is approximately the same as int. If the usage is unfamiliar or unexpected to someone else, I can't argue that person out of it because it's a subjective reaction, but I would be surprised to learn that this was a common reaction.

What if we just name this tilde put before type identifier a "dig operator" — as it digs out the underlying type for use in this context.
Or possibly use some other short orthogonal name that will not confuse neither mathematicians nor poets among the community. Neither "underlying" nor "approximation" are good for documentation and tutorials.

@tooolbox @carlmjohnson I never said that ~ is only ever used as negation operator in programming history. And I also seen !, ^ and <> as negation operators. That's not the point. It's not that I don't like ~. I just don't care if that's ~ or ≈ or some other symbol for approximation.
The main concern is that you need to specifically learn new syntax, learn how these operators work, which operator has higher precedence, etc.
Type lists notation is OK. As someone said, this proposal only changes type and , to | and introduces new operator ~. That's it. You don't need to know how comma works, you don't need to guess whether author of language reads Wikipedia articles in English or not.
Type list looks like your typical Go code, but this proposal alienating clarity in favor of something. And I don't mean that idea of having representation for underlying type is bad. It's good.
When you introduce whatever operator in the language it will have different meaning for some people other than OP. Yes, it is subjective. Yes any keyword (or literally any other term with same meaning) might have some subtle difference for different people. But you can't read any as "not" and "similar to" at the same time.

Type list looks like your typical Go code, but this proposal alienating clarity in favor of something. And I don't mean that idea of having representation for underlying type is bad. It's good.

I don't think I agree with your premise. This proposal attempts to replace type lists because of a lack of clarity. Type lists say type int8, but what they actually mean is "int8 and any types whose underlying type is int8", but there's no syntactic indication that underlying types are being considered. There's room for argument that ~ isn't the right symbol to indicate an underlying type match, but I think having some kind of indicator is much better than having none.
With that in mind, this proposal favors clarity as well as flexibility, since now it's possible to express exact type matches too.
I take your point about ~ meaning "not" in some languages, but the fact that Go uses ! to mean "not" in my view reduces the likelihood of confusion.

This causes more confusion because "approximation" is "something like X but not exactly X".
What does that even mean in terms of types? Approximately how?

Having syntax that completely describes language semantics seems like a non-goal to me, since it's nearly impossible to achieve. The any keyword you suggest doesn't do that, either.

type Constraint {
    type int8 // exactly int8
    type ______
         uint16 // any type whose underlying type is uint16
}

I probably should have posted this a bit over a week ago, sorry for the delay.

Go had a history, in my opinion, of imprecise expression, to the benefit of the user. For instance, there’s a well circulated scathing critique of Gos file library. However, my opinion is that this is a strength of Go. Unnecessary flexibility and clarity is harmful to users, not helpful. It’s the same reason simple menus with only a few options took off at the turn of the century. We’re doing the majority of users a great service if we decide for them.

@gudvinr

This causes more confusion because "approximation" is "something like X but not exactly X".

That is exactly the proposed meaning.

What does that even mean in terms of types? Approximately how?

"Having T as the underlying type". Note that underlying type is a precisely defined concept.
It's not necessarily a widely known concept to Gophers - but it really should be. And in either case, it's not extremely difficult to understand.

Is something like this: […] really has better readability than something like this: […]

I would say they are about equal. Note that all your criticism of ~ and the meaning of "approximately" transfers to "any" - if not more. Because it's simply not correct to say that type MyInt int defines "some" int. So it's not particularly clear that it should be allowed to be any int.
I don't have a huge problem with a keyword such as any, instead of ~, but it definitely shouldn't get any longer than that.

The main concern is that you need to specifically learn new syntax,

I disagree with this argument. Without this proposal, you need to learn the syntax type A, B C, with this proposal you have to learn the syntax A | ~B | ~C. Both is new syntax you have to learn, so this proposal doesn't mean you have to learn "new syntax".

learn how these operators work, which operator has higher precedence, etc.

Perhaps. I don't think this is particularly hard to learn.
@ohir

Neither "underlying" nor "approximation" are good for documentation and tutorials.

I strongly disagree. "underlying" is a well-defined term. You can google "golang underlying type" and will be pointed to the spec and other resources.

For the record, if all we are discussing is if it should be ~ or another operator or a keyword, I think that is a good thing - because it means we've officially reached the bikeshedding phase of the proposal :)

FTR, just so we are all up to speed, this is the complete definition of "underlying type":

Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its type declaration.

This is, IMO, not hard to understand or explain. It is, ironically, significantly easier to understand than "identical type", which no one seems to take issue with :)

I don't have a huge problem with a keyword such as any, instead of ~, but it definitely shouldn't get any longer than that.

This would be a big change to the existing proposals, as under the current generics proposal any is a pre-declared identifier, not a keyword, but it would have to be a keyword to be used instead of ~. Making it a keyword would make the entire generics proposal no longer backwards-compatible.

I have a question as to the general value of constraining the approximation element syntax only to type whose underlying type is itself? Is that important for some implementation detail? What would you lose if you instead allowed approximation elements on all concrete (non-interface) types?

For the record, if all we are discussing is if it should be ~ or another operator or a keyword, I think that is a good thing - because it means we've officially reached the bikeshedding phase of the proposal :)

I agree. Despite being personally totally on board with ~ syntax as laid out in this proposal, it seems like of the discussion and controversy is around the readability and meaning of ~. From there, we've seen a number of proposals to eliminate that syntax either by unifying around exact type matching or around underlying type matching.
As a preface, I want to note that go has a number of constructs which are provided by the language that you could not implement yourself (make, new, close) but which fit definitions of concepts we know about (those things are functions). At the end of the day here, the ~ is about expressing a type set.
We already have a concept we're introducing to express a type set, the interface. An interface can be generic. Instead of adding new syntax, could we add a new predeclared identifier underlying which is a generic interface type underlying[T isItself] interface which is defined as the infinite set of types which have T as their underlying type. And isItself is my shorthand for an unexported and unusable constraint which means exactly what the proposal currently says for when you can use ~.

@DeedleFake I don't believe this is a blocker. It is possible to work around that, by interpreting any differently in different contexts. But also, to be clear, I'm not advocating for any, I prefer ~ - I just also acknowledge that it would be possible to use any :) And I still don't think it super matters.

any as somehow meaning underlying feels super confusing given any is already something we're talking about making mean interface{}. I don't expect to be writing interfaces over underlying types that frequently, is 8 characters going to break the bank here?

@ajwerner

I have a question as to the general value of constraining the approximation element syntax only to type whose underlying type is itself? Is that important for some implementation detail?

For other types, ~T is the empty set. See also this comment, where @griesemer answers this question.

Instead of adding new syntax, could we add a new predeclared identifier underlying

This has been suggested before. It certainly is possible. IMO it is too verbose, but I'm just a person with an opinion.

@velovix I don't think you really get the idea. There's no issue with that particular symbol nor with existence of indication of some sort. It is a good idea to bring some representation for underlying types.
The whole point is not the symbol. The point is that this proposal brings completely new syntactic constructs that are very different from what is used in Go now for representing types without clear benefits of those constructs.
Right now you can use following syntax in Go:

var x, y, z T
func(x T1, y T2, z T3)
switch x.(type) { case T1, T2, T3: }

I am aware about only one thing that involves types and operator |.
It is reflect.Kind which is uint so you can do reflect.String | reflect.Int8 | reflect.Int16.
Type parameters introduce any which means constraint that have zero requirements. Ok, that's understandable and self explanatory.
Then someone suggests the idea of type lists. type T1, T2, T3 understandable too but you can't distinguish between type TX T1 and T1. That's a fair point.
But why introduce a whole new operators when you could just improve the basic idea by using familiar syntax. That's what I can't comprehend. Ok, you don't like any or other keyword. But that's subjective anyway so it shouldn't matter.

@Merovius

Instead of adding new syntax, could we add a new predeclared identifier underlying

This has been suggested before. It certainly is possible. IMO it is too verbose, but I'm just a person with an opinion.

My proposal is not exactly https://github.com/golang/go/issues/45346#issuecomment-812678694. That proposal is a new keyword. I'm also not wedded to the string underlying by any stretch. A new keyword feels horrible to me. A new langauge provided interface constraint feels sane. I'm cool with like[T] and is[T] to cut down on verbosity.

@gudvinr

I am aware about only one thing that involves types and operator |.

I don't think this is a good benchmark. On the contrary - this not being used previously is a good thing, as it means we don't have pre-existing connotations to content with.

It is reflect.Kind which is uint so you can do reflect.String | reflect.Int8 | reflect.Int16.

This is off-topic, but FTR, this code is almost definitely incorrect.

But why introduce a whole new operators when you could just improve the basic idea by using familiar syntax.

There is no "familiar syntax". It is a new concept. You seem to be anchored to type-lists, but they, too, are a new concept. They are not inherently better than | and ~ - both are new, unfamiliar syntactical constructs.

But that's subjective anyway so it shouldn't matter.

Note that your dislike of ~ is subjective as well.
Subjective opinions do matter. Almost all of language design is subjective opinion. Not everyone's individual, singular subjective opinion matters. But expressing subjective opinions is how we find out what people like. Your subjective dislike (as well as other's) of ~ has been registered, don't worry :)

Note that your dislike of ~ is subjective as well.

@Merovius like, for real? I explicitly mentioned several times that specific symbol doesn't matter. I do not care if "approximation" represented by ~ or by something that I prefer to use in that context.

If we disqualify subjectivity from this discussion then how can we consider the subjective experience of users, which is the most important thing here?

@gudvinr I must admit, then I really don't understand your goal. If the specific symbol does not matter, but you also think

It is a good idea to bring some representation for underlying types.

then ISTM you should be satisfied with the proposal as is?

@gudvinr You're right, I completely misunderstood what you were arguing. If I understand now, what you want is to keep the basic syntax of type lists, namely the type keyword and the use of commas, while still being more explicit about matching against underlying types.
I suppose type lists do look more like some other Go syntax, but is that necessarily a good thing? The similarities are superficial. var x, y, z int or type myInt int don't mean what type lists mean. You could also argue that type lists don't look like other interface syntax. If type lists start with type, why don't methods in interfaces start with func? It's hard to compare the similarity of syntax across different features.
I think that type list or type set syntax will necessarily look a bit different from other Go syntax because it represents a new concept. Type sets, to me, look enough like previous Go code to be inoffensive, but that's my subjective opinion.
Do you have a specific alternative syntax in mind? It would be easier to compare pros and cons with more concrete information.

@ajwerner

I don't expect to be writing interfaces over underlying types that frequently, is 8 characters going to break the bank here?

With the understanding that this is just my opinion: To me, it is not the length that matters here, but how easily the code is to scan. It is, to me, very hard to scan what the actual types in underlying int, underlying int8, underlying string are. any is marginally better, but not by much. It is exactly the fact that it is a valid identifier, that makes this less readable to me. You may argue that syntax highlighting can help, but so far, Go is very readable without it (and many people, me included, prefer not to use it). Others specifically want a keyword or identifier, so I'll just have to agree to disagree with them :) Just thought I'd make clear that at least to some people, a keyword/identifier is less readable :)
Your is[T] suggestion is better, in that regard, as the type stands out more. Personally, I find ~T a lot clearer in its intent. Again, I understand that others feel different and that's okay - we can agree to disagree on this as well :)

@Merovius, for the record, I like ~T. The thrust of my comment was, if we're going to consider adding a new predeclared identifier for anything, let's please not make it a keyword or a function but rather a generic interface constraint.

@Merovius

I strongly disagree. "underlying" is a well-defined term. You can google "golang underlying type" and will be pointed to the spec and other resources.

Um. I meant "underlying operator" to be used in docs. I'll edit.

The proposed name of the operator, FWIW, is "approximation element" (and maybe "approximation operator"). That's at least what's used in this discussion and the proposal. I would expect that to be what's in the spec and docs, a priori, and at that point become similarly easy to search for as "underlying type" :) But FWIW, "how do we call this thing in the docs" is even more of a question of bikeshed-painting than "should we write it as ~ or any or underlying :)

@Merovius I mentioned my experience only as an example of how symbolic operator can introduce completely opposite meaning.  I get the idea. I know what approximation means and I understand what OP meant by that.
Yes, whatever way of putting types inside interface{ ... } is a new concept. But this new concept doesn't change semantic of type keyword. It still means type. interface{ ... } still related to constraints in terms of generics. any still related to nonexistent constraint.
I do not lean towards any keyword.
Anything that somehow related to how types used now (whether in Go1 or Go-with-generics) would be better than this abomination of a syntax. I don't see clarity here and whatever operator is used makes no difference. The difference between ~, ≈, ∈ and other ones is that you can type ~ using single keyboard key. Other than that they are equally bad.
It can be this:
type T1 any, T2, T3 any, T4

It can be that:
type any T1, T2, any T3, T4

or
type T1 folks, T2, T3 gang, T4, T5 homies, T6

Even different lists for underlying and strict types can be fine:
any type T1, T3
type T2, T4

or
kind T1, T3
type T2, T4

or
underlying T1, T3
type T2, T4

@gudvinr Okay. For the most part, as I said, your dislike of ~ (or any other symbol) has been noted.

The difference between ~, ≈, ∈ and other ones is that you can type ~ using single keyboard key.

I disagree. To me, the main difference, which is what, to me, makes it preferable, is that it is a symbol. x would also be typeable with a single keyboard key, but I would find it less readable, precisely because it is not a symbol.
But that's an opinion. I don't really see the point in arguing about that. Your opinion, as I said, is valid and has been noted :)

@gudvinr

It can be this: or or or or or

None of proposed reads for me. That said in just minutes I can have screen reader say to me plain understandable "underlying type of T OR underlying type of U".  (At least until tilde will be overloaded with other meaning requiring me to get syntax in context ).

@gudvinr
FWIW, all of your suggestions seem reasonable and possible to me (except the one where you were being facetious). Personally, I don't like them and I don't think they are inherently better than the proposal as is. But @ianlancetaylor and @griesemer might feel different and will likely consider them.

The approximate and exact types can't be in separate lists, because separate lists are intersected (not unioned). (They could perhaps be defined otherwise, but then you need even more keywords and even more special cases for when things are unioned vs. intersected.)

Sorry for being blunt, but then you underestimate exponential growth.

Sure, but note that there are other compilers that solve "intractable" problems during type-checking, namely Swift and C#. I'm not saying the problems are equivalent, but people don't seem to run into bad behavior in practice.

Until they do. Example: https://spin.atomicobject.com/2016/04/26/swift-long-compile-time/. (See also https://raquo.net/2016/04/debug-long-compile-times-swift/.) And you get blog posts that begin with sentences like: "Swift is an efficient language to write but if you’re not careful you can encounter excruciating build and compile times."
Go takes pains to make sure that compilation is consistently fast. We don't always succeed, but it is an explicit design goal, and using exponential algorithms in the compiler is a non-starter.
It's tempting to say "normal programs won't hit this", but in years of working on compiler performance issues, I've come to the conclusion that this is wrong approximately always.

Assume we are given a boolean formula in variables A…Z, in CNF (for example, say (A∨¬B∨¬C) ∧ (¬A∨C∨D)).
— @Merovius in https://github.com/golang/go/issues/45346#issuecomment-813639125

Shouldn't it be (A∨¬B∨¬C) ∧ (¬A∨B∨C)?

Is something like this: […] really has better readability than something like this: […]
— @gudvinr in https://github.com/golang/go/issues/45346#issuecomment-816841567

What a profoundly peculiar question to ask given what "readability" is commonly understood to mean.

some other short orthogonal name that will not confuse neither mathematicians nor poets among the community
— @ohir in https://github.com/golang/go/issues/45346#issuecomment-816876197

How about the "peel operator"? Short and sweet. As in, "peel away the concrete  type to reveal the underlying T". And ~ even kind of looks like it.

@antichris Fixed, thanks.

It's tempting to say "normal programs won't hit this", but in years of working on compiler performance issues, I've come to the conclusion that this is wrong approximately always.

The related assumption “humans will not write packages like this” may hold, but also bear in mind that not all packages are written by human authors. Some are generated by tools, such as protoc-gen-go or mkwinsyscall, which may produce a lot more code — and code with much more indirection — than a human would write to implement the same API.

The proposal mentions "sum types". An observation: even without explicit sum types in the language, union elements can be used to approximate them, keeping a similar amount of type safety:
https://go2goplay.golang.org/p/bHe9Buf8VhS
type MsgLike interface {
	OpenMsg | CloseMsg
}

func NewMsg[M MsgLike](m M) Msg {
	return Msg{x: m}
}

type Msg struct {
	x interface{}
}

func Get[M MsgLike](m Msg, mp *M) bool {
	if x, ok := m.x.(M); ok {
		*mp = x
		return true
	}
	return false
}

That this compiles in the playground today is I think a strong argument for figuring out the story around sum types before releasing generics publicly. If generics allow sum-types-ish, they will be (ab)used for sum types in places where they don't really fit and runtime interfaces would make more sense.

This additional static type safety of that example seems fairly small compared to what "real" sum types would provide.  In particular there is no restricted type switch.  I don't think we need to let writing this kind of code force us into deciding anything about sum types at this juncture.

If generics allow sum-types-ish, they will be (ab)used for sum types in places where they don't really fit and runtime interfaces would make more sense.

That, e.g., unsafe, also can be abused, does not mean it is the common practice or it should not have been made available.

In particular there is no restricted type switch

Yup, it's definitely more awkward to use without having a type switch. But the fact that you can only ask about types that are mentioned in the type list makes it about equivalent in safety if not convenience. The ergonomics are pretty similar to those of errors.As, which we manage to live with (though I'd have preferred a solution there that did allow a type switch FWIW).
FWIW I definitely support adding real sum types to the language - I think that this proposal makes them a natural fit.
But if they do prove problematic for one reason or another, I think it's interesting to see that at least we can get "poor man's" sum types anyway. And if someone does happen to use that idiom, they can always change to using actual sum types without breaking compatibility, albeit at the cost of an extra wrapper type.

Discussion still seems active for now, but I don't see any substantial objections since last week. This seems headed for likely accept but will put in the minutes as-is for another week.

Here's another edge case that's worth clarifying.
Should this be valid?
type A interface {
	[1]struct{} | [2]struct{}
}

func f[T A]() int {
	var t T
	var a [len(t)]int
	return a[0]
}

The way I read the proposal, I think it probably should be valid, because the body of f is valid for every possible  type mentioned in A, but there are reasons why we might not want to allow it.
Currently it's not considered valid.

A variant on the above:
type A interface {
	type [1]byte, [2]byte
}

func f[T A]() int {
	var t T
	var a [unsafe.Sizeof(t)]int
	return a[0]
}

The spec says "Calls to Alignof, Offsetof, and Sizeof are compile-time constant expressions of type uintptr." so this should probably be OK by the letter of the law.

@rogpeppe that use of unsafe.SizeOf or len to size array types feels related to https://github.com/golang/go/issues/44253.

@ajwerner Yes, it's closely related. In fact, even without #44253, if you make len of an array type parameter into a const, you can implement a form of generic arrays (I'll leave the evil details as an exercise for the reader).

LGTM, hopefully we won't need to express negation. If we did though, then unimplementable constraints may be of much higher value, just like being able to select the negation of the untyped nil typeset singleton.

The purpose of introducing type lists in the generics proposal was to specify the operations available to type parameters in parameterized functions. This is easy to define based on the idea of type sets. Given a type parameter P with a constraint C, a parameterized function is permitted to use an operation with a value of type P if the operation is permitted for every member of the type set of C.

I'm starting to think this sounds deceptively simple. It's easy to think about abstractly and I very much like the conceptual simplicity of the proposal. But many difficulties we talked about come down to that set sometimes being difficult to determine in practice. For example, should this be permitted?
type A = interface { A() }
type NotA = interface { A() int }
type B = interface{ B() }

type Constraint interface {
    A | NotA
    A | B
    NotA | B
}

func F[T Constraint](v T) {
    v.B()
}

By the wording of the above, it should be permitted - any type in the type set Constraint will have a method B. However, in terms of method sets, A | NotA, A | B and NotA | B all have empty method set, so the union of their method sets is also empty. So, in terms of method sets, this code should not be permitted. Experimentally, the go2go tool does not accept the equivalent code using the published proposal.
Based on this, we can show that type checking the type set based definition is (likely, barring errors in the proof) NP-complete:

Technical proof
We can devise a proof based on the proof that checking if a type set is empty is NP-complete, by adding
type Proof interface {
    Formula
    QED() string
}

type NoProof interface {
    QED() int
}

type Constraint interface {
    Proof | NoProof
}

func G[T Constraint](v T) {
    var _ int = v.QED()
}

Should this code compile? If there is a concrete type X in the type set Formula, it shouldn't:
type Contradiction struct {
    X // is in the type set of Formula, so Contradiction is as well
}

// Contradiction now is in the type set of Proof, thus in Proof|NoProof.
func (Contradiction) QED() string {}

We can construct a type that is in the type set of Constraint, but its QED method does not return an int.
However, if the type set of Formula is empty, then so is the type set of Proof. Thus, any type in the type set Proof | NoProof must necessarily be in NoProof, which has a QED method returning an int, so F is valid.
Therefore, to type check this program, the compiler has to proof whether or not Formula is empty, which we have already proven to be NP-complete.

Notably, the proof breaks if you check the validity of G based on method sets, using the rules from the proposal:

The method set of an interface is the union of the method sets of its lines
The method set of a union element is the intersection of method sets of its elements

The method set of Proof | NoProof is empty, so the call is disallowed. So is the method set of Formula. I believe the relevant observation is, that type sets in this proposal are given in CNF, while method sets are given in DNF - and solving SAT in CNF is NP-complete, but solving it in DNF is "trivial".

I still like the syntax of this proposal though. To fix this problem, I think we should

define constraint-satisfaction (at instantiation time) in terms of type-sets
define allowed operations in terms of method-sets (expanding them to include operator usages etc)

This also has the upside of forcing us to explicitly tackle the "what is an operation" question, as the spec needs to give a precise answer to that.
Unfortunately, I'm not entirely convinced, that this really fixes the problem, though. Because the natural follow up question is how to treat instantiations of a generic function using a type parameter. That is, code like
func F[T SomeConstraint](v T) {
    G[T](v)
}

func G[T SomeOtherConstraint](v T) {
}

forces us to to consider T both in the context of type set membership, and in the context of method set membership (i.e. "instantiating a generic function" is an "operation"). At this point in time, I can't give a specific demonstration of a problem, but to me, there seem to be dragons here.

As @rogpeppe observed correctly on slack: All of these problems would likely go away, if we didn't have type lists/union elements. Abstractly, they add disjunction (OR, ∨) to the logic we can express in constraints, where previously we only had conjunction (AND, ∧). We also need negation (NOT, ¬), but in my proofs, we only got that using OR (with the TertiumNonDatur constraint).
So, I think, the problem can be phrased as type lists/union elements creating a functionally complete list of connectives. The Featherweight Generic Go paper didn't have them, so these problems might not have been examined formally yet.

BTW, to be clear: Nothing in that last comment should be considered an immediate threat to this proposal. The syntax changes are immaterial and the addition of ~T itself is also not problematic. So, as long as we figure out the spec correctly, this proposal does not worsen the computational situation compared to the accepted design.

I think your initial constraint might simply be deemed invalid.
An element in A  (so satisfying A | NotA) may not be able to satisfy NotA | B.
The vertical bar operator is exclusif I think.

An element in A (so satisfying A | NotA) may not be able to satisfy NotA | B.

Why not?
type X struct{}

func (X) A() {}

func (X) B() {}

Clearly this is in NotA | B (it's in B) and clearly it's in A|NotA (it's in A) and clearly it's in A|B (it's in both).

The vertical bar operator is exclusif I think.

I don't think so and nothing in the proposal indicates that it is.

Ah I was correcting... It's not exclusive. It's just that A and NotA have incompatible method sets so their type sets are not intersecting if I'm not mistaken.

 On the contrary, the proof critically depends on their intersection being empty.

When I'm reading this typeset proposal (as well as previous one which it's based on), I keep asking myself, if this all about constraints why these entities are still referred to as an interface, why not constraint or smth? Clearly, I cannot use these "interfaces" as before. In my understanding they are not "interfaces", at least "very special" ones, so why not resolve this by separating with different keywords? What am I missing?

@inliquid You are not missing anything. It would be possible and the contracts-proposal did that. One of the most common complaints about that was that contracts and interfaces seemed to be very similar, as both expressed constraints on types, so why are they using different keywords? So this is a typical damned if you do, damned if you don't situation.
Using interface though has arguably the advantage of enabling us to actually use them as full interface types later, if we want to.

@atdiar You can use type aliases type A = interface { A() } etc., in which case it doesn't matter either way (which is why I used that a couple of times, to prevent this objection). FWIW, the proposal considers interfaces in union elements based on interface-satisfaction.

But again: The point is that A | NotA is exclusive. And I specifically had to introduce TertiumNonDatur in the first proof. Together, this guarantees that we can treat these interfaces as logical variables - any type we consider has to be in TertiumNonDatur, so it is either in A, or in NotA and never in both. That's the entire point.

Decidedly... This is an interesting mental exercise. And agreed.
The method sets are actually irrelevant I think.
It's just the semantic of the vertical bar operator. Should denote exclusive inclusion into a "named" type set. (wording still approximative, want to express that a type can only implement one of the interface elements of a union element at a time for the whole constraint it satisfies)
That's what I was trying to infer when writing about how interfaces were handled. But admittedly, I need to pay a bit more attention to the wording to make it clearer, even for myself.

I don't think we'd want that. We would want to be able to write
type Stringish interface {
    ~string | fmt.Stringer
}

func ToString[T Stringish](v T) string {
    // with #45380 or something similar, this would be easier
    if s, ok := interface{}(v).(fmt.Stringer) {
        return s.String()
    }
    return reflect.Value(v).String()
}

and still have this work with a type that has underlying type string but overrides the behavior using fmt.Stringer.
What's more, XOR can be used to build OR using A | B = (A^B)^(A∧B), so I don't think making | mean XOR would even help.

I don't think it would be a problem.
The implementation behaviour of interface types should be conserved. It's for interface elements that it would change.
In other terms, interface elements are not interface types. The type sets they create are different notions.
So for constraint resolution, we could/should afford disjoint type sets.
Also, seems that combining constraints by embedding them behave differently for evaluation because type set union stems from constraint set intersection.
And the intersection of set unions in one case is different from the union of set intersections.

@Merovius, the solution to https://github.com/golang/go/issues/45346#issuecomment-822330394 seems clear to me: the type-checker should compute method sets (and operations) conservatively, rather than based on formula satisfiability.
And indeed, as proposed it does exactly that (emphasis mine):

Much as the type set of an interface type is the intersection of the type sets of the elements of the interface, the method set of an interface type can be defined as the union of the method sets of the elements of the interface. … The method set of a union element is the intersection of the method sets of the elements of the union.

From what I can tell, your comment assumes something more like, “The method set of an interface type is the intersection of the method sets of the types in the type set of the interface.” Note that the definition in the proposal is substantially weaker than that: the proposal does not allow the union operation to recover methods that are not retained by any of the intersection elements.

To address the specific examples in your comment:
type A = interface { A() }
type NotA = interface { A() int }
type B = interface{ B() }

type Constraint interface {
    A | NotA
    A | B
    NotA | B
}

func F[T Constraint](v T) {
    v.B()
}

Here, the method set of A | NotA is empty (because A and NotA have no methods in common), and the method set of A | B is empty (because A and B have no methods in common), and the method set of NotA | B is empty (because NotA and B have no methods in common). So the method set of Constraint is itself empty, because it is the union of three empty sets.
In order for that program to compile, it would have to use a type-assertion to add the method back:
func F[T Constraint](v T) {
	var vi interface{} = v
	vi.(B).B()
}

Similarly, in the example:
type Proof interface {
    Formula
    QED() string
}

type NoProof interface {
    QED() int
}

type Constraint interface {
    Proof | NoProof
}

func G[T Constraint](v T) {
    var _ int = v.QED()
}

Under the proposed rules, the method set of Proof | NoProof is empty, because the intersection of the method sets of Proof and NoProof is empty: NoProof has only one method (QED() int), which is not in the method set of Proof.

@bcmills

the solution to #45346 (comment) seems clear to me: the type-checker should compute method sets (and operations) conservatively, rather than based on formula satisfiability.

Yupp. I mention that the computed method set is empty, in the Constraint example :)
I take issue with this specific part of the proposal

The purpose of introducing type lists in the generics proposal was to specify the operations available to type parameters in parameterized functions. This is easy to define based on the idea of type sets. Given a type parameter P with a constraint C, a parameterized function is permitted to use an operation with a value of type P if the operation is permitted for every member of the type set of C.

I think that's not how we can define the operations available to type parameters. I believe we must a) explicitly list them for the primitive elements (that is, for ~T, T and interface types) and b) then describe how union elements and embedding (i.e. intersection of type sets) imply the available operations of their result (i.e. intersection and union of "operation sets" respectively).
I read the section on method sets as a consequence of the above definition, not as the proposed definition.
Note that even then, this doesn't entirely answer if the situation where a type-parameter is used to instantiate a different generic function is resolvable.

FWIW, I agree that the definition for the set of allowed operations for a type parameter should parallel the definition of method sets. I think they should both be defined in terms of intersection and union, rather than both in terms of “every member of the type set”.
Where the current proposal defines:

Given a type parameter P with a constraint C, a parameterized function is permitted to use an operation with a value of type P if the operation is permitted for every member of the type set of C.

perhaps we should instead define something like:

The operation set of an interface type is defined as the union of the operation sets of the elements of the interface. The operation set of a union element is the intersection of the operations supported by the elements of the union.
Given a type parameter P with a constraint C, a parameterized function is permitted to use an operation with a value of type P if the operation is in the operation set of C.

@bcmills And of course "the operation set of a method is calling that method, the operation set of an approximation element is the operation set of the approximated type and the operation set of a single type is [? TBD]". But yes :) That would be roughly my suggestion :)

Note that even then, this doesn't entirely answer if the situation where a type-parameter is used to instantiate a different generic function is resolvable.

You are referring to this example?
func F[T SomeConstraint](v T) {
    G[T](v)
}

func G[T SomeOtherConstraint](v T) {
}

This is indeed a hard problem if SomeConstraint and SomeOtherConstraint each comprise non-trivial unions of intersections, because it is equivalent to the question of whether the formula SomeConstraint ⋀ ¬SomeOtherConstraint is satisfiable.

Actually, wait, no. The specific rule in the proposal is:

a type argument A satisfies a constraint C exactly when A implements the interface type C, or, in terms of type sets, exactly when A is a member of the type set of C.

There is no SAT problem there, because the rule is not “when every type in the type set of A is a member of the type set of C” — the rule is “when A [itself] is a member of the type set of C”.
So in that example, T satisfies SomeOtherConstraint only if SomeConstraint itself is in the type set of SomeOtherConstraint, which is much more restrictive.
But perhaps that is a problem in the other direction. For example, it does not allow interface { int8 | int16 } to satisfy interface { int8 | int16 | int32 }, because the type interface { int8 | int16 } is not itself in the type set of interface { int8 | int16 | int32 }.

Yes, that's what I'm referring to. I'm still thinking about it, but I'm not certain it'll be a problem.
In think the operation set (as we defined it) can be efficiently computed explicitly from the interface. So we might just be able to say "A type parameter T can be used to instantiate a generic function/type with constraint C, if the operation set of C is a subset of the operation set of T". If both can be computed and are finite (!), the subset compare is not hard to do.
The reassuring aspect of this is that the "operation set" is given in DNF, so to speak. Interface element operations are unioned and union element are intersected, so it's an OR of AND terms. SAT on DNF is "easy" to solve. Further, I'm not even sure we can get a NOT in there under the rules we talked about (I think that's kind of why all the method sets collapsed into empty sets).
So if I was a betting man, my intuition right now is that this will be possible to do. But I'll think about it further :) And maybe the compiler team proves its possible by just writing down an algorithm in the meantime :)

Afterthought: the union elements don't need to be discriminated.
Operation sets as you define them are just different from type sets.
For the nested instantiation issue, why would you need a subset? I think that the constraining is just scoped to the body of the function where the type parameter is used.
Same type parameter but different constraints, (scoped) operation set?

Maybe I'm mistaken, but it seems to me that the interesting question here is whether "invoking a method" is "an operation".  That wasn't the intent.  If we define calling a method as only being permitted if the method is in the method set, do we still have a problem?

@ianlancetaylor, even if we treat method sets as disjoint from operators, it is still a hard problem to determine whether a type parameter satisfies the constraint of another generic declaration. Given a type parameter P with a constraint C1, and a generic declaration such as func G[Q C2](…), then in order to allow G[P] we need to determine whether P is (guaranteed to be) in the type set of C2. (Otherwise, when P is instantiated with an actual type argument, we may discover that it fails to satisfy C2, and then we will need to report a stack of instantiation errors rather than just the topmost one — a situation possible in C++ that we are attempting to avoid in Go.)
The naive algorithm is to expand out the constraints of C1 into disjunctive normal form (DNF). A type in DNF has at most one union element, found at the root of the type. We iterate over the elements within that union, and if each element of DNF(C1) implements C2, then the parameter P itself implements C2.
Unfortunately, the disjunctive normal form of a formula (in this case a type) is in the worst case exponentially larger than the original. (I would be surprised if that becomes a problem in practice, because typically our types will contain at most one union element in the first place; however, if we want to avoid exponential-time costs even in theory, we must either make some additional restriction, or defer at least some part of constraint-checking to instantiation time.)

What would happen if we restrict interface types to have at most one union element or at most one approximation element, but not both?  Would that forbid any constraints that we really want to be able to write?

I'm not sure how bad it would be to restrict interface types to at most one union. (I'll have to chew on that one.) 🤔
At the very least, I think it would be reasonable to allow an implementation to restrict the size of the DNF-expansion of a type to within a reasonable bound (that is, to promote a type-checking explosion from “unexpectedly slow” to an outright error), or to allow an implementation to fail to diagnose a constraint error within a parameterized declaration (that is, to avoid the type-checking explosion by deferring the check to instantiation-time).

@ianlancetaylor I think this either unreasonably restricts constraint-embedding or wouldn't help, because you can put them in separate interfaces that you embed separately. At least if I understand you correctly.

I had managed to confuse myself. The method set rule seems just fine. @merovius example was a special case where, if we computed disjointness of union members, we could figure out that B is satisfied.
But in the general case where A and NotA have different but not conflicting method sets, B may or may not B satisfied. So the method set rule provides the correct answer (empty method set).
Can't even force disjointness as I was arguing for as
interface{
    interface{ String()} | interface{Num()}
    Num()
    String()
}

should be valid (any type implementing both methods.
So just to say that it's fine as it is.
For the nested parameterized declarations, my hunch would be to defer to instantiation-time but that leads to some other issues.
The alternative seems amenable for combinatorial explosion too easily.

@ianlancetaylor

If we define calling a method as only being permitted if the method is in the method set, do we still have a problem?

If you still define "operation" the old way, I'm not sure. So far, we've only really talked about the case with a single type parameter. I could imagine that it's possible to break things by using multiple variables and throwing some concrete types into the mix. For example, if all the True constraints have an ~int element in it and all the False cases have a ~string, you can still probe whether a type constraint must be True or False by converting them to a plain int/string respectively. So you'd a) use one type parameter per variable and b) use a parametric interface type to express their relationships and then c) use + in this way to probe for truthiness.
Of course this is handwaving quite a bit. If need be, I can try to make a real argument, but these always take some time to come up with and write down. My hunch would be that there's still some form of this problem there.
I genuinely think that if we have to talk about "all elements" of a type set, we should assume that we might get into trouble.

What would happen if we restrict interface types to have at most one union element or at most one approximation element, but not both? Would that forbid any constraints that we really want to be able to write?

Does that include embeddings (by name)? If it doesn't, I'm not sure it would help - I'd assume you can generally add a layer of indirection by embedding multiple constraints with one element each. If it does, I would find that unfortunate. It would mean that you can't reliably embed existing constraints, unless you are certain that doesn't create such a clash. It at least takes a lot of the conceptual simplicity I like from this proposal.
I still think we can mostly preserve the proposal if we are deliberate about what an "operation" is and then construct explicitly computable operation sets. And I feel we've found enough edge-cases with the term "operation", that it's justified to really sit down and think about it at this point anyway.

Like @atdiar If find all this getting extremely complex and confusing, which I think is due to introducing set theory into the mix. With set theory, we run the risk of Russel's paradox, namely to be unable to see if a type is or is not an element of a type set, because it may include itself. Furthermore, a complete set implementation leads to a system that is homologous to Zermelo–Fraenkel set theory, which is Gödel incomplete, and therefore also Turing complete, which we definitely want to avoid.
In other words, we have to place in some restriction to get a less powerful system in order to avoid confusion for the users of go, and to avoid a slow running compiler that has to try to solve the halting problem.

I think that restricting interface types to have at most one union could work OK.
In fact, I think it could help readability quite a bit. The problem with the current proposal isn't just algorithmic complexity - it's understandability of the code: it would hard for a user to work out what operations are allowed given an intersection of several unions.
By restricting interface types to have at most one union, it's easy (for users and the compiler) to enumerate all the members of that union, and hence what operations are supported by the intersection of those members.
I haven't yet managed to think of a circumstance where I'd need to be able to embed more than one union interface independently.

@jimmyfrasche

So instead of
type ABCD interface {
  interface{ A() } | interface { B() }
  interface{ C() } | interface { D() }
}

you'd have to write
type ABCD interface {
	interface { A(); C() } |
	interface { A(); D() } |
	interface { B(); C() } |
	interface { B(); D() }
}

That seems like a huge increase in readability to me, despite the increased verbosity.
I'd like to see real-world use cases for this kind of thing.

@jimmyfrasche makes a good analysis. But my key takeaway from that is: restricting types to a single union does not actually help much of anything, because users can already write their own code generators that expand out the desired conditions into disjunctive normal form. That is: if we forbid users from writing code that requires exponential-time processing, they may just write exponentially larger code. 😅
If the counter-argument to that is “of course the compiler can reject source code that is too large”, then by the same argument the compiler ought to be able to reject type definitions that expand to be too large.
So the only benefit I can see to restricting types to a single union is that it makes the increase in compile times marginally easier to explain in terms of code size: we can say “the build is slow because it  is compiling a large quantity of source code” instead of “the build is slow because it is compiling source code that requires a lot of analysis”.
But that comes at a significant cost. If we just place an implementation restriction on the expansion of a type, then down the road we can decide to dramatically improve compile times by deferring error-reporting to instantiation. In contrast, if users have already worked around our restriction by expanding the code to normal form, we no longer have that option: we can't speed up the code by deferring expansion, because that expansion is already reflected in the source code.
Taking all that into account, I think it would be unwise to restrict types to at most a single union element.

@beoran

If find all this getting extremely complex and confusing, which I think is due to introducing set theory into the mix. With set theory, we run the risk of Russel's paradox, namely to be unable to see if a type is or is not an element of a type set, because it may include itself.

Note that this proposal can be expressed using type theory, which is a standard tool for reasoning about types systems for programming languages and which specifically avoids Russel's paradox (thanks in part to work by Russel himself!) by distinguishing between “types” and “sets of types”.

That is: if we forbid users from writing code that leads to exponential-time processing, they may just write exponentially larger code.

Having the compiler run time depend on the size of the input code seems like usual behaviour to me. People can already generate huge code if they want to. My question would be: why would people want to do this kind of thing in practice?

Having the compiler run time depend on the size of the input code seems like usual behaviour to me.

The compiler presumably already has at least a few operations that are not strictly linear in the size of the source code. (For example, compare #30077, or #25120, or #19751, or #19379.) Why should we require linearity in this case but not everywhere?

People can already generate huge code if they want to. My question would be: why would people want to do this kind of thing in practice?

I think that is exactly the point! 🙂 In practice, people will want to intersect small interfaces in fairly trivial ways, so in some sense it doesn't matter what the worst-case behavior is. (For comparison, type checking Standard ML is hyper-exponential in the worst case, but the only programs that trigger it in practice are the ones written by PL students who are assigned the task of specifically doing so.)

I think that is exactly the point!  In practice, people will want to intersect small interfaces in fairly trivial ways, so in some sense it doesn't matter what the worst-case behavior is. (For comparison, type checking Standard ML is hyper-exponential in the worst case, but the only programs that trigger it in practice are the ones written by PL students who are assigned the task of specifically doing so.)

Can you point to a good real example where we'd want to intersect union interfaces?

Like @atdiar If find all this getting extremely complex and confusing

Admittedly, I have a tendency to get confused a bit too fast sometimes. The case was about when the coder has more information than the algorithm in question which is a fair point that occurs often in programming (do you nil check when you know that something will never be nil?)
Here, the algorithm does not check that constraints are disjoint. Only the coder may know and realize that with that added information, some precision could be gained.
I don't see the advantage in being restrictive here. We could and then decide to lift the restriction later.
But the issue is not necessarily unions intersections, the issue is embedding interface that are unions.
And more than that, it only really get truly confusing with interfaces with infinite type set (our regular old go interfaces).
Now the computation of operator sets may need the same treatment as method set computation so that people can mentally check which operators are permitted. I would expect the rule to be similar.

FWIW I don't understand why we are talking about placing arbitrary limits on constraints like that. To me, what @bcmills and I have been discussing (namely, defining allowed operations as explicitly computed sets) seems, so far, a perfectly workable solution to me, which preserves the syntax and expressive power of this proposal. There is the concern about instantiating a generic function/type using a type-parameter, but while there was concern, I don't think anyone so far really came to the conclusion that it's unworkable.
Apparently I missed something, but I'm not sure what.

I don't understand why we are talking about placing arbitrary limits on constraints like that

Assuming you're talking about the single-union limitation, I was talking about it because @ianlancetaylor mentioned it as a possibility and it seems to me like a promising avenue.

The more I think about the complexities of sets, the more it seems to me that they are specific only to method calls.
So let's just say that a method call on a value of generic type is only permitted if the method is in the method set of the constraint.
Other operations are based on the type set, as before.
Then for purposes of determining whether an operation is permitted an interface type supports no operations, and an approximation element ~T supports the operations of T.  We don't have to worry about the fact that their type sets are infinite, because that is irrelevant when deciding whether an operation is supported or not.

The more I think about it the more I think that:

a single concrete type or approximation element should be considered a union with one member
there should be a restriction to at most a single union per interface

It makes it simpler to reason about interface elements without losing any theoretical power.
As @bcmills points out it means that someone who wants to express something extremely complex will have to do it by writing a lot of code but that's good simply because the complex code will look complex. (Any implementation defined limit here is going to be far past the human brain defined limit, anyway).
It will also makes something like #41716 easier as otherwise what would it mean to have an interface with two unions in it?

It will also makes something like #41716 easier as otherwise what would it mean to have an interface with two unions in it?

The same as it means to have a constraint with two unions in it: Any type in the intersection of the two unions is valid.
I genuinely don't understand the question.

FWIW, I'm not super motivated to iron out the details right now, so this comes with an even larger grain of salt than the other cases. But I'm pretty sure the issue is not limited to methods. For example, if we think field accesses are an "operation" (and follow the usual rules) we can do something like
type True struct{ IsTrue struct{} }
type False struct{ IsFalse struct{} }

type Var interface {
	True | False
}

// Not[T] has an IsTrue field, if T is False and a IsFalse field if T is True, by exploiting ambiguous
// field names - whichever field is present in T, will be duplicated at the same depth.
type Not[T Var] struct {
	True
	False
	T
}

type Formula[A, B, C, D Var] interface {
    A | Not[B] | Not[C]
    Not[A] | C | D
}

func Proof[A, B, C, D Var, F Formula[A, B, C, D]](f F) {
    _ = f.IsTrue
}

go2go playground link to demonstrate Not[T]
We don't have ¬¬A⇔A with this construction, but I don't think we need it - all we need is to be able to express the boolean formula in CNF, so one ¬ is enough.
As I said I haven't really sat down and actually convinced myself this really works as a proof. I think it can likely be made into one in any case. And as I said, doing full writeups takes time and effort. And I'm not really motivated to do it for every revision, while feeling pretty strongly that the systematic issue is still that we should stay clear of having to talk about "all elements of a type set". And also feeling there's an option on the table which seems reasonably easy to implement, is computationally efficient and preserving ~all of the expressive power we care about.
Note, again, that if we are talking about operation sets, the operation set of Var is empty (or rather "the operations allowed on all types", whatever they are), thus so is the operation set of Formula, thus the problem goes away.
Of course, if there are reasons why operation sets can't be made to work, I'm all ears :)

Then for purposes of determining whether an operation is permitted an interface type supports no operations, and an approximation element ~T supports the operations of T. We don't have to worry about the fact that their type sets are infinite, because that is irrelevant when deciding whether an operation is supported or not.

Agreed. Reason is that users cannot add operations to the types they define. So it's much simpler to reason about for operators.

@Merovius is your parametric Not type supposed to compile? Isn't there a problem with duplicate fields.
Perhaps you meant to write your constraint
type Var interface{
   ~True | ~False
}

?

FWIW I don't think it matters whether or not a type set is infinite or not. On the contrary, infinite type sets prevented the NP-completeness proof of determining if a type set is empty until I thought of the TertiumNonDatur idea to reign them in (they are still infinite, but might as well not be - we could pretend that the Go type system only supports a single concrete type with an arbitrary set of methods and the proof still works). None of my constructions so far relied on ~T (I think).
The possible assignments to a boolean formula are finite - but they are large. If every type parameter can take one of two possible options, any representation of N type parameters (which can all be arguments of one generic type, whose "operations" we have to determine) will have to consider 2^N different states. It doesn't really matter whether those individual states are infinite, but can be finitely represented (such as ~T). If the compiler needs to proof something about "all possible types of a type set" it thus has to consider these 2^N possibilities somehow.
At least that's what I think. Again, I can't reasonably claim to know more or be more experienced than others in this thread, so maybe I'm just plain wrong.

@atdiar The duplicate fields are the point. They give a way to run the computation at compile time - if compilation fails because of duplicate fields, the type must evaluate to the other boolean value. See the go2go playground link - the error messages appear exactly on the lines with wrong assertions.

@Merovius Note https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#embedded-type-parameter-methods

Yes but for Not[T] to exist, we could require that it needs to be valid for every member of the union in Var.
Again, when the union has a member which denote an infinite type set, that could be a bit less obvious but not sure it is problematic.
Following your example
type SuperTrue True

type Var interface{
    ~True | ~False
}

type Not2[T Var] struct{
     SuperTrue
     T
}

SuperTrue is in the typeset of ~T if I'm not mistaken.

Not2 may be invalid if T = SuperTrue... Rest of the time it's fine.
If the rule has the same granularity as the method set rule, this will be deemed invalid.
That's also where we might want to express negation to say that SuperTrue is not an accepted value for the type parameter.

Again, that's if I have not made any flagrant mistake.
That's what I was trying to refer to when mentioning infinite type sets.


Production AoT builds are throwing error with 0.8.2

After upgrading to 0.8.2 production build with AoT in Angular CLI, it throws the below error:
ERROR in Unexpected value 'ImageCropperModule in /node_modules/ng2-img-cropper/index.js' imported by the module 'FileUploadModule in /src/app/shared/file-upload/file-upload.module.ts'. Please add a @NgModule annotation.                                                                                ERROR in ./src/main.ts                                                                                Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory' in '\src'    @ ./src/main.ts 4:0-74                                                                                @ multi ./src/main.ts
Can you please have a look?

I am having a similar error to @naveedahmed1:
ERROR in Can't export value ImageCropperModule in /Users/agustin/Documents/projects/surfercoder/arc10/node_modules/ng2-img-cropper/src/imageCropperModule.d.ts from SharedModule in /Users/agustin/Documents/projects/surfercoder/arc10/src/app/shared/shared.module.ts as it was neither declared nor imported!
ERROR in ./src/main.ts
Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory' in '/Users/agustin/Documents/projects/surfercoder/arc10/src'
@ ./src/main.ts 4:0-74
@ multi ./src/main.ts
And the module is there and it is being exported.
Besides that, could you please run linter on your code? I am getting several warnings too :)
Thanks in advance.

getting this error after update to angular 4.0.0

have the same issue with angular 4.0.0.
I could do ng serve but noway with ng build --aot --prod
ERROR in Unexpected value 'ImageCropperComponent in C:/Users/huajinso/sakura/nod
e_modules/ng2-img-cropper/src/imageCropperComponent.d.ts' declared by the module
'AppModule in C:/Users/huajinso/sakura/src/app/app.module.ts'. Please add a @Pi
pe/@Directive/@Component annotation.

Using version 0.8.1 make everything work again. We had same issue with AOT....and also after we upgraded to 4.0.0 - see my other comment #157

Working on it!

@adamkundrat
Thank for suggestion.
I roll back to 0.8.1.
It resolves this issue.
But I have to use  ImageCropperModule instead of ImageCropperComponent in @NgModule to avoid duplicate declaration issue.

Should be fixed in 0.8.4

I think it is still not working. I get this error
ERROR in Unexpected module 'ImageCropperModule in C:/dev/enum/dubrovnik-eye/Angular2/node_modules/ng2-img-cropper/src/imageCropperModu
le.d.ts' declared by the module 'SharedModule in /shared.module.ts'. Please add a @Pip
e/@Directive/@Component annotation.

Same issue here, using 0.8.8 (Also tried 0.8.1 and 0.8.4)

Is there any progress on this? It is still failing for me too. Tried 0.9, 0.8.1

Any update / or thoughts on how to work around this?  Still seeing with 0.10.4...


PCA Instability Fix

/intelci: run

@agorshk Run-to-run are passed. But I haven't switched on them yet. I'm going to do it right now


Use relative path for rosrust dependency in rosrust_msg

I was trying to use a relative path to a fork of rosrust and rosrust_msg in my own project, but rosrust_msg was pulling in the official version of rosrust and generating this confusing error:
error[E0277]: the trait bound `rosrust_msg::rosgraph_msgs::Log: rosrust::Message` is not satisfied
...

Making rosrust_msg use the local rosrust via a relative path fixes that, and makes it consistent with the other Cargo.tomls here that all use relative paths.

This is also being handled by #161 , thanks for the PR


When assigned to more than one user, only remembers one (original?) when editing task

Describe the bug
I was delighted to see that you can assign a task to more than one user. I was a bit less delighted when only one of them is shown when editing the task again :)
To Reproduce
Steps to reproduce the behavior:
Create a task.
Assign it to user A.
See that it's shown as assigned to A.
Edit it and using ctrl-click, assign it to user B as well.
See that it's shown as assigned to both.
Edit it again - only A is selected.
Expected behavior
When editing a task, all the currently assigned users be selected in the assign box.

It's ok with another one, but that was assigned to two people from the start.


Can I use ltchiptool/UPK2ESPHome with esphome-kickstart on ESP8266 devices?

I just realized one of my Tuya devices is actually ESP8266-based.
I'm wondering if I can use UPK2ESPHome to help me generate the ESPHome YAML for my device.

No. ESP devices use a completely different format of the configuration data. Same goes for RTL8710B, where the format is similar to the one of ESP devices.
Currently only the Beken format is supported in upk2esphome.

I see. Thank you!

However, it could be possible to at least display UPK data in ltchiptool. That would show you the GPIOs, but you'd have to write the config manually anyway.
I don't know what format do ESP binaries use. If you drop a full firmware dump onto UPK2ESPHome in ltchiptool, it will try to read it. It won't generate any valid configuration, but it will show the raw data in Source data tab (if the storage encryption format is the same, that is).

Ok, I will try to get the original firmware dump. I lost it for this device, but I think I have another unit.
Out of curiosity, this is what happens when I try with kickstart:

Unfortunately it also doesn't work with the firmware dump:

I was able to obtain the dump using Tasmotize. I'm attaching here in case it's any useful.
backup_20241129_180115.bin.txt
After downloading, remove the .txt extension from the file name.

There doesn't appear to be any pin configuration present in that dump.

Well, turns out my device actually uses a Tuya MCU. Would that explain it? The Tuya MCU pins are:
uart:
  rx_pin: GPIO13
  tx_pin: GPIO15

Yes, TuyaMCU doesn't have device configuration, as those UART pins are programmed into the firmware.

@Cossid, sorry, I know this is completely unrelated, but, do you happen to know if I can extract any useful information from the dump regarding MCU communication?
I have an odd issue when running ESPHome on this device, which I'd love to fix: https://github.com/esphome/issues/issues/6503

I'll respond in the ESPHome thread, as it make slightly more sense there.


[Request] Change tab name to the name of the collection

I able very pleased with CosmosDbExplorer - well done! So much better than the Azure Portal experience. I opened my instance and started viewing a few collections. I quickly realized that it was difficult to tell which collection I was viewing unless I check the data. It would be nice to give an indicator of which collection belongs to which tab. Maybe change the tab name to the collection name?
Thanks again!

I downloaded the source and found where I could change the tab title and it seemed to work, but this is my first time looking at WPF code, so I'm not sure if it's the best place:
public override async void Load(string contentId, DocumentNodeViewModel node, CosmosConnection connection, CosmosDatabase database, CosmosContainer container)
        {
            ContentId = contentId;
            Node = node;
            Connection = connection;
            Container = container;
            PartitionKey = container.PartitionKeyPath;

            //var split = Node.Parent.Container.SelfLink.Split(new char[] { '/' });
            ToolTip = $"{Connection.Label}/{database.Id}/{Container.Id}";

			// *********************************************** HERE
            Header = Container.Id;
			// *********************************************** HERE

            AccentColor = Connection.AccentColor;

            _cosmosDocumentService = ActivatorUtilities.CreateInstance<CosmosDocumentService>(_serviceProvider, connection, database, container);

            await LoadDocuments(true, new CancellationToken());
        }

Thanks so much!


Pulsed events on V2 Hardware

Wasn't sure where to raise this. I've added a note to
https://github.com/microsoft/pxt-microbit/issues/3731
However, on my V2 Microbit the pulse events aren't working. For examples
control.onEvent(EventBusSource.MICROBIT_ID_IO_P0, EventBusValue.MICROBIT_PIN_EVT_PULSE_HI, function () {
    led.toggle(0, 2)
})
control.onEvent(EventBusSource.MICROBIT_ID_IO_P0, EventBusValue.MICROBIT_PIN_EVT_PULSE_LO, function () {
    led.toggle(1, 2)
})
pins.setEvents(DigitalPin.P0, PinEventType.Pulse)
basic.forever(function () {
    if (pins.digitalReadPin(DigitalPin.P0) == 1) {
        led.plot(0, 0)
    } else {
        led.unplot(0, 0)
    }
})
basic.forever(function () {
    pins.digitalWritePin(DigitalPin.P1, 0)
    basic.pause(500)
    pins.digitalWritePin(DigitalPin.P1, 1)
    basic.pause(500)
})

The code works as expected on V1 hardware. Note, that if the event type is changed to edge detection, then that works fine on V1 & V2. So it looks like just the pulse is not working

Hi @markb139
Thanks for the bug report. This looks like a bug either here in codal or in the MakeCode layer that consumes the codal APIs... so quite possibly the right place to raise this issue.
Can you please describe the behaviour that you see? Do you receive any events at all?
I wonder if this is related to the fact that you are measuring both HI and LO pulses at the same time? Does it make any difference if you register for a single PULSE event (either HI or LO) ?

On the V2 microbit I don't receive the onEvent for either HI or LO. On my V1 microbit the LEDs flash after the pulses.
I just tried just the HI and then just the LO onEvent handlers. Still not working on the V2 microbit

Thanks @markb139.
Yes, looks like an initialization bug crept in when the NRF52Pin::getPulseUs() method was introduced recently. Fixed in here:
https://github.com/lancaster-university/codal-nrf52/commit/6a8f1d5effdef4a177b6e8cbed91042a619bcd64
This code now works as expected C++ side:
void onHi(MicroBitEvent e)
{
    DMESG("P0: PULSE HIGH");
}
void onLo(MicroBitEvent e)
{
    DMESG("P0: PULSE LO");
}

void
pulse_event_test()
{
    uBit.io.P1.setPull(PullMode::Up);
    uBit.io.P1.eventOn(DEVICE_PIN_EVENT_ON_PULSE);

    uBit.messageBus.listen(uBit.io.P1.id, DEVICE_PIN_EVT_PULSE_HI, onHi);
    uBit.messageBus.listen(uBit.io.P1.id, DEVICE_PIN_EVT_PULSE_LO, onLo);

    while(1)
        uBit.sleep(1000);
}

This fix should work its way through in the next update.

The C++ version of my code works now. Thanks

cool - thanks for confirming.


make plotly scatter plot have a 1:1 aspect ratio

Want the plot to use the same amount of x and y space.

Probably tricky to do while remaining responsive. We hacked around it for bokeh but it was fragile.

at least the grid lines are 1:1

I think the issue is related to legend width. What do you mean 1:1 for the most part? Grids are 47x47 pixels on my screen.

I think the issue is related to legend width. What do you mean 1:1 for the most part? Grids are 47x47 pixels on my screen.

Just the first column from -1?? to 200, so not so important


Petition

Add animepahe scrap link batch to download externally if possible...

Hi @Sunderlevi, if I understand your suggestion correctly, it's been done already in the script. You could use the -l flag:

-l                      optional, show m3u8 playlist link without downloading videos

Can give me an example command line

Hi @Sunderlevi, if you see 403 error, please check the request header. The referer header is required Referer: https://kwik.cx/. Check out README.md if you need an example using mpv.


[BUG] index rollover - doesn't create all aliases in the newly created index

What is the bug?
When using ISM rollover and the index contains multiple aliases, only the alias configured for rollover is created in the new index.
Other aliases existing in original index (before rollover) are not copied.
How can one reproduce the bug?
Steps to reproduce the behavior:

create ism policy and index template such as:

PUT  _index_template/rollovertests_template
{
  "index_patterns": [
    "rollovertests-*"
  ],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "plugins.index_state_management.rollover_alias": "rolloveralias"
    },
    "mappings": {
      "_routing": {
      "required": true
      },
      "properties": {
        "timestamp": {
          "type": "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
      }
    }
  }
}

PUT _plugins/_ism/policies/rollovertests_policy
{
  "policy": {
    "description": "test rollover policy.",
    "default_state": "rollover",
    "states": [
      {
        "name": "rollover",
        "actions": [
          {
            "rollover": {
              "min_index_age": "2m"
            }
          }
        ],
        "transitions": []
      }
    ]
    ,
    "ism_template": {
      "index_patterns": ["rollovertests*"],
      "priority": 100
    }
  }
}

create index with more than one alias:

PUT rollovertests-000001
{
  "aliases": {
    "rolloveralias": {
      "is_write_index": true,
      "filter": {
        "term": {
          "timestamp": "1574641891142"
        }
      },
      "routing": "12345"
    },
    "anotheralias": {
      "filter": {
        "term": {
          "timestamp": "157464189133"
        }
      },
      "routing": "12345"
    }
  }
}

After rollover, check the newly created index:

GET rollovertests-000002

Result:
{
  "rollovertests-000002": {
    "aliases": {
      "rolloveralias": {
        "filter": {
          "term": {
            "timestamp": "1574641891142"
          }
        },
        "index_routing": "12345",
        "search_routing": "12345",
        "is_write_index": true
      }
    },...

As you can see, the alias 'anotheralias' is missing from the new index 'rollovertests-000002'.
What is the expected behavior?
The alias 'anotheralias' should be copied to the new index upon rollover.
In fact, all existing aliases with all of their properties (routing, filter,...) should be copied.
The _rollover API supports that by providing the "aliases" part in the body. But the ISM just doesn't call it properly.
As a proof, I can do the rollover manually, and then I get the expected results:
POST rolloveralias/_rollover
{
  "aliases": {
    "anotheralias": {
      "filter": {
        "term": {
          "timestamp": "157464189133"
        }
      },
      "routing": "12345"
    }
  }
}

GET rollovertests-000002

Result:
{
  "rollovertests-000002": {
    "aliases": {
      "anotheralias": {
        "filter": {
          "term": {
            "timestamp": "157464189133"
          }
        },
        "index_routing": "12345",
        "search_routing": "12345"
      },
      "rolloveralias": {
        "filter": {
          "term": {
            "timestamp": "1574641891142"
          }
        },
        "index_routing": "12345",
        "search_routing": "12345",
        "is_write_index": true
      }
    },...

What is your host/environment?

OS:  Linux
Version 2.5  (AWS Opensearch)

This doesn't seem to be a bug. Like you said, even using rollover API, you need to explicitly define the aliases you want on the new index.
If the alias you want to add is a static one, I think maybe index template is what you want https://opensearch.org/docs/latest/im-plugin/index-templates/. You can define the template to add alias to the matched new index.
But if it's a dynamic one, like I can see timestamp field in the alias, if that's the time of when rollover API is called, then this could be a new feature to add.

@bowenlan-amzn ,
I'll try to better explain why it is a bug.
The timestamp is just an example for a field. As you already observed, this is dynamic alias that can contain any filter and any field.
In my index, I maintain several dynamic aliases - each with a different filter.
When ISM is calling rollover API automatically for me, then it makes no sense to rollover a single alias. Instead, it should rollover (or copy) all aliases of the index.
The ISM plugin should call the existing rollover API properly, with all available options.
If it doesn't do it, then I'll have to copy all aliases by myself, which makes the automatic rollover pretty much useless for the case of multiple aliases.
In case of a single alias, then it will behave as today.
Thanks.

@bowenlan-amzn ,
When I wrote "dynamic", I didn't mean that alias should be modified during rollover.
I meant that the alias is dynamic and changes over time, before rollover occurs.
I create index aliases and update their filters dynamically, and therefore index template is irrelevant in this case.
Example for index lifecycle:

create an index rollovertests-000001 with rollover alias rolloveralias.
After some time, add another alias anotheralias to the index.
After some more time, update the filter of anotheralias by API.
After some more time ISM performs a rollover according to predefined conditions.
At this point, I expect both aliases to be part of rollovertests-000002 with the exact properties as they were in rollovertests-000001.

I hope now it is more clear and that you see why it is a bug and not a new feature.
I expect ISM to provide all aliases (if exists) to the rollover API call, just as I demonstrated above. Effectively, it will copy all aliases to the new index.
Thank you.

To move forward, we need to enhance the rollover action https://opensearch.org/docs/latest/im-plugin/ism/policies/#rollover
Enable user to define another field, copy_alias, and default to false to keep existing user's experience un-changed
In the code, before executing rollover https://github.com/opensearch-project/index-management/blob/main/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollover/AttemptRolloverStep.kt#L116
Add logic to handle coping alias.
@oridool do you want to help implement this function?

@bowenlan-amzn ,
Are you sure we need an additional flag? I think that if someone has created additional aliases for the index then it is the natural thing to copy existing ones into the newly created index. Or at least the flag default should be true IMO.
Unfortunately I'm not familiar with Kotlin or the Opensearch project specifically, and therefore it won't be effective for me to implement it. Appreciate if you can take it or someone else who is more familiar with this project.
Thanks.

I do not believe that the default setting would be the user's preference. In my understanding, an alias serves as a namespace for a set of indices (index patterns). Therefore, if the user wishes to assign an alias to a newly created index within a specific index pattern, this can be achieved through an index template, as previously proposed. It should also be noted that Rollover is an operation performed on an alias in order to create a new index that also conforms to the specified index pattern (since only the suffix number increases).
So in your use case, apart from updating the alias of one index, you can also update the alias in the index template so it works for all the newly created index. Let me know if you see any issues with this approach.

@bowenlan-amzn ,
I thought we already agreed on the correct solution: upon rollover, the ISM job should provide all existing aliases to be copied to the new index.
What you are now suggesting won't work for the use case I wrote.
"an alias serves as a namespace" - not exactly, an alias is like a view on the index (if I try to compare OpenSearch to RDBMS).
"this can be achieved through an index template" - this is not correct. In index template you can only provide a static pre-configured alias, while some people create the aliases after the index is created or modifying an existing alias filter.
Please let's go ahead with what you already offered here: https://github.com/opensearch-project/index-management/issues/734#issuecomment-1508930781


SCSS: Move from import to use

See https://sass-lang.com/documentation/at-rules/import

Never mind: It's on the roadmap, but hasn't made it into libsass yet, much less node-sass, much-much less nwb-sass.


[Feature] Ensure comment_node exists before trying to access is_registration [OSF-7056]

Purpose
comment_node did not always exist before comment_node.is_registration was checked. This error caused comment replies not to be deleted as it failed out.
Changes
Initialize comment_node to false and make sure comment_node exists.
Side effects
None known.
Ticket
https://openscience.atlassian.net/browse/OSF-7056
This might also solve: https://openscience.atlassian.net/browse/OSF-6994
[#OSF-7056]

Yay tests! :tada::penguin::tada:


S3.py raises a string Exception which is not allowed

In mongodb_consistent_backup/Upload/S3/S3.py, the S3 class raises a string exception in its __init__ method which is currently on line 50. I presume you want this to be:
raise OperationError("Invalid S3 security key or region detected!")
rather than just:
raise "Invalid S3 security key or region detected!"

Thanks @rob256, this will be in the 1.0.4 release. See the branch '1.0.4' for progress

Fixed and merged into 1.0.4. Closing as this will be released soon.
https://github.com/Percona-Lab/mongodb_consistent_backup/pull/168


ASM: cannot start a customer session with uppercase chars

Environment Details
Spartacus: epic/asm-mvp-1
Browser: Chrome
Device: mac
Happens on: http://dev-com-2.accdemo.b2c.ydev.hybris.com:4200/electronics-spa/en/USD/?asm=true
Steps to Reproduce

Have an existing customer with all lowercase chars, eg<EMAIL_ADDRESS>Login as asagent/12345
In the customer field, type<EMAIL_ADDRESS>
Actual Result
An error message that the user doesn't exist is displayed.
However, if you login with<EMAIL_ADDRESS>from the login page, it works.
Expected Result
It should work for asm too.

PR #4832

Tested on dev2. Works now. Can find a customer using either uppercase or lowercase chars.


Allow to run commands one after another

This can be done with https://github.com/EngincanV/jonturk-cli/issues/1, by defining the pipeline in the generated script. Or as an alternative, we can introduce a new command to chain commands one after another or update the existing save command:
For example:
jonturk save -n name1 -c "my-command" --chains "my-command-2" "mycommand-3"

Then, whenever we want to run the name1 command, CLI asks if we want to run all commands in the chain (or a parameter can be passed such as --run-all) or only run the main command:
jonturk run -n name1 --run-all

-c and --chains stands for the same purpose, Instead of different parameters how about defining a single array property in command class?
[CommandOption("commands", 'c')]
public string[] Commands { get; set; }  = Array.Empty<string>();

This makes more sense 👍

Also, there can be an option in the Run Command for sequentially running and parallel running.
# Runs all commands parallel
jonturk run -n name1 --run-parallel

# Runs all commands sequentially chained.
jonturk run -n name1 --run-chained

# Also this can be default behavior without parameter:
jonturk run -n name1

I have also considered these three options, the first and second one seems really helpful and I'll implement them. But for the third one, I'm not sure, because when I first created the issue, I imagined the chained commands as optional commands but I'll also consider that, it might be even better to make its default behavior and pass an option to just run the main one (such as --run-only-main).


C support in markdown

VSCode Version: 1.21.1
OS Version: macOS

struct proc_struct {
    enum proc_state state;                      // Process state
    int pid;                                    // Process ID
    int runs;                                   // the running times of Proces
    uintptr_t kstack;                           // Process kernel stack
    volatile bool need_resched;                 // bool value: need to be rescheduled to release CPU?
    struct proc_struct *parent;                 // the parent process
    struct mm_struct *mm;                       // Process's memory management field
    struct context context;                     // Switch here to run process
    struct trapframe *tf;                       // Trap frame for current interrupt
    uintptr_t cr3;                              // CR3 register: the base addr of Page Directroy Table(PDT)
    uint32_t flags;                             // Process flag
    char name[PROC_NAME_LEN + 1];               // Process name
    list_entry_t list_link;                     // Process link list
    list_entry_t hash_link;                     // Process hash list
};

int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
    int ret = -E_NO_FREE_PROC;
    struct proc_struct *proc;
    if (nr_process >= MAX_PROCESS) {
        goto fork_out;
    }
    ret = -E_NO_MEM;
}

Just try these code snippets above and you will see the problem. I'm using default Markdown engine from VS Code. The problem is, it doesn't match the real coloring from the VS Code and somehow it's not the same as it's by default. Markdown seems to have bad color scopes.
Markdown

C

Duplicate of #34525. See the discussion and links on https://github.com/atom/language-c/issues/250 for why this is difficult to fix