Skip to content

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

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

Open
tapsns opened this issue Aug 30, 2023 · 73 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 · 73 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)
  }
}

@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?

@AnthonyLatsis
Copy link
Collaborator

@xavgru12 Are you still interested in pursuing this? I doubt that simply circumventing isPassThroughTypealias is a good idea for the scope of this issue because we do not currently support extensions of most other generic type aliases. While doing so may appear to make some things work — not this issue, though — it will break other things down the line and complicate the solution.

@xavgru12
Copy link

xavgru12 commented Jan 6, 2025

@AnthonyLatsis Thanks for your reply. I am still interested in resolving this. I removed the line of code in order to check which consequences it has. Do any of my comments make sense? Your comment „we do not support extensions of most other generic type aliases“ seems to be an important point. Can you guide me into the correct direction?

@AnthonyLatsis
Copy link
Collaborator

To give you an idea of why we currently cannot properly extend just any generic type alias:

struct Foo<T> {}
typealias A<T> = Foo<(T, T)>
extension A {}

We need a generic extension to represent this, which we do not support:

extension <U> Foo where T == (U, U) {}

isPassThroughTypealias is an approximation of what generic type aliases we can extend today. It does not account for the case shown in this issue, and we can fix that. So the first step is to extend the definition of a pass-through type alias to encompass cases similar to the one in question.

@xavgru12
Copy link

xavgru12 commented Jan 17, 2025

@AnthonyLatsis
I need to know a few things:

#1
The existing tests do not seem to have 100% code coverage in this file.
This means I cannot rely on tests to understand the complete code behavior.

#2 I need to fix this issue in main, even if it was found in 5.8.1, right?

#3 Earlier you said that modifying the function isPassThroughTypealias is too
risky and to introduce a more lenient check. Now you are talking about
extending the definition which sounds like modifying the function.
This confused me.

#4 extending the definition
Line 3018 inside isPassThroughTypealias it returns false since the size of typealias is
different to the nominal one. This is because of the Int that is inferred.

This

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

is nominal since Tag and Value do not have a specific type like Int.
with the typealias Int is inferred so it is not nominal anymore.

Whenever in typealias a type like Int is inferred,
I need to have a check to detect it correctly and then it needs to be resolved to the where clause.

I noticed the difference in the Generic signature:
where clause:
Generic signature: <Tag, Value where Value == Int>

typealias:
Generic signature: <Tag, Value>

my possible next steps would be of this kind:
I need a check if a type is inferred via the typealias. There may be more restrictions that I must think of.
I need the original nominal type and the inferred type(extendedType might be just that?) in
order to write the where clause into the generic signature.
I need to know wether this must happen outside or inside isPassThroughTypealias.

Other than the size check from line 3018, are there any other checks inside isPassThroughTypealias that I need to think about?
One approach would be to take this isPassThroughTypealias as base and slightly make it less strict.

I am waiting for feedback so I do not walk into the wrong direction.

@xavgru12
Copy link

@AnthonyLatsis I do not think my @ works if I edit my post.

Anyways to make it more compact:
I will duplicate the function isPassThroughTypealias, kick out the part where it compares the generic signatures for equality and add in a check for identifying if the typealias introduced a type like Int from the example.
I am just not sure how to implement this check. I want to debug it, see some values and hopefully find some functions that give me what is needed to implement the check eg: isType (Int).

I am trying to compile the code in debug.
I believe that
./utils/build-script --release-debuginfo --debug-swift
will remove optimizations. It does not compile though.

./utils/build-script --debug
crashes because of memory peaks I guess.

Is there any way to remove optimizations for lib/Sema or even only in that one file without messing with cmake?

@AnthonyLatsis
Copy link
Collaborator

The existing tests do not seem to have 100% code coverage in this file.
This means I cannot rely on tests to understand the complete code behavior.

I am almost positive our tests exercise all the code paths in isPassThroughTypealias. But if you find otherwise, please let me know. I can help devise a test case.

I need to fix this issue in main, even if it was found in 5.8.1, right?

Right. We cannot back-deploy a fix for this. Either way we primarily want to land a fix on main.

Earlier you said that modifying the function isPassThroughTypealias is too risky and to introduce a more lenient check. Now you are talking about extending the definition which sounds like modifying the function. This confused me.

The point was to take one step at a time. isPassThroughTypealias is used in a couple of places. Expanding the scope of your change gradually facilitates progress when working with unfamiliar code.

is nominal since Tag and Value do not have a specific type like Int.

A nominal in this context is a nominal type declaration (struct, enum, protocol, class, actor).


Are you following docs/HowToGuides/GettingStarted.md? What is the error? You cannot configure the build variant per library without messing with the generated build rules.

@xavgru12
Copy link

xavgru12 commented Jan 28, 2025

@AnthonyLatsis thanks for the reply and for bearing with me

The point was to take one step at a time. isPassThroughTypealias is used in a couple of places. Expanding the scope of your change gradually facilitates progress when working with unfamiliar code.

I think we need to separate means of getting there from what actually solves the issue as this confuses me a lot of the time

Are you following docs/HowToGuides/GettingStarted.md? What is the error? You cannot configure the build variant per library without messing with the generated build rules.

Instead of release with debug info I wanted to build with optimizations off. I thought --debug-swift does this job, yet it does not compile so I continue using release with debug info. I saw inside the build folder a compile_commands.json is generated. If I turn off optimizations for this source file, that might already solve that. However I assume that this file gets overwritten with every cmake configure and I am unsure if and when cmake configure is called from the build script.

Back to solving the issue:

My understanding is we need to extend the checks so this case is returns true and therefore extendedType is returned instead of nominal->getDeclaredType()

I need to check if the type inferred by typealias goes from generic to non generic (Int). I see that there is a method called isGeneric(). I just need the right two objects to call isGeneric() on. If so, return true and that is it.

@xavgru12
Copy link

--debug-swift compile error is this one at #78815

@xavgru12
Copy link

I stepped through isTypeThroughAlias
and printed two objects

(lldb) p *nominalSig.Ptr
(const swift::GenericSignatureImpl) {
  llvm::FoldingSetNode = (NextInFoldingSetBucket = 0x0000555562250b39)
  NumGenericParams = 2
  NumRequirements = 4
  GenericEnv = 0x00005555624e3a70
  Machine = 0x000055556226ae10
  CanonicalSignatureOrASTContext = {
    llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 0, const swift::GenericSignatureImpl *, swift::ASTContext *> = {
      llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 1, swift::ASTContext *> = {
        llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 2> = {
          Val = {
            Value ={...}
          }
        }
      }
    }
  }
}

(lldb)  p *typealiasSig.Ptr
(const swift::GenericSignatureImpl) {
  llvm::FoldingSetNode = (NextInFoldingSetBucket = 0x0000555562250af1)
  NumGenericParams = 1
  NumRequirements = 2
  GenericEnv = 0x0000555562509210
  Machine = 0x00005555625056b0
  CanonicalSignatureOrASTContext = {
    llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 0, const swift::GenericSignatureImpl *, swift::ASTContext *> = {
      llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 1, swift::ASTContext *> = {
        llvm::pointer_union_detail::PointerUnionMembers<llvm::PointerUnion<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPair<void *, 1, int, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *>, llvm::PointerIntPairInfo<void *, 1, llvm::pointer_union_detail::PointerUnionUIntTraits<const swift::GenericSignatureImpl *, swift::ASTContext *> > >, 2> = {
          Val = {
            Value ={...}
          }
        }
      }
    }
  }
}

one difference that NumGenericParams went from 2 to 1. This is exactly what I expected since the inferred type Int is non generic.
As I saw by the logger, canonical generic signature contains something like this:
<τ_0_0, τ_0_1 where τ_0_1 == Int>

I need to further explore the structure CanonicalSignatureOrASTContext. I need to learn how to print values and nested values using only the lldb shell.

My goal is to compare the typealias with the struct, check if the inferred argument changed from generic to non generic so I return true and therefore return the extendedType.

@AnthonyLatsis
Copy link
Collaborator

My understanding is we need to extend the checks so this case is returns true and therefore extendedType is returned instead of nominal->getDeclaredType()

Yes.

one difference that NumGenericParams went from 2 to 1. This is exactly what I expected since the inferred type Int is non generic.

The generic signature here is a property of the declaration. typealias IntField<Tag> has 1 generic parameter (<Tag>), and struct Field<Tag, Value> has 2 (<Tag, Value>).

I need to further explore the structure CanonicalSignatureOrASTContext. I need to learn how to print values and nested values using only the lldb shell.

Many of our classes, including GenericSignature, have a dump() method for this, declared using the SWIFT_DEBUG_DUMP macro. Note that sometimes dumpers have custom names.

@xavgru12
Copy link

@AnthonyLatsis

I printed the typealias with this:

(lldb) p typealias->dump()
(typealias range=[extensionTypealias.swift:6:1 - line:6:40] "IntField" "<Tag>" interface type="IntField<Tag>.Type" access=internal type="Field<Tag, Int>")

The nominal has the two generic parameters:

(lldb) p ((swift::TypeDecl *)nominal)->getDeclaredInterfaceType()->dump()
(bound_generic_struct_type decl="extensionTypealias.(file)[email protected]:1:8"
  (generic_type_param_type depth=0 index=0 name="Tag" param_kind=type)
  (generic_type_param_type depth=0 index=1 name="Value" param_kind=type))

The typealias has the Int which is inferred and the other generic parameter:

(lldb) p ((swift::TypeAliasDecl *)typealias)->getUnderlyingType()->dump()
(bound_generic_struct_type decl="extensionTypealias.(file)[email protected]:1:8"
  (generic_type_param_type depth=0 index=0 name="Tag" param_kind=type)
  (struct_type decl="Swift.(file).Int"))

I would like to iterate over the parameters of nominal and typealias and check if the inferred parameter changed from generic to non generic.

I am struggling to find a way to iterate over the parameters.
I found TypeBase::isEqual and
TypeBase::matchesParameter, which could help me.

Is there any method/function which does what I am looking for?

@AnthonyLatsis
Copy link
Collaborator

You can downcast the type objects to BoundGenericType like this: x->getAs<BoundGenericType>(). This class has methods to access the generic arguments.

@xavgru12
Copy link

xavgru12 commented Mar 9, 2025

@AnthonyLatsis

Thank you for your help!
I added this code at #73169 commit e513182

  auto nominalGenericArguments = ((nominal->getDeclaredInterfaceType().getPointer())->getAs<BoundGenericType>())->getGenericArgs();
  auto typealiasGenericArguments = ((typealias->getUnderlyingType().getPointer())->getAs<BoundGenericType>())->getGenericArgs();

  if (nominalGenericArguments.size() !=typealiasGenericArguments.size()) {
    //std::cerr << "Error: ArrayRefs must have the same size.\n";
    return false;
  }

  for (size_t i=0; i<nominalGenericArguments.size(); i++){
    auto nominalBoundGenericType = nominalGenericArguments[i];
    if (auto classTy = dyn_cast<GenericTypeParamType>(nominalBoundGenericType)){
      auto typealiasBoundGenericType = typealiasGenericArguments[i];
      if (auto classTy = dyn_cast<StructType>(typealiasBoundGenericType)){
        return true;
      }
    }
  }

This checks if the type changed from generic to struct and if so, it immediately returns true.
I still need to account for the case when the struct has more arguments and apart from generic to struct, some other non supported arguments with type conversions are entered.

For that I would need to iterate over the arguments and check if the type changed to something unsupported. I would like to use std::is_same<decltype(obj1), decltype(obj2)>::value for that.

Let me know if I am still on track.

Also:
I tried using std::is_same in the lldb shell, but it gave me errors. Debugging the code also shows that it is optimized in some way. Could you tell me how to turn off optimizations for one file(TypeCheckDecl.cpp)?

(lldb) p std::is_same<int, int>::value
error: <user expression 3>:1:6: no member named 'is_same' in namespace 'std'
std::is_same<int, int>::value

@xavgru12
Copy link

I pushed another commit: #73169 at 06d2a96

This avoids returning true for unsupported cases.
Let me know what you think.

@xavgru12
Copy link

I fixed the failing unit test. The github issue says that the test is supposed to fail anyway, but for another reason with another error message.

I also added the unit test for the now supported case.
I will stop and wait for feedback now in order to make sure I am still on track.

@xavgru12
Copy link

@AnthonyLatsis Could you have a look?

@xavgru12
Copy link

xavgru12 commented Apr 1, 2025

@AnthonyLatsis
Since I got no feedback, I cleaned up my code.
Can you look now?

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