This is an archive of the discontinued LLVM Phabricator instance.

[clang] template / auto deduction deduces common sugar
ClosedPublic

Authored by mizvekov on Oct 6 2021, 6:09 PM.

Details

Summary

After upgrading the type deduction machinery to retain type sugar in
D110216, we were left with a situation where there is no general
well behaved mechanism in Clang to unify the type sugar of multiple
deductions of the same type parameter.

So we ended up making an arbitrary choice: keep the sugar of the first
deduction, ignore subsequent ones.

In general, we already had this problem, but in a smaller scale.
The result of the conditional operator and many other binary ops
could benefit from such a mechanism.

This patch implements such a type sugar unification mechanism.

The basics:

This patch introduces a getCommonSugaredType(QualType X, QualType Y)
method to ASTContext which implements this functionality, and uses it
for unifying the results of type deduction and return type deduction.
This will return the most derived type sugar which occurs in both X and
Y.

Example:

Suppose we have these types:

using Animal = int;
using Cat = Animal;
using Dog = Animal;

using Tom = Cat;
using Spike = Dog;
using Tyke = Dog;

For X = Tom, Y = Spike, this will result in Animal.
For X = Spike, Y = Tyke, this will result in Dog.

How it works:

We take two types, X and Y, which we wish to unify as input.
These types must have the same (qualified or unqualified) canonical
type.

We dive down fast through top-level type sugar nodes, to the
underlying canonical node. If these canonical nodes differ, we
build a common one out of the two, unifying any sugar they had.
Note that this might involve a recursive call to unify any children
of those. We then return that canonical node, handling any qualifiers.

If they don't differ, we walk up the list of sugar type nodes we dived
through, finding the last identical pair, and returning that as the
result, again handling qualifiers.

Note that this patch will not unify sugar nodes if they are not
identical already. We will simply strip off top-level sugar nodes that
differ between X and Y. This sugar node unification will instead be
implemented in a subsequent patch.

This patch also implements a few users of this mechanism:

  • Template argument deduction.
  • Auto deduction, for functions returning auto / decltype(auto), with special handling for initializer_list as well.

Further users will be implemented in a subsequent patch.

Signed-off-by: Matheus Izvekov <mizvekov@gmail.com>

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
rsmith added inline comments.May 24 2022, 1:17 PM
clang/lib/AST/ASTContext.cpp
11782–11793

This seems a bit suspicious: getCommonTypeArray assumes the two arrays contain the same types in the same order, but if the arrays can be different sizes, is it really correct to assume that they contain the same types if they're the same size?

Can we make this work the same way that the conditional expression handling deals with differing lists of exception types?

11853

I think this should probably be a declaresSameEntity check: we might be comparing injected class names from two different merged modules with two different declarations for the same class definition. The right declaration to use is the one that is chosen to be "the" definition.

Herald added a project: Restricted Project. · View Herald TranscriptMay 24 2022, 1:17 PM
mizvekov updated this revision to Diff 432339.May 26 2022, 11:26 AM
  • Just rebase, no review comments addressed yet.
  • One small churn in test/SemaCXX/deduced-return-void.cpp, which is new since last rebase. As this patch uses the template deduction machinery to also handle the mutiple return statements, we detect the inconsistent deduction before substitution, so we don't error out trying to form the reference to void.
mizvekov updated this revision to Diff 443731.Jul 11 2022, 1:11 PM
mizvekov edited the summary of this revision. (Show Details)
mizvekov marked 2 inline comments as done.
mizvekov added inline comments.Jul 11 2022, 1:12 PM
clang/lib/AST/ASTContext.cpp
11543–11544

I have updated the ASContext::getCommonSugaredType to accept an Unqualified parameter to handle this case.
We will strip qualifiers from each other until the types are the same.

11570

Yeah, but this has been dead code since we went back to canonical template parameter / arguments.
I have removed it and put some unreachables in place.
We will circle back around to add it with this suggestion later.

11782–11793

Handled this with a new ASTContext::mergeTypeLists that does what the conditional expression handling did, while getting the common sugar of types when they occur in both lists.

11853

Implemented this with a new getCommonDecl function, which for now will just fall back to the canonical decl if they differ.

Wonder if there is a cheap way to determine which of the two decls was declared earlier. Have to look this up, but would a simple pointer comparison be guaranteed to work? Eg would the earliest declaration, since being created earlier and allocated earlier, be guaranteed by design to have a lower (or greater) pointer value?

mizvekov updated this revision to Diff 443734.Jul 11 2022, 1:13 PM
mizvekov updated this revision to Diff 443749.Jul 11 2022, 2:02 PM

The libc++ build failures are due a broken libc++ HEAD. The current HEAD should work again.

rsmith added inline comments.Jul 12 2022, 1:09 PM
clang/lib/AST/ASTContext.cpp
11570

This will need to deal with all the cases that can arrive here. If it sees resolved template arguments, then this includes Type, Declaration, NullPtr, Integral, Template, TemplateExpansion, and Pack. If it sees unresolved template arguments, then this includes at least Type, Expression, Template, and TemplateExpansion. This function currently doesn't cover either set, so I strongly suspect that this llvm_unreachable is actually reachable.

Can we just return X; on all the cases we don't currently handle?

11627
11655

Please add braces to this outer for.

11675–11677

What guarantees that we won't see never-canonical types here?

I understand why we won't see sugar-free types (we can't have two Type*s that are different but represent the same type if they're sugar-free), and why we won't see non-unique types here (we can't have two Type*s that are different but represent the same type if they're not uniqued). But why can't we find, say, two different TypedefTypes here that desugar to the same type?

11703–11704

I don't understand why the deduced type must be null here. I think it usually will be, because if the deduced type were non-null, it would have to be identical (because we know desugaring one more level results in identical types), and in that case we'd typically have produced the same AutoType. But it seems like we could have two constrained AutoTypes here that desugar to the same type but differ in the spelling of their constraints, in which case the common type should presumably have a deduced type. I wonder if we should just be checking AX->getDeducedType() == AY->getDeducedType() here and passing in AX->getDeducedType() as the deduced type below?

11777–11778

If either of them was spelled as an lvalue then the pointee type was spelled as either an lvalue reference or a non-reference for that input type, and we'll strip off the spelling-as-rvalue-reference part of the other type when finding the common type. The only case in which I think it makes sense to retain the "spelled as rvalue" flag would be if *both* sides were lvalue references spelled as rvalue references.

11828–11829

Can we cope with a variadic function with no ellipsis location? I'd expect that to confuse some parts of Clang. Picking one of the two arbitrarily isn't great but may be the best we can do.

11853

No, there's no guarantee on pointer ordering. While we do use slab allocation, we allocate multiple slabs and there's no ordering between slabs. (Also declarations can be read from an AST file in any order.) There's no good way to find which of two declarations was declared first without a linear scan; see NamedDecl::declarationReplaces for existing code doing that scan. It might be that we need the scan rarely enough that we can afford to do it.

11914

No-op change, but this would make it a bit more obvious to a reader why it's OK to pass in TX here.

clang/lib/Sema/SemaExprCXX.cpp
1476–1477

It'd be nice to add an assert(Result == TDK_AlreadyDiagnosed); here now that we can do so.

2013

Likewise it'd be nice to add an assert(Result == TDK_AlreadyDiagnosed); here.

clang/lib/Sema/SemaTemplateDeduction.cpp
4577–4580

This comment is out of date.

4709–4710

I do like this approach to repeated deduction, but I'm not sure that it actually works. For example:

void f();
void g();
void g(int);
auto h(bool b) {
  if (b) return f;
  return g;
}

I think this is required to fail to compile, because we can't deduce the return type from the returned expression g. But I think what will happen here is that we'll say that g is a non-deduced context and then succeed because we pre-populated the deduced type from the void(*)() from f.

Instead, how about not pre-populating Deduced[0], and instead "deducing" the template parameter from Result after we check for the TDK_Incomplete case below? That should still let us reuse the logic for merging deductions.

4726

Can we use checkDeducedTemplateArguments here to unify the type sugar?

mizvekov updated this revision to Diff 444065.Jul 12 2022, 1:16 PM

The libc++ build failures are due a broken libc++ HEAD. The current HEAD should work again.

Thanks for letting me know! Pushed my patches again just to have a look at the result, haven't made any changes yet.

And Richard, tons of thanks again for the review!

mizvekov marked 2 inline comments as done.Jul 12 2022, 2:36 PM
mizvekov added inline comments.
clang/lib/AST/ASTContext.cpp
11675–11677

The idea is that this function is only called from a context where removeDifferentTopLevelSugar / unwrapSugar will have removed all top-level nodes which are sugar. So in particular, we will never see here any type nodes which have an implementation for is isSugared which always returns true, such as the TypedefType case.

So this is peculiar to this initial implementation, which never tries to "merge" sugar nodes.
There is value in trying to merge those, but you can imagine that the implementation for that is much more complicated and needs more thinking to get right and efficient.

For example for the TypedefType case right now:

If we had two different typedefs with the same underlying type, then there seems to be no sensible choice here except stripping off the typedef, I mean what Decl would we put on the TypedefType? Right now nullptr would not work since we always take the underlying type from the Decl, but even if it did, the value of having a nullptr decl seems questionable given the amount of trouble this could cause in other code.

If the TypedefTypes pointed to the same TypedefDecl, then it would make sense to create a new one with the "common" Decl between them, such as the earliest declaration or the canonical one, but might not be worth the extra complexity in the overall logic just for this effect.

Note that on my D127695, I implemented resugared TypedefTypes, which can have a different (but canonically the same) underlying type. With that, we would have more to do here and I think then it would be worth improving this further.

Otherwise, there is other interesting sugar we could try merging in the future as well, such as ElaboratedType and alias TemplateSpecializationTypes.

11703–11704

Well if they desugared to anything, then they would never show up in this function, they would have been stripped off by unwrapSugar as I explained in the previous comment.

mizvekov updated this revision to Diff 445358.Jul 17 2022, 4:10 PM
mizvekov updated this revision to Diff 445364.Jul 17 2022, 5:20 PM
mizvekov marked 9 inline comments as done.
Herald added a project: Restricted Project. · View Herald TranscriptJul 17 2022, 5:20 PM
mizvekov added inline comments.Jul 17 2022, 5:23 PM
clang/lib/AST/ASTContext.cpp
11570

Yeah I will just return X for the peace of mind for now, but I think what is happening here is that we only see unresolved template arguments, so we would theoretically be missing the handling of Template and TemplateExpansion cases.

The problem is, I think we are not actually preserving type sugar of those two cases, because of a profiling bug.
I pointed this problem out recently in this revision: https://reviews.llvm.org/D126172

So this is probably pretty hard to test right now, and we would not even get any results from handling it, so I will
leave a FIXME instead.

11828–11829

Can we cope with a variadic function with no ellipsis location? I'd expect that to confuse some parts of Clang. Picking one of the two arbitrarily isn't great but may be the best we can do.

Will leave that out for now with a FIXME.

clang/lib/Sema/SemaTemplateDeduction.cpp
4709–4710

Nice find. We don't actually need to repeat the deduction. We only need to return Inconsistent deduction if Result and Deduced[0] are different, and get the common sugar otherwise.

We already had to do so for the decltype(auto) case, and we could have totally resugared the initializer lists but forgot as you pointed out, and we had to make this same check for that case anyway.

So I combined all these cases, further simplifying things, and added the new test case for initializer_list.

mizvekov marked an inline comment as done.Jul 17 2022, 5:23 PM
mizvekov updated this revision to Diff 445635.Jul 18 2022, 3:01 PM
mizvekov marked an inline comment as done.
mizvekov marked 2 inline comments as done.Jul 18 2022, 3:03 PM
mizvekov updated this revision to Diff 445647.Jul 18 2022, 4:03 PM
mizvekov updated this revision to Diff 447086.Jul 23 2022, 10:17 AM
mizvekov updated this revision to Diff 447973.Jul 27 2022, 2:54 AM
mizvekov added reviewers: Restricted Project, erichkeane, shafik.Aug 10 2022, 3:28 AM

This is a sizable patch, and it isn't really clear from context what I'm looking at. Can you please improve the description/commit message to better clarify what you are trying to do, what you did, and how you are trying to accomplish it?

mizvekov updated this revision to Diff 451799.Aug 11 2022, 4:09 AM
mizvekov edited the summary of this revision. (Show Details)

This is a sizable patch, and it isn't really clear from context what I'm looking at. Can you please improve the description/commit message to better clarify what you are trying to do, what you did, and how you are trying to accomplish it?

Updated description, also, for reference, the other patches that continue this work are D111509 and D130308.

Thanks for taking a look!

davrec added a subscriber: davrec.Aug 11 2022, 7:18 AM

This part of the description is confusing:

We take two types, X and Y, which we wish to unify as input.
These types must have the same (qualified or unqualified) canonical
type.

We dive down fast through top-level type sugar nodes, to the
underlying canonical node. If these canonical nodes differ, we
build a common one out of the two, unifying any sugar they had.
Note that this might involve a recursive call to unify any children
of those. We then return that canonical node, handling any qualifiers.

If they don't differ, we walk up the list of sugar type nodes we dived
through, finding the last identical pair, and returning that as the
result, again handling qualifiers.

The first paragraph says X and Y must have the same canonical type, the second suggests they need not. In all the test cases, they have the same canonical type.

IIUC the second paragraph only applies to the stuff done in D130308; is that correct?

If so, the description should clearly state this patch only handles the case when the canonical types of X and Y are identical, and note that if the canonical types of X and Y differ, this patch does nothing, but D130308 adds some additional logic to search for common sugar in child type nodes of X and Y to use in the merged type.

And of course please add a full description to D130308 as well when you have a chance.

The first paragraph says X and Y must have the same canonical type, the second suggests they need not.

The first paragraph is talking about canonical equality, or if you take it that types 'refer' to their canonical types, referential equality.
Ie 'X.getCanonicalType() == Y.getCanonicalType()'

For short, when there is referential equality, I will say that the types are 'the same'. This matches terminology in Clang, for example see the helper ASTContext::hasSameType.

When there is structural equality, ie 'X == Y', I will say for short the types are 'identical'.

The second paragraph is talking about 'Canonical nodes', not 'Canonical types'.

A canonical node is a type node for which 'isSugared' method returns false.
This is not the same thing as saying that a Canonical node is a canonical type.

For example, 'PointerType' nodes are always canonical nodes, because it has an 'isSugared' implementation which always returns false:

bool isSugared() const { return false; }

But pointer types can be non-canonical, in particular when the PointeeType is not a canonical type.

In all the test cases, they have the same canonical type.

Well not exactly, we also handle and test the case they are also the same when 'unqualified'.

The case here is function types that differ only in top-level qualifiers of parameters, so this is used only when we are recursing down a FunctionType.

Those FunctionTypes should be treated as the same, ie you can't overload based on differences in top-level qualifiers of function parameters.
But this should not be treated as a semantic adjustment, like we do for decays, eg in array parameters, because we still need to consider those qualifiers on the parameter itself within the function definition, unlike what happens with decays (where we basically just rewrite the type of the parameter).

See the t8 and t11 test cases in clang/test/SemaCXX/sugared-auto.cpp, which test top-level differences in const and ObjectiveC lifetime qualifiers respectively.

IIUC the second paragraph only applies to the stuff done in D130308; is that correct?

No, it's talking about the stuff in this patch.

In this patch, we unify Canonical nodes that differ, but don't unify different non-canonical nodes (ie those which 'isSugared()' returns true), that last part is done in D130308.

If so, the description should clearly state this patch only handles the case when the canonical types of X and Y are identical, and note that if the canonical types of X and Y differ, this patch does nothing, but D130308 adds some additional logic to search for common sugar in child type nodes of X and Y to use in the merged type.

Well it's not that we do nothing if the types are not the same!
That the inputs should be the same (but not necessaritly identical) is an invariant, ie a contract to use this function.
In a debug build, we will check that this invariant is respected, asserting otherwise.

And of course please add a full description to D130308 as well when you have a chance.

Sure.

The second paragraph is talking about 'Canonical nodes', not 'Canonical types'.

A canonical node is a type node for which 'isSugared' method returns false.

Thanks for the clarification, but note that that term is not in general use so far as I'm aware. But instead of defining it and getting into details it might be clearer, and certainly would have saved me the most time, for the description to simply note that this patch introduces ASTContext::getCommonSugaredType(QualType X, QualType Y, bool Unqualified = false), and puts it to use in type deduction for binary expressions etc., and give an example or two to demonstrate. (More generally on a large patch I prefer a description to give me a few starting points, the primary changes which necessitate all the others.)

Re the patch itself: it looks good to me other than a few nits, but this has such a broad and deep span (intricate details of the AST + intricate details of Sema) it is difficult to give it a final thumbs up - really hoping @rsmith might take a final look. But if time runs out it is definitely worth accepting as is and seeing how it goes; the benefits exceed the risks. I will try to take a close look at the other patches on your stack, but the same probably applies. You've done a tremendous amount of work here, very impressive.

clang/include/clang/AST/ASTContext.h
2738

A clearer name might be combineExceptionSpecs, or the original mergeExceptionSpecs, since this is getting the union of their sets of exception specs, whereas getCommon* suggests getting the intersection, e.g. getCommonSugaredType is getting the intersection of two "sets" of type sugar in a sense.

Also, please add some brief documentation to the function.

2756

Any reason this is public? Or in the header at all? Seems like it could be a static function in the cpp.

clang/lib/AST/ASTContext.cpp
11544

I think "canonical" should be replaced with "first" here and isCanonicalDecl() with isFirstDecl(). So far as I can tell getCanonicalDecl() returns getFirstDecl() everywhere for now, but that could conceivably change, and in any case the goal of this code is to find which is older, so "first" would be clearer as well.

mizvekov updated this revision to Diff 453779.Aug 18 2022, 2:09 PM
mizvekov edited the summary of this revision. (Show Details)
mizvekov marked 3 inline comments as done.Aug 18 2022, 2:14 PM

But instead of defining it and getting into details it might be clearer, and certainly would have saved me the most time, for the description to simply note that this patch introduces ASTContext::getCommonSugaredType(QualType X, QualType Y, bool Unqualified = false), and puts it to use in type deduction for binary expressions etc., and give an example or two to demonstrate. (More generally on a large patch I prefer a description to give me a few starting points, the primary changes which necessitate all the others.)

Done, thanks for the feedback!

Re the patch itself: it looks good to me other than a few nits, but this has such a broad and deep span (intricate details of the AST + intricate details of Sema) it is difficult to give it a final thumbs up - really hoping @rsmith might take a final look. But if time runs out it is definitely worth accepting as is and seeing how it goes; the benefits exceed the risks. I will try to take a close look at the other patches on your stack, but the same probably applies. You've done a tremendous amount of work here, very impressive.

Yeah, but we made good progress here, thanks again!

Thanks for working on this! I left some comments. I did not look very deep in the patch but it seems quite consistent to the rest of the code in Sema.

Can you provide some performance numbers for this patch. I believe that the less confident reviewers will be more comfortable letting that patch in. I think this work is important and should be merged before GSoC ends. It will allow clang to diagnose better errors in template instantiations even for cases such as llvm.org/PR12853

clang/lib/AST/ASTContext.cpp
11554

Maybe getCommonDecl should take const Decl* instead of const_cast-ing here.

11597
11833

Defining decl groups does not seem common in the llvm/clang codebase. I don't think we have a specific rule discouraging that. However, I think stepping through with a debugger will be harder. Maybe make this and other occurrences more consistent to the rest of the codebase?

mizvekov updated this revision to Diff 453824.Aug 18 2022, 4:01 PM
mizvekov marked an inline comment as done.

Thanks for working on this! I left some comments. I did not look very deep in the patch but it seems quite consistent to the rest of the code in Sema.

Thanks!

Can you provide some performance numbers for this patch. I believe that the less confident reviewers will be more comfortable letting that patch in.

Yeah, sounds good.

clang/lib/AST/ASTContext.cpp
11554

If getCommonDecl took const Decl, I believe I would still need to const_cast to const Decl * anyway, because otherwise we would end up in infinite recursion into this function template.

And I would then in addition need to const_cast in the return value, which would make the whole thing more complicated.

11833

Though in these cases the expression is just a cast, you would so rarely need to step into them as there is very little interesting going on in there.

mizvekov updated this revision to Diff 454303.Aug 21 2022, 5:42 AM
mizvekov edited the summary of this revision. (Show Details)
mizvekov updated this revision to Diff 456196.Aug 28 2022, 10:27 AM
ChuanqiXu added inline comments.Sep 1 2022, 7:23 PM
clang/include/clang/AST/ASTContext.h
2747–2751

For this comment, I want to know what if Xand Y is not equal. Is there an assertion failure or special type returned?

mizvekov added inline comments.Sep 1 2022, 7:30 PM
clang/include/clang/AST/ASTContext.h
2747–2751

Assertion failure, the function is supposed to handle only the case where hasSame[Unqualified]Type(X, Y) == true.

It will always return a type R for which hasSame[Unqualified]Type(R, Y) == true also holds, so there are no special return values.

davrec accepted this revision.Sep 5 2022, 5:03 PM

I hope I'm not stepping on any toes given the recent changes in code ownership, but I'm accepting this and D130308 because

  1. They are a nice improvement to the AST that naturally follows from the earlier work adding sugar to type deductions,
  2. I have given it a fairly close look and it LGTM,
  3. @rsmith has taken a step back and so may not be available to give it a final look,
  4. @mizvekov has verified that the performance impact is negligible, and
  5. Other reviewers seem to have at least given it a once over and have not identified major issues.

Anyone object/need more time to review?

Thanks for your patience, this was a sizable review that it took a while to be able to make time for. A handful of non-functional reviews, but the functionality looks fine.

clang/lib/AST/ASTContext.cpp
11538

As a nit, I'd prefer this assert above the 'if' above, that way it catches Nulls in that case. It seems that the 'contract' for this function is no null values, so we want to catch this ASAP.

11544

For class types, canonical decl is usually the definition if one exists. So I assume we do mean 'first' here.

11553

Do we REALLY need to tolerate 'null' here as well? It seems we should do a normal cast and have the cast assert.

11571

Please comment these functions, it isn't clear on read through what you mean by 'Array' here. You probably mean for this to be something like getCommonTypes.

11603

Please give me a message here, just anything reasonably descriptive so that when it shows up I have something to grep.

11640

I guess I don't see the po int of the next 3 functions? There is no sugaring or anything, AND they already assume they are the same expression/element/etc?

11722

something a little more unique in this (and others!) would be appreciated.

mizvekov updated this revision to Diff 458587.Sep 7 2022, 3:51 PM
mizvekov edited the summary of this revision. (Show Details)
mizvekov marked 5 inline comments as done.
mizvekov marked an inline comment as done.Sep 7 2022, 4:33 PM
mizvekov added inline comments.
clang/lib/AST/ASTContext.cpp
11538

Actually we want to handle the case they are both null, in which case we return null as well.
This case would trip the assert below, as declaresSameEntity considers that not the same entity.

For example, unconstrained AutoType will have null ConceptDecl.

In the D130308 patch, we add another variant, `getCommonDeclChecked, which requires non-null inputs, which can be used in places where non-null is required.

I went ahead and moved the introduction of getCommonDeclChecked to this patch, even though there is only one use of it.

11553

Yes, as above.

11571

Yeah that name works as well.

11640

Well the point is to not repeat the assert in all use sites.

Thanks for your patience, this was a sizable review that it took a while to be able to make time for. A handful of non-functional reviews, but the functionality looks fine.

Thanks, I appreciate the effort!

erichkeane accepted this revision.Sep 8 2022, 6:18 AM
This revision was not accepted when it landed; it landed in state Needs Review.Sep 8 2022, 10:18 AM
This revision was automatically updated to reflect the committed changes.
ychen added a subscriber: ychen.Sep 11 2022, 11:00 PM

Hi @mizvekov , I noticed that deduction for partial ordering also inherits this new behavior. Do you think partial ordering could opt out of this, or is it better for partial ordering to deal with the new sugared types?

Hi @mizvekov , I noticed that deduction for partial ordering also inherits this new behavior. Do you think partial ordering could opt out of this, or is it better for partial ordering to deal with the new sugared types?

I would expect partial ordering to be performed only using canonical types, that type sugar should make no difference there.

Note that previous to this patch, we would already produce sugar on deduction, but the behavior with regards to deducing the same template parameter from multiple places was pretty badly behaved: We would just keep the sugar from the first deduction, and ignore any subsequent ones. That meant that the deduction order had an effect on the result. With this patch, deduction becomes order agnostic.

What kind of difference are you seeing?

alexfh added a subscriber: alexfh.Sep 12 2022, 6:29 AM

Hi Matheus, an early heads up: this commit is causing clang crashes on some of our code. I'll post more details a bit later.

Hi @mizvekov , I noticed that deduction for partial ordering also inherits this new behavior. Do you think partial ordering could opt out of this, or is it better for partial ordering to deal with the new sugared types?

I would expect partial ordering to be performed only using canonical types, that type sugar should make no difference there.

Note that previous to this patch, we would already produce sugar on deduction, but the behavior with regards to deducing the same template parameter from multiple places was pretty badly behaved: We would just keep the sugar from the first deduction, and ignore any subsequent ones. That meant that the deduction order had an effect on the result. With this patch, deduction becomes order agnostic.

I see. I think it is definitely a good thing. I'm still learning what the expected AST should look like during the partial ordering.

What kind of difference are you seeing?

For

template <typename...> struct A {};

template <class T>
bool foo(A<T>);

template <class T, class... Args>
bool foo(A<T, Args...>);

template <class Tuple> bool bar()
{
    return foo(Tuple{});
}

A<T> is currently modeled as ElaboratedType. It was TemplateSpecializationType before. Reading comments for ElaboratedType, it looks like sugar type might not be needed here?

ElaboratedType 0xd79c8f0 'A<T>' sugar dependent
`-TemplateSpecializationType 0xd79c8b0 'A<T>' dependent A
  `-TemplateArgument type 'T'
    `-TemplateTypeParmType 0xd79c7f0 'T' dependent depth 0 index 0
      `-TemplateTypeParm 0xd79c768 'T'

A<T> is currently modeled as ElaboratedType. It was TemplateSpecializationType before. Reading comments for ElaboratedType, it looks like sugar type might not be needed here?

Ah you might be seeing an ElaboratedTYpe here, where there was none before, perhaps because of https://reviews.llvm.org/D112374, and not because of this patch.

Yeah ElaboratedType is just sugar that should have no effect on partial ordering. But then I think no sugar should have effect on partial ordering. What is stopping you from just looking at the canonical type instead? On a canonical type, you would never see an ElaboratedType node, or a TemplateSpecializationType which is not dependent.

Is this related to the AutoType canonicalization issues we were discussing in the other patch of yours?

A<T> is currently modeled as ElaboratedType. It was TemplateSpecializationType before. Reading comments for ElaboratedType, it looks like sugar type might not be needed here?

Ah you might be seeing an ElaboratedTYpe here, where there was none before, perhaps because of https://reviews.llvm.org/D112374, and not because of this patch.

Yeah ElaboratedType is just sugar that should have no effect on partial ordering. But then I think no sugar should have effect on partial ordering. What is stopping you from just looking at the canonical type instead? On a canonical type, you would never see an ElaboratedType node, or a TemplateSpecializationType which is not dependent.

Thanks for the link. I'm not blocked by any of these patches, instead just trying to have a mental model of when to expect the sugared type :-). For partial ordering, the TemplateSpecializationType could be dependent, since the injected template arguments are dependent, I guess that's the reason there is the ElaboratedType?

Is this related to the AutoType canonicalization issues we were discussing in the other patch of yours?

Nope. I found this AST difference while investigating D133683.

Thanks for the link. I'm not blocked by any of these patches, instead just trying to have a mental model of when to expect the sugared type :-). For partial ordering, the TemplateSpecializationType could be dependent, since the injected template arguments are dependent, I guess that's the reason there is the ElaboratedType?

The ElaboratedType is a sort of a companion node to other nodes that represent things in the language which can have (non-dependent) nested name qualifiers (a NNS for short) and / or an elaborated type specifier (such as the struct in struct A).

It's only purpose is to carry that extra bit of data, like some external storage really, and it shouldn't affect the semantics of the program once the source code is parsed into an AST.

Here, in your example, the ElaboratedType is there, as a companion to that TemplateSpecializationType, just to say that this template specialization was written without any name qualifiers nor elaboration.

ychen added a comment.Sep 12 2022, 2:10 PM

Thanks for the link. I'm not blocked by any of these patches, instead just trying to have a mental model of when to expect the sugared type :-). For partial ordering, the TemplateSpecializationType could be dependent, since the injected template arguments are dependent, I guess that's the reason there is the ElaboratedType?

The ElaboratedType is a sort of a companion node to other nodes that represent things in the language which can have (non-dependent) nested name qualifiers (a NNS for short) and / or an elaborated type specifier (such as the struct in struct A).

It's only purpose is to carry that extra bit of data, like some external storage really, and it shouldn't affect the semantics of the program once the source code is parsed into an AST.

Here, in your example, the ElaboratedType is there, as a companion to that TemplateSpecializationType, just to say that this template specialization was written without any name qualifiers nor elaboration.

Very helpful explanation :-).

Hi Matheus, an early heads up: this commit is causing clang crashes on some of our code. I'll post more details a bit later.

The stack trace of the failure looks like this:

 #0 0x0000564e08f78aca llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (./clang-after+0x7778aca)
 #1 0x0000564e08f76b78 llvm::sys::RunSignalHandlers() (./clang-after+0x7776b78)                                                                                                                                                                       
 #2 0x0000564e08f10027 (anonymous namespace)::CrashRecoveryContextImpl::HandleCrash(int, unsigned long) (./clang-after+0x7710027)
 #3 0x0000564e08f102bd CrashRecoverySignalHandler(int) (./clang-after+0x77102bd)
 #4 0x00007f4773e731c0 __restore_rt
 #5 0x0000564e06591b6b (./clang-after+0x4d91b6b)
 #6 0x0000564e06374ef8 checkDeducedTemplateArguments(clang::ASTContext&, clang::DeducedTemplateArgument const&, clang::DeducedTemplateArgument const&) (./clang-after+0x4b74ef8)
 #7 0x0000564e0636b7b1 DeduceTemplateArgumentsByTypeMatch(clang::Sema&, clang::TemplateParameterList*, clang::QualType, clang::QualType, clang::sema::TemplateDeductionInfo&, llvm::SmallVectorImpl<clang::DeducedTemplateArgument>&, unsigned int, bool, bool) (./clang-after+0x4b6b7b1)
 #8 0x0000564e0636c896 DeduceTemplateArgumentsByTypeMatch(clang::Sema&, clang::TemplateParameterList*, clang::QualType, clang::QualType, clang::sema::TemplateDeductionInfo&, llvm::SmallVectorImpl<clang::DeducedTemplateArgument>&, unsigned int, bool, bool) (./clang-after+0x4b6c896)
 #9 0x0000564e06376430 DeduceTemplateArguments(clang::Sema&, clang::TemplateParameterList*, clang::QualType const*, unsigned int, clang::QualType const*, unsigned int, clang::sema::TemplateDeductionInfo&, llvm::SmallVectorImpl<clang::DeducedTemplateArgument>&, unsigned int, bool) (./clang-after+0x4b76430)
#10 0x0000564e06370895 isAtLeastAsSpecializedAs(clang::Sema&, clang::SourceLocation, clang::FunctionTemplateDecl*, clang::FunctionTemplateDecl*, clang::TemplatePartialOrderingContext, unsigned int, bool) (./clang-after+0x4b70895)
#11 0x0000564e063702c1 clang::Sema::getMoreSpecializedTemplate(clang::FunctionTemplateDecl*, clang::FunctionTemplateDecl*, clang::SourceLocation, clang::TemplatePartialOrderingContext, unsigned int, unsigned int, bool, bool) (./clang-after+0x4b702c1)
#12 0x0000564e0626a07c clang::isBetterOverloadCandidate(clang::Sema&, clang::OverloadCandidate const&, clang::OverloadCandidate const&, clang::SourceLocation, clang::OverloadCandidateSet::CandidateSetKind) (./clang-after+0x4a6a07c)
#13 0x0000564e0625e199 clang::OverloadCandidateSet::BestViableFunction(clang::Sema&, clang::SourceLocation, clang::OverloadCandidate*&) (./clang-after+0x4a5e199)
#14 0x0000564e06272aec clang::Sema::BuildOverloadedCallExpr(clang::Scope*, clang::Expr*, clang::UnresolvedLookupExpr*, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::Expr*, bool, bool) (./clang-after+0x4a72aec)
#15 0x0000564e05fd3157 clang::Sema::BuildCallExpr(clang::Scope*, clang::Expr*, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::Expr*, bool, bool) (./clang-after+0x47d3157)
#16 0x0000564e05feb5b6 clang::Sema::ActOnCallExpr(clang::Scope*, clang::Expr*, clang::SourceLocation, llvm::MutableArrayRef<clang::Expr*>, clang::SourceLocation, clang::Expr*) (./clang-after+0x47eb5b6)
#17 0x0000564e063d0bc6 clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformCallExpr(clang::CallExpr*) (./clang-after+0x4bd0bc6)
#18 0x0000564e063c80f9 clang::Sema::SubstInitializer(clang::Expr*, clang::MultiLevelTemplateArgumentList const&, bool) (./clang-after+0x4bc80f9)
#19 0x0000564e064057c8 clang::Sema::InstantiateVariableInitializer(clang::VarDecl*, clang::VarDecl*, clang::MultiLevelTemplateArgumentList const&) (./clang-after+0x4c057c8)
#20 0x0000564e063fa1be clang::Sema::BuildVariableInstantiation(clang::VarDecl*, clang::VarDecl*, clang::MultiLevelTemplateArgumentList const&, llvm::SmallVector<clang::Sema::LateInstantiatedAttribute, 16u>*, clang::DeclContext*, clang::LocalInstantiationScope*, bool, clang::VarTemplateSpecializationDecl*) (./clang-after+0x4bfa1be)
#21 0x0000564e063f99db clang::TemplateDeclInstantiator::VisitVarDecl(clang::VarDecl*, bool, llvm::ArrayRef<clang::BindingDecl*>*) (./clang-after+0x4bf99db)
#22 0x0000564e0642f599 void llvm::function_ref<void ()>::callback_fn<clang::Sema::SubstDecl(clang::Decl*, clang::DeclContext*, clang::MultiLevelTemplateArgumentList const&)::$_0>(long) (./clang-after+0x4c2f599)
#23 0x0000564e05cc55ee clang::Sema::runWithSufficientStackSpace(clang::SourceLocation, llvm::function_ref<void ()>) (./clang-after+0x44c55ee)
#24 0x0000564e06406ceb clang::Sema::SubstDecl(clang::Decl*, clang::DeclContext*, clang::MultiLevelTemplateArgumentList const&) (./clang-after+0x4c06ceb)
#25 0x0000564e063e91ac clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformDeclStmt(clang::DeclStmt*) (./clang-after+0x4be91ac)
#26 0x0000564e063e08c7 clang::TreeTransform<(anonymous namespace)::TemplateInstantiator>::TransformCompoundStmt(clang::CompoundStmt*, bool) (./clang-after+0x4be08c7)
#27 0x0000564e063c96be clang::Sema::SubstStmt(clang::Stmt*, clang::MultiLevelTemplateArgumentList const&) (./clang-after+0x4bc96be)
#28 0x0000564e06408b57 clang::Sema::InstantiateFunctionDefinition(clang::SourceLocation, clang::FunctionDecl*, bool, bool, bool) (./clang-after+0x4c08b57)
#29 0x0000564e0640ad95 clang::Sema::PerformPendingInstantiations(bool) (./clang-after+0x4c0ad95)
#30 0x0000564e06408c54 clang::Sema::InstantiateFunctionDefinition(clang::SourceLocation, clang::FunctionDecl*, bool, bool, bool) (./clang-after+0x4c08c54)
#31 0x0000564e0640ad95 clang::Sema::PerformPendingInstantiations(bool) (./clang-after+0x4c0ad95)
#32 0x0000564e05cc7b40 clang::Sema::ActOnEndOfTranslationUnitFragment(clang::Sema::TUFragmentKind) (./clang-after+0x44c7b40)
#33 0x0000564e05cc8877 clang::Sema::ActOnEndOfTranslationUnit() (./clang-after+0x44c8877)
#34 0x0000564e05a344ec clang::Parser::ParseTopLevelDecl(clang::OpaquePtr<clang::DeclGroupRef>&, clang::Sema::ModuleImportState&) (./clang-after+0x42344ec)
#35 0x0000564e05a2effe clang::ParseAST(clang::Sema&, bool, bool) (./clang-after+0x422effe)
#36 0x0000564e057af31a clang::FrontendAction::Execute() (./clang-after+0x3faf31a)
#37 0x0000564e057224e6 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (./clang-after+0x3f224e6)
#38 0x0000564e049d9446 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (./clang-after+0x31d9446)
#39 0x0000564e049cdb40 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (./clang-after+0x31cdb40)
#40 0x0000564e049cbda4 ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&) (./clang-after+0x31cbda4)
#41 0x0000564e058afcf7 void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<llvm::Optional<llvm::StringRef>>, std::__u::basic_string<char, std::__u::char_traits<char>, std::__u::allocator<char>>*, bool*) const::$_1>(long) (./clang-after+0x40afcf7)
#42 0x0000564e08f0fef1 llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) (./clang-after+0x770fef1)
#43 0x0000564e058af64c clang::driver::CC1Command::Execute(llvm::ArrayRef<llvm::Optional<llvm::StringRef>>, std::__u::basic_string<char, std::__u::char_traits<char>, std::__u::allocator<char>>*, bool*) const (./clang-after+0x40af64c)
#44 0x0000564e05874f77 clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const (./clang-after+0x4074f77)
#45 0x0000564e05875230 clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::__u::pair<int, clang::driver::Command const*>>&, bool) const (./clang-after+0x4075230)
#46 0x0000564e058911af clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::__u::pair<int, clang::driver::Command const*>>&) (./clang-after+0x40911af)
#47 0x0000564e049cb50b clang_main(int, char**) (./clang-after+0x31cb50b)
#48 0x00007f4773d04633 __libc_start_main
#49 0x0000564e049c846a _start (./clang-after+0x31c846a)
clang-after: error: clang frontend command failed with exit code 133 (use -v to see invocation)
clang version google3-trunk (1819d5999c7091781a6441dad4bcede9c554ca90)
Target: x86_64-unknown-linux-gnu

I'm reducing the test case.

Hi Matheus, an early heads up: this commit is causing clang crashes on some of our code. I'll post more details a bit later.

The stack trace of the failure looks like this:

And the assertions-enabled build of clang prints this:

Unexpected sugar-free: TemplateTypeParm
UNREACHABLE executed at llvm-project/clang/lib/AST/ASTContext.cpp:12383!

Hi Matheus, an early heads up: this commit is causing clang crashes on some of our code. I'll post more details a bit later.

The stack trace of the failure looks like this:

And the assertions-enabled build of clang prints this:

Unexpected sugar-free: TemplateTypeParm
UNREACHABLE executed at llvm-project/clang/lib/AST/ASTContext.cpp:12383!

Reduced test case:

$ cat q.cc
struct a {
  int b;
};
template <typename c, typename d> using e = c d::*;
struct f {
  long b() const;
  template <typename c> c g(e<c, a>) const;
  a h;
};
template <typename c, typename d, typename... i>
void j(e<c, d>, const d *, i...);
template <typename c, typename d> c j(e<c, d>, const d *);
template <typename c> c f::g(e<c, a> k) const { return j(k, &h); }
long f::b() const { return g(&a::b); }
$ ./clang-after q.cc
...
1.      <eof> parser at end of file
2.      q.cc:13:28: instantiating function definition 'f::g<int>'
...
clang-after: error: unable to execute command: Trace/breakpoint trap
clang-after: error: clang frontend command failed due to signal (use -v to see invocation)

Please fix or revert soon.

Thanks!

I've reverted d200db38637884fd0b421802c6094b2a03ceb29e in 637da9de4c6619c0e179c2c2f0dbfebd08ac2a0f.

And a bit more compact test case:

template <typename T>
using P = int T::*;

template <typename T, typename... A>
void j(P<T>, T, A...);

template <typename T>
void j(P<T>, T);

struct S {
  int b;
};
void g(P<S> k, S s) { j(k, s); }
mizvekov reopened this revision.Sep 13 2022, 3:38 AM

Even simpler:

template <class T> using P = T;

template <class T, class... A> void j(P<T>, T, A...);
template <class T> void j(P<T>, T);

void g() { j(P<int>{}, int{}); }
davrec added inline comments.Sep 13 2022, 5:41 AM
clang/lib/AST/ASTContext.cpp
11762

Could we just return X here? Would that just default to the old behavior instead of crashing whenever unforeseen cases arise?

mizvekov added inline comments.Sep 13 2022, 5:52 AM
clang/lib/AST/ASTContext.cpp
11762

No, I think we should enforce the invariants and make sure we are handling everything that can be handled.

Classing TemplateTypeParm as sugar-free was what was wrong and we missed this on the review.

mizvekov updated this revision to Diff 459725.Sep 13 2022, 5:58 AM
mizvekov edited the summary of this revision. (Show Details)
mizvekov updated this revision to Diff 459726.Sep 13 2022, 6:05 AM
davrec added inline comments.Sep 13 2022, 6:05 AM
clang/lib/AST/ASTContext.cpp
11762

There might always going to be a few rare corner cases vulnerable to this though, particularly as more types are added and the people adding them don't pay strict attention to how to incorporate them here, and don't write the requisite tests (which seem very difficult to foresee and produce). When those cases arise we will be crashing even though we could produce a perfectly good program with the intended semantics; the only thing that would suffer for most users is slightly less clear diagnostic messages for those rare cases. I think it would be better to let those cases gradually percolate to our attention via bug reports concerning those diagnostics, rather than inconveniencing the user and demanding immediate attention via crashes. Or am I missing something?

mizvekov added inline comments.Sep 13 2022, 6:13 AM
clang/lib/AST/ASTContext.cpp
11762

I could on the same argument remove all asserts here and just let the program not crash on unforeseen circumstances.

On the other hand, having these asserts here helps us catch bugs not only here, but in other parts of the code. For example uniquing / canonicalization bugs.

If someone changes the properties of a type so that the assumptions here are not valid anymore, it's helpful to have that pointed out soon.

Going for as an example this specific bug, if there werent those asserts / unreachables in place and we had weakened the validation here, it would take a very long time for us to figure out we were making the wrong assumption with regards to TemplateTypeParmType.

I'd rather figure that out sooner on CI / internal testing rather than have a bug created by a user two years from now.

davrec added inline comments.Sep 13 2022, 6:40 AM
clang/lib/AST/ASTContext.cpp
11762

Yes its nicer to developers to get stack traces and reproductions whenever something goes wrong, and crashing is a good way to get those, but users probably won't be so thrilled about it. Especially given that the main selling point of this patch is that it makes diagnostics nicer for users: isn't it a bit absurd to crash whenever we can't guarantee our diagnostics will be perfect?

And again the real problem is future types not being properly incorporated here and properly tested: i.e. the worry is that this will be a continuing source of crashes, even if we handle all the present types properly.

How about we leave it as an unreachable for now, to help ensure all the present types are handled, but if months or years from now there continue to be crashes due to this, then just return X (while maybe write something to llvm::errs() to encourage users to report it), so we don't make the perfect the enemy of the good.

For reference, another small reproducer of the crash, but with a different stack trace than the first example posted here:

// Must compile with -std=c++03 to crash
#include <algorithm>

int main(int, char**)
{
  int i[3] = {1, 2, 3};
  int j[3] = {4, 5, 6};
  std::swap(i, j);

  return 0;
}

Compile with -std=c++03 to reproduce the assertion failure.
We found it by running the libcxx tests.

mizvekov added inline comments.Sep 13 2022, 8:48 AM
clang/lib/AST/ASTContext.cpp
11762

It's not about crashing when it won't be perfect. We already do these kinds of things, see the FIXMEs around the TemplateArgument and NestedNameSpecifier, where we just return something worse than we wish to, just because we don't have time to implement it now.

These unreachables and asserts here are about testing / documenting our knowledge of the system and making it easier to find problems. These should be impossible to happen in correct code, and if they do happen because of mistakes, fixing them sooner is just going to save us resources.

llvm::errs suggestion I perceive as out of line with current practice in LLVM, we don't and have a system for producing diagnostics for results possibly affected by FIXME and TODO situations and the like, as far as I know, and I am not aware of any discussions in that direction. I expect a lot of complexity and noise if we went this way.
And I think this would have even less chance of working out if we started to incorporate the reporting of violation of invariants into that.

I think even just using raw llvm::errs on clang would be incorrect per design, and could likely break users that parse our output.

I think if months and years from now, if someone stumbled upon an assert firing here and, instead of understanding what is happening and then fixing the code, just removed / weakened the assert, that would simply not be good and I hope a reviewer would stop that from happening :)

@alexfh This new revision that I just pushed should be good.

Do you want to give it a look / test, or should we go ahead and merge it?

mizvekov added a comment.EditedSep 13 2022, 8:58 AM

For reference, another small reproducer of the crash, but with a different stack trace than the first example posted here:

// Must compile with -std=c++03 to crash
#include <algorithm>

int main(int, char**)
{
  int i[3] = {1, 2, 3};
  int j[3] = {4, 5, 6};
  std::swap(i, j);

  return 0;
}

Compile with -std=c++03 to reproduce the assertion failure.
We found it by running the libcxx tests.

Thanks. Is this a different crash, or is It the same unreachable firing as the one @alexfh reported?

EDIT: NVM, I see it's a different problem, thanks for reporting.

aaronpuchert added inline comments.Sep 13 2022, 5:03 PM
clang/lib/AST/ASTContext.cpp
11762

I tend to agree that an assertion is appropriate for this. But you could turn this into

assert(false && "...");
return X;

which would still assert, but fall back to something "reasonable" in builds without assertions. The issue with llvm_unreachable is that it translates to __builtin_unreachable() in builds without assertions, and the optimizer takes advantage of that quite heavily. That's why llvm_unreachable is better left to places where we're pretty sure they're unreachable unless something went horribly wrong, such as after switches that handle all enumeration values.

mizvekov added inline comments.Sep 13 2022, 5:49 PM
clang/lib/AST/ASTContext.cpp
11762

I see, that is reasonable.

But grepping for assert(false in the code base, I see that they are pretty rare, accounting for a bit less than 0.5% in clang/ and 0.3% in llvm/, compared to the total number of asserts. They occur fifty times less than plain llvm_unreachables. I don't remember ever seeing them in practice, so I was not aware this was something we would do.

I feel like in these cases where we are using llvm_unreachable in this patch, I am 100% sure they are really unreachable, unless something went horribly wrong. But I mean I could be completely wrong as well, I must admit, so maybe in those cases that is what went horribly wrong? (Me being wrong I mean)

mizvekov updated this revision to Diff 459944.Sep 13 2022, 6:07 PM
mizvekov updated this revision to Diff 459946.Sep 13 2022, 6:17 PM

@vhscampos that issue should be fixed now as well, thanks for the report!

mizvekov updated this revision to Diff 459959.Sep 13 2022, 7:24 PM
mizvekov updated this revision to Diff 460010.Sep 14 2022, 1:45 AM
mizvekov updated this revision to Diff 460021.Sep 14 2022, 3:33 AM
aaronpuchert added inline comments.Sep 14 2022, 1:47 PM
clang/lib/AST/ASTContext.cpp
11762

It depends on how likely you think it is that someone inadvertently violates the invariant without noticing (even after running the tests). For internal invariants or things unlikely to miss in a test, llvm_unreachable should be fine. The less certain you are that mistakes would be hard to make or easy to find, the more preferable would be a fallback. If you think this was a one-off blunder and is unlikely to reoccur, you can also stick to llvm_unreachable. So this was just a suggestion, and since I merely skimmed over the code I'm not the best to judge.

davrec added inline comments.Sep 15 2022, 7:06 AM
clang/lib/AST/ASTContext.cpp
11762

The assert(false) was a great suggestion. I see the argument for unreachable too.

It seems there are two ways to proceed: unreachable for now, but downgrade to assert(false) if the issue re-arises, or assert(false) for now, upgraded to unreachable later if we find that assert is never tripped.

I'm inclined to prefer the latter, assert(false) for now, assuming there is not a noticeable performance impact on optimized builds with no assertions.

Reason: IIUC, it is not just a) new types, but also b) changes to Sema to support new features that do not construct type sugar properly but are not caught during review, could introduce bugs here that are not detected until a corner case (involving combining instances of such types) is discovered much later. While these bugs do need to be caught and fixed, it would be nice if builds without assertions could continue to work while they were.

But either course is reasonable and @mizekov has the best understanding of how likely such bugs are to find their way in.

@alexfh This new revision that I just pushed should be good.

Do you want to give it a look / test, or should we go ahead and merge it?

Thanks for the fix! If it fixes the test case I provided, that should be enough for now. If it breaks anything else, we'll find this out in a few days after you land the patch.

mizvekov updated this revision to Diff 460586.Sep 15 2022, 6:09 PM
This revision was not accepted when it landed; it landed in state Needs Review.Sep 16 2022, 2:21 AM
This revision was automatically updated to reflect the committed changes.