Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler error when extending a typealias of a partially specialized generic type #68212

Open
tapsns opened this issue Aug 30, 2023 · 56 comments · May be fixed by #73169
Open

Compiler error when extending a typealias of a partially specialized generic type #68212

tapsns opened this issue Aug 30, 2023 · 56 comments · May be fixed by #73169
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself declarations Feature: declarations extension Feature → declarations: `extension` declarations generics Feature: generic declarations and types good first issue Good for newcomers swift 5.9 type checker Area → compiler: Semantic analysis typealias Feature → type declarations: `typealias` declarations TypeResolver unexpected error Bug: Unexpected error

Comments

@tapsns
Copy link

tapsns commented Aug 30, 2023

Description
When creating a typealias that partially specializes a generic type, the compiler generates an error when attempting to extend that type via the typealias.

Steps to reproduce
Create a Generic type with two type parameters.

struct Field<Tag,Value> {
  let tag: Tag
  let value: Value
}

Define a typealias which specializes one of the type parameters as such :

typealias IntField<Tag> = Field<Tag,Int>

Define an extension based on the typealias:

extension IntField {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

Expected behavior
I would expect this code to compile.

Instead the compiler complained: "Binary operator '+' cannot be applied to operands of type 'Value' and 'Int'"

If instead of extending the typealias, I extend the type directly it compiles fine as such:

extension Field where Value == Int {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

Environment

  • Swift compiler version info swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
  • Xcode version info: Xcode 14.3.1 Build version 14E300c
  • Deployment target: macOS Playground
@tapsns tapsns added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Aug 30, 2023
@AnthonyLatsis AnthonyLatsis added compiler The Swift compiler itself typealias Feature → type declarations: `typealias` declarations unexpected error Bug: Unexpected error extension Feature → declarations: `extension` declarations declarations Feature: declarations generics Feature: generic declarations and types swift 5.9 type checker Area → compiler: Semantic analysis TypeResolver good first issue Good for newcomers and removed triage needed This issue needs more specific labels labels Aug 30, 2023
@dochoi-bot
Copy link

I would like to solve this problem, It will be my first open source contribution.
Do you mind if I start?

@tapsns
Copy link
Author

tapsns commented Aug 31, 2023

I would like to solve this problem, It will be my first open source contribution. Do you mind if I start?

Sounds good. Thanks.

@dochoi-bot
Copy link

@AnthonyLatsis

image
I've completed this step, (highlighted text) but when the 'swift-frontent' was executed, I encountered an error as follows
img-1 2023-09-07 오전 1 58 23
What is the problem?
These are the things I have done
img-1 2023-09-07 오전 2 15 41

img-1 2023-09-07 오전 2 15 30

img-1 2023-09-07 오전 2 15 27

@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Sep 7, 2023

@dochoi-bot Does the following command make any difference in the terminal?

ninja -C /Users/dochoi/swift-project/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64 bin/swift-frontend && bin/swift-frontend /Users/dochoi/file.swift -typecheck

If not, you may have interrupted the build script before it finished building the standard library. You can build it by rerunning your build script invocation, or by running ninja swift-stdlib-macosx-x86_64 from within the swift-macosx-x86_64 directory. Targets can be listed with ninja -t targets.

@dochoi-bot
Copy link

Thanks to you, I solved it:)

@saehejkang
Copy link
Contributor

@dochoi-bot are you still working on this issue?

@dochoi-bot
Copy link

@saehejkang I tried it, but it's still unresolved.
If you want to, you can take this issue.

@saehejkang
Copy link
Contributor

If you want to, you can take this issue.

yes please, I would like to work on it

@AnthonyLatsis could you assign me to this issue? I was looking into this issue and am kind of stumped on where to begin. I was able to reproduce the issue and make updates to the ERROR message just to see how it all connects. Do updates need to be made in the Sema directory? One of the TypeChecker files?

@saehejkang
Copy link
Contributor

@AnthonyLatsis spending a good amount of time on this issue and I am pretty lost with how to tackle this.

Any hints/good starting points to start understanding what needs to be done? I feel I need to make some sort of change in this file here

@AnthonyLatsis
Copy link
Collaborator

I feel I need to make some sort of change in this file here

You’re not wrong, but besides adjusting the logic in this place, just as Element == Int is inferred for extension Array<Int>, we need to make sure to infer the correct generic requirements for extension IntField. A good starting point would be to read up on our AST.

@saehejkang
Copy link
Contributor

I read the blog by Slava wrote and it was really helpful!!! Understanding how the compiler builds the AST makes a lot more sense.

I just wanted to walk through a piece of code to better familiarize myself with what is happening.

// Check if you can take the extended type and get it as the the generic type Field<Tag, Value>
if (auto *unboundGeneric = extendedType->getAs<UnboundGenericType>()) {
  // Check if the typealias IntField is allowed with the generic that was declared
  if (auto *aliasDecl = dyn_cast<TypeAliasDecl>(unboundGeneric->getDecl())) {
    // get the underlying type of the typealias, which would be Field<Value, Int>
    auto underlyingType = aliasDecl->getUnderlyingType();
}

The next piece is a little tricky but I think I understand it (correct me if I am wrong).

if (auto extendedNominal = underlyingType->getAnyNominal()) {
  return TypeChecker::isPassThroughTypealias(
    aliasDecl, extendedNominal)
    ? extendedType
    : extendedNominal->getDeclaredType();
}

We are setting the extendedNominal variable to the NominalType. Is the NominalType check what you mean by

just as Element == Int is inferred for extension Array

The blog I read talked about what it means to be a NominalType and I think that is why the code snippet compiles below.

extension Field where Value == Int {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

Do we now have to add a check for the BoundGenericType , or is it another type?

@AnthonyLatsis
Copy link
Collaborator

// Check if you can take the extended type and get it as the the generic type Field<Tag, Value>
if (auto *unboundGeneric = extendedType->getAs<UnboundGenericType>()) {
  // Check if the typealias IntField is allowed with the generic that was declared
  if (auto *aliasDecl = dyn_cast<TypeAliasDecl>(unboundGeneric->getDecl())) {
    // get the underlying type of the typealias, which would be Field<Value, Int>
    auto underlyingType = aliasDecl->getUnderlyingType();
}

getAs and dyn_cast are dynamic casts (think of as?). UnboundGenericType is exactly that, a generic type that is not bound as in Array vs. Array<Int>.

We are setting the extendedNominal variable to the NominalType. Is the NominalType check what you mean by

just as Element == Int is inferred for extension Array

getAnyNominal does not return a NominalType. Have you looked at the function definition?

extension Array<Int> is syntactic sugar for extension Array where Element == Int. When building the generic signature of the former extension, the generic requirement Element == Int is inferred. This is how (not directly from the extended type) the compiler knows that the Element type parameter is Int inside the extension. Similarly, the requirement Value == Int must be inferred from extension IntField if we are to resolve this issue. Note that a valid extension of a type alias is an extension of the underlying nominal type declaration, not the type alias declaration itself.

If you’ve dealt with generic parameters and generic where clauses in Swift before, wrapping your head around a GenericSignature by inspecting debug output and looking around the codebase should be pretty straightforward. However, if you want some theory and an in-depth explanation of their role in the generics model, Slava has plenty of this goodness too.

@saehejkang
Copy link
Contributor

saehejkang commented Oct 14, 2023

The use of the syntactic sugar extension FieldInt is in fact an extension of the nominal type declaration, which in this case is Field<Tag,Int> ?

That is why this piece of code compiles. We are simply not using the syntactic sugar of the typealias and straight up defining the type the extension is using? It is pretty much making DIRECT reference for Value == Int, but we want this to be INFERRED with the usage of the typealias.

extension Field where Value == Int {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

@saehejkang
Copy link
Contributor

saehejkang commented Oct 28, 2023

Criteria for a pass-through-generic-type-alias. I added a list of criteria and a ✅ means that it passes with the issue.

  • typealias needs to refer to a nominal type.
if (!nominal) return false;
// typealias defined
typealias IntField<Tag> = Field<Tag,Int>
// it refers to the nominal type below 
struct Field<Tag,Value> {
  let tag: Tag
  let value: Value
}
  • The nominal type and the typealias are either both generic
if (nominal->isGeneric() != typealias->isGeneric())
   return false;

This is true for both the nominal and typealias in this case because they both have generic arguments

  • Both have generic signatures or neither do.
if (static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig))
   return false;

This is true because if they are both generic, then a generic signature exists

  • If neither is generic, we're done, it's a pass-through alias.
if (!nominalSig) return true;

Just checking one of the values to see it is not generic

  • Type parameters are the same the whole way through
auto nominalGenericParams = nominalSig.getGenericParams();
auto typealiasGenericParams = typealiasSig.getGenericParams();
if (nominalGenericParams.size() != typealiasGenericParams.size())
   return false;
..........

This is a check for GenericParams and making sure that they are same. This does not pass with the current issue but I see why it passes in other cases. I outputted the params and could see they were the same and that they were different with extending IntField.

  • Neither is generic at this level, we have a pass-through typealias.
if (!typealias->isGeneric()) return true;

Another simple check to see if the variable is generic

  • Check that the typeAlias->underlyingType and nominal->selfInterfaceType are the same
(bound_generic_struct_type decl="main.
  (generic_type_param_type depth=0 index=0 decl="main.(file).IntField.Tag
  (struct_type decl="Swift.(file).Int"))
(bound_generic_struct_type decl="main.(file).Field
  (generic_type_param_type depth=0 index=0 decl="main.(file).Field.Tag
  (generic_type_param_type depth=0 index=1 decl="main.(file).Field.Value

If the check ever gets this far, simply compare the two types if they are the same.

@saehejkang
Copy link
Contributor

So should I be adding a new TypeChecker function like isPassThroughTypealias, but only works for cases of extending a typealias that has a nominal type of struct? This one should be a more lenient check.

😰

@saehejkang
Copy link
Contributor

Check if the UnboundGenericType of the typealias equals the extendedType. If it is the same, we want to simply return extendedType, which in this case is IntField.

if(aliasDecl->getUnboundGenericType()->isEqual(extendedType)) {
   return extendedType;
}

Make this check inside the lines of TypeCheckDecl.cpp.

@AnthonyLatsis
Copy link
Collaborator

So should I be adding a new TypeChecker function like isPassThroughTypealias, but only works for cases of extending a typealias that has a nominal type of struct? This one should be a more lenient check.

That’s a more informed thought, which is good. A new function is a good idea in itself. Why only struct? The issue would be just as relevant if Field were an enum, or a class.

typealias needs to refer to a nominal type.

Not sure if this is a misconception or just colloquial language. There is a nominal type declaration (NominalTypeDecl), e.g. struct S {}, and then there is a nominal type (NominalType), e.g. S in let x: S`. These are different notions — one is a declaration and the other is a type. While you are still new to the compiler, I recommend articulating these differences in mind and prose to avoid confusing yourself in the first place. I’m going to assume you mean nominal type declaration.

This is true for both the nominal and typealias in this case because they both have generic arguments

Not arguments — parameters. Arguments are values that are substituted into parameters.

Both have generic signatures or neither do.

Do you understand how this differs from if (nominal->isGeneric() != typealias->isGeneric())? Would you be able to explain the difference with Swift examples?

Type parameters are the same the whole way through

This sounds rather ambiguous. Are you sure you understand what this means? Would you be able to explain it with Swift examples?

Check if the UnboundGenericType of the typealias equals the extendedType. If it is the same, we want to simply return extendedType, which in this case is IntField.

Have you tried reasoning about the scope of this condition? It works for this particular case, but what is a general example of Swift code that satisfies it the aforementioned context?

@saehejkang
Copy link
Contributor

Swift Example

struct Field<Tag,Value> {
  let tag: Tag
  let value: Value
}
typealias IntField<Tag> = Field<Tag,Int>

Do you understand how this differs from if (nominal->isGeneric() != typealias->isGeneric())? Would you be able to explain the difference with Swift examples?

The isGeneric check simply calls the getGenericParams function in Decl.cpp to see if the genericParams actually exist.

The genericParams of Field are <Tag, Value>. The genericParams of this IntField are <Tag>.

In this Swift example, both have genericParams so this check passes.

if (static_cast(nominalSig) != static_cast(typealiasSig)) return false;

Using the Swift example above, there are two GenericSignatures that exist, <Tag> for IntField and <Tag, Value> for Field, so this check passes as well. The genericParams look the "same" as the GenericSignature, but one is of type GenericParamList and the other is of type GenericSignature.

This sounds rather ambiguous. Are you sure you understand what this means? Would you be able to explain it with Swift examples?

Going deeper, there is a check for each GenericParamType to compare them (if it the size check passes).

(generic_type_param_type depth=0 index=0 decl="main.(file).Field.Tag
(generic_type_param_type depth=0 index=0 decl="main.(file).IntField.Tag
(generic_type_param_type depth=0 index=1 decl="main.(file).Field.Value
(error_type)

This is what is being compared ^ at a deeper level.

Have you tried reasoning about the scope of this condition? It works for this particular case, but what is a general example of Swift code that satisfies it the aforementioned context?

The scope of this check does not seem right because the isPassThroughTypealias function is still being called.

@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Nov 8, 2023

The isGeneric check simply calls the getGenericParams function in Decl.cpp to see if the genericParams actually exist.

The genericParams of Field are <Tag, Value>. The genericParams of this IntField are <Tag>.

In this Swift example, both have genericParams so this check passes.

Using the Swift example above, there are two GenericSignatures that exist, <Tag> for IntField and <Tag, Value> for Field, so this check passes as well. The genericParams look the "same" as the GenericSignature, but one is of type GenericParamList and the other is of type GenericSignature.

This is correct, but the IntField example doesn’t show the difference between the two conditions because they are both false. What is a Swift example where the former is false and the latter is true?

This sounds rather ambiguous. Are you sure you understand what this means? Would you be able to explain it with Swift examples?

Going deeper, there is a check for each GenericParamType to compare them (if it the size check passes).

Again, that you understand what is happening here is good, but how does this map to source code? What is a Swift example where the size matches, but the element-wise comparison fails?

Check that the typeAlias->underlyingType and nominal->selfInterfaceType are the same

Ditto, what is a Swift example where this is true/false? Just reading these if conditions does not make you feel like you fully understand what a pass-through type alias is, right? See if you can to pair them with Swift examples for the true & false cases, one by one. If you guess right but don’t understand why, be honest.

The current goal is to formulate the criteria relative to Swift source code, without using internal terms or mentioning implementation details.

@saehejkang
Copy link
Contributor

saehejkang commented Nov 9, 2023

Swift Example for nominal is not generic but typealias is

struct Field {
  let tag: Int
  let value: Int
}

typealias IntField<A, B> = Field

extension IntField {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

The nominal type of IntField is of type Field. But, Field is not generic in this case. However, the typealias IntField is generic. This would result in the former returning false and the latter check returning true.

Swift Example for genericSignatureCheck

This check is kind the same as the isGeneric check as above. If that check fails, the function will return false and it will never get to this check. If it does pass the generic check, then both of them will have a GenericSignature and continue through the function.

Swift Example for size matches but element wise comparison fails

struct Field<A, B> {
  let tag: Int
  let value: Int
}

typealias IntField<Tag, Value> = Field<Tag, Int>

extension IntField {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

The GenericSignature of both the nominal type and typealias type are both the same size. But, comparing the actual GenericParams closely, they are in fact different. One is <Tag, Value> and the other is <A, B>.

These are the params being compared below and they are different.

(generic_type_param_type depth=0 index=0 decl="main.(file).Field.A
(generic_type_param_type depth=0 index=0 decl="main.(file).IntField.Tag
(generic_type_param_type depth=0 index=1 decl="main.(file).Field.B
(generic_type_param_type depth=0 index=1 decl="main.(file).IntField.Value

Swift Example for comparing underlying type with self interface type

Gonna be honest, I can't think of an example that gets to this final check and returns true. Why do I feel like this is where the check needs to get updated to account for solving this issue 🤔 .

struct Field<A, B> {
  let tag: Int
  let value: Int
}

typealias IntField<A, B> = Field<A, B>

extension IntField {
  func adding(_ value: Int) -> Self {
    Field(tag: tag, value: self.value + value)
  }
}

Even with the Swift example above, the selfInterfaceType and underlyingType are not the same.

(bound_generic_struct_type decl="main.(file).Field
  (generic_type_param_type depth=0 index=0 decl="main.(file).IntField.A
  (generic_type_param_type depth=0 index=1 decl="main.(file).IntField.B
(bound_generic_struct_type decl="main.(file).Field
  (generic_type_param_type depth=0 index=0 decl="main.(file).Field.A
  (generic_type_param_type depth=0 index=1 decl="main.(file).Field.B

@AnthonyLatsis
Copy link
Collaborator

The nominal type of IntField is of type Field. But, Field is not generic in this case. However, the typealias IntField is generic. This would result in the former returning false and the latter check returning true.

This is not true. nominal->isGeneric() != typealias->isGeneric() will evaluate to true with this example, not false.

If it does pass the generic check, then both of them will have a GenericSignature and continue through the function.

This is also not true. nominal->isGeneric() != typealias->isGeneric() being false does not imply that both have a generic signature.

The GenericSignature of both the nominal type and typealias type are both the same size. But, comparing the actual GenericParams closely, they are in fact different. One is <Tag, Value> and the other is <A, B>.

Are you sure they are different? Then how come struct S<U> {}; typealias Alias<T> = S<T> is a pass-through type alias? Have you verified that the equality check actually evaluates to false?

Gonna be honest, I can't think of an example that gets to this final check and returns true.

Let’s get back to this one once we have a clear understanding of the rest.

@saehejkang
Copy link
Contributor

saehejkang commented Nov 9, 2023

This is not true. nominal->isGeneric() != typealias->isGeneric() will evaluate to true with this example, not false.

I worded this thought incorrectly. I was speaking about the two checks made and what THOSE evalutate to. nominal->isGeneric() evaluates to false and typealias->isGeneric() evaluates to true. Because they are not equal to each other, the statement in the if is executed and false is returned.

This is also not true. nominal->isGeneric() != typealias->isGeneric() being false does not imply that both have a generic signature.

I can see now with the example, how one of them can have a generic signature and the other can't have one either.

if (static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig)) checks to make sure both have a generic signature or none of them do.

Are you sure they are different? Then how come struct S {}; typealias Alias = S is a pass-through type alias? Have you verified that the equality check actually evaluates to false?

Completely realized I posted an example of it passing. Even after taking some time thinking about it, I can't think of an example where the sizes are the same but they don't pass. I can only think of examples where the sizes are not the same and that is why it fails.

@AnthonyLatsis
Copy link
Collaborator

I worded this thought incorrectly. I was speaking about the two checks made and what THOSE evalutate to. nominal->isGeneric() evaluates to false and typealias->isGeneric() evaluates to true. Because they are not equal to each other, the statement in the if is executed and false is returned.

Ah, I wasn’t clear enough. What I meant was a Swift example where nominal->isGeneric() != typealias->isGeneric() is false and static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) is true.

@saehejkang
Copy link
Contributor

saehejkang commented Nov 9, 2023

I can't think of an example where both nominal and typealias are generic BUT the one has a genericSignature and the other does not.

If they pass the first check, then we know that both are either generic or both are not generic. Now with the check for the genericSignature, we know that both will either have one or both will not have one.

To me, it seems like if any of them are generic, then a genericSignature exists. Are the genericParams being built based off the genericSignature? I read about it somewhere in here

@AnthonyLatsis
Copy link
Collaborator

I can't think of an example where both nominal and typealias are generic BUT the one has a genericSignature and the other does not.

If by generic you mean it has a generic parameter list, then indeed there is no such valid example. Hint: Calling isGeneric() is asking whether the declaration has a generic parameter list. Inner below doesn’t have a generic parameter list. What is its generic signature?

struct Outer<X> {
  struct Inner {}
}

Are the genericParams being built based off the genericSignature? I read about it somewhere in here

No, a generic parameter list is either parsed from source or derived from the generic parameter list of an associated declaration. Protocols are an exception: the generic parameter list of a protocol is derived from the protocol itself and has a fixed format — <Self: TheProtocol>.

@saehejkang
Copy link
Contributor

What is its generic signature?

I believe it is <X>

@AnthonyLatsis
Copy link
Collaborator

I believe it is <X>

So, does this light a bulb in regards to a Swift example?

@saehejkang
Copy link
Contributor

saehejkang commented Nov 12, 2023

So, does this light a bulb in regards to a Swift example?

The example you showed above with a struct defined in generic struct, just made me see that a genericSignature always exists as long as a genericParamlist exists.

The example below does not pass the isGeneric check because Inner does not have a genericParamList, thus it does not have a genericSignature. The typealias Alias does have a genericParamList and the genericSignature is <T>. BUT, because nominal->isGeneric() != typealias->isGeneric() evaluates to true , the function just returns false.

TLDR, I still don't see how nominal->isGeneric() != typealias->isGeneric() can be false and static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) can be true at the same time with any of these examples.

struct S<X> {
    struct Inner {};
};

typealias Alias<T> = S<T>.Inner

extension Alias {};

Thank you for being patient with me

@AnthonyLatsis
Copy link
Collaborator

Sorry for the delay.


TLDR, I still don't see how nominal->isGeneric() != typealias->isGeneric() can be false and static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) can be true at the same time with any of these examples.

That may be because the following two propositions contradict each other. Which one is right?

Inner does not have a genericParamList, thus it does not have a genericSignature.

Inner below doesn’t have a generic parameter list. What is its generic signature?

I believe it is <X>

@saehejkang
Copy link
Contributor

Which one is right?

Inner does not have a genericParamList but it has a genericSignature of <X>.

That may be because the following two propositions contradict each other.

I think that the typealias always needs to be generic in all the examples, because that is the only way to get into the check for a pass through type alias.

That is why I don't see any other way to get past the isGeneric check without both the nominal and typealias being defined as generic.

struct S<X> {
    struct Inner {};
};

typealias Alias<T> = S<T>.Inner

extension Alias {};

In theory, the example above will cause both of the lines below to evaluate to true. But we never get to the second statement check because the check for pass through type alias exits after the isGeneric check.

nominal->isGeneric() != typealias->isGeneric()
static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig)

@AnthonyLatsis
Copy link
Collaborator

I think that the typealias always needs to be generic in all the examples, because that is the only way to get into the check for a pass through type alias.

That is why I don't see any other way to get past the isGeneric check without both the nominal and typealias being defined as generic.

Ah, sorry, I ought to have been more clear. The goal is just to come up with an example of a type alias that points to a nominal type declaration where these conditions evaluate to false and true respectively, not one that necessarily reaches both if statements in that function. We’re not touching pass-through type aliases yet.

In theory, the example above will cause both of the lines below to evaluate to true.

Why would static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) evaluate to true?

@saehejkang
Copy link
Contributor

Why would static_cast(nominalSig) != static_cast(typealiasSig) evaluate to true?

It would not evaluate to true because nominalSig has a GenericSignature of X and typeAlias has a GenericSignature of T. It exists for both so the cast succeeds and they are both true thus the whole if evaluates to false.

@AnthonyLatsis
Copy link
Collaborator

Alright. You’ve argued that static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) will never be true if both are generic as in have a generic parameter list, which is true: having generic parameters means having a generic signature. But it can be true in the remaining case when nominal->isGeneric() != typealias->isGeneric() is false: if neither has a generic parameter list. Never mind isPassThroughTypealias, we’re talking about these predicates in isolation.

@saehejkang
Copy link
Contributor

having generic parameters means having a generic signature

This makes sense to me.

But it can be true in the remaining case when nominal->isGeneric() != typealias->isGeneric() is false: if neither has a generic parameter list.

I see how if neither have a generic param list then they both are not generic, thus the check above evaluates to false. But would that make static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) evaluate to false as well?

@AnthonyLatsis
Copy link
Collaborator

But would that make static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig) evaluate to false as well?

It depends. As we found out earlier, a nominal type declaration without a generic parameter list is not necessarily bereft of a generic signature.

@saehejkang
Copy link
Contributor

I am going to take a pause on this. If anyone else comes along and wants to work on this please unassign me and give it to them.

@xavgru12
Copy link

I would like to work on this if you don't mind. @saehejkang @AnthonyLatsis

I was able to reproduce this issue and see the signature with the -debug-generic-signatures flag.
I commented the isPassThroughTypealias in order to get rid of the fall back Field:
7025a0d

Why is the check isPassThroughTypealias check needed here after all? After commenting the complete "Hack to allow extending a generic typealias", extending the generic typealias throws an error in the code as expected.

I believe when the hack is resolved to a reasonable check, we can also fix this issue with ease.

In order to tackle this better I am required to debug the code using LLDB. I have not yet figured out how to run my example swift file and start the LLDB. If you could refer me to any tuturial, would be appreciated.

This is how I ran my swift file:
../withDebugSwift/build/Ninja-RelWithDebInfoAssert/swift-linux-x86_64/bin/swift-frontend -debug-generic-signa
tures issue.swift

@AnthonyLatsis AnthonyLatsis assigned xavgru12 and unassigned saehejkang Apr 21, 2024
@xavgru12
Copy link

Thanks for starting the CI for me @AnthonyLatsis.
I ran the tests locally as well.

It comes down to two failing tests:

 (1) Swift(linux-x86_64) :: Generics/requirement_inference.swift line 427
 (2) Swift(linux-x86_64) :: decl/ext/specialize.swift line 33

(1) This outputs two new error messages in line 427, which is setup to fail anyway. I did not go further in analyzing this one.

(2) ultimately Foo was typealiased to line IntFoo 25: declaring x to be of type Int. In line 33 x is defined to be "test" which is a String.
This test is setup to pass. I think this test should never pass and the error message makes sense for me:
"`- error: cannot convert value of type 'String' to expected argument type 'Int'"

Furthermore I setup LLDB and I will start playing around with it as well.
What do you think about it?

@xavgru12
Copy link

Investigating further into the specialize.swift test, I figured there is the where clause.
Here is an example code where I explain the the compiler error due to my code changes:
https://pastebin.com/Py5bMUV5

The where clause sets which of the two arguments is to be inferred. I am wondering if this where clause resolution is made by the type through alias. I would like to extract/split the type through alias function in order to keep the where clause functionality working while also fixing this bug.

Am I still on track?

@xavgru12
Copy link

Digging deeper I have stumbled upon this issue which was linked in decl/ext/specialize.swift: #47452
The mentioned test in (2) is indeed meant to fail. If the extension has a where clause, there needs to be a check that the struct/whatever is either non-generic or unspecialized. So this needs to fail before it notices that String is not Int. I will ignore this for now and have a look at it separately.

Back to requirement_inference (1)
There is exactly one failing test. the implementation for the test to pass seems to be isPassThroughTypealias. One big function for only one small test seems to be overkill for me. Is this function really needed for this one test or could this be simplified? Is the code coverage consistent or are there missing tests that justfify the function?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself declarations Feature: declarations extension Feature → declarations: `extension` declarations generics Feature: generic declarations and types good first issue Good for newcomers swift 5.9 type checker Area → compiler: Semantic analysis typealias Feature → type declarations: `typealias` declarations TypeResolver unexpected error Bug: Unexpected error
Projects
None yet
5 participants