Skip to content

[lldb] Implement TypeSystemSwiftTypeRef::IsPossibleDynamicType #2232

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

Conversation

kastiglione
Copy link

Implement TypeSystemSwiftTypeRef::IsPossibleDynamicType to match the SwiftASTContext implementation.

rdar://68170302

@kastiglione kastiglione marked this pull request as draft December 11, 2020 23:44
@kastiglione
Copy link
Author

This is written to match SwiftASTContext. I haven't run tests yet, I am publishing a draft because I have some questions.

The SwiftASTContext implementation is here: https://github.com/apple/llvm-project/blob/db5bc1d58bf76a0ebe709275711317ef6c9b52bb/lldb/source/Plugins/TypeSystem/Swift/SwiftASTContext.cpp#L5161-L5179

I'm not entirely sure how to translate swift::CanType predicates to swift::Demangle::Node. Hopefully the tests will identify gaps. But there are a couple of questions I have:

  1. I don't know how to translate hasArchetype() and hasOpaqueArchetype()
  2. Some node kinds might be relevant, but I am not sure how to know, for example AnyProtocolConformanceList.

I have other questions too, ex is ContainsGenericTypeParameter() equivalent to hasTypeParameter()? Hopefully tests will cover everything, but I'd also like to know how to know each of the two sides (swift types vs demangling/reflection) well enough to translate expressions between the two.

Copy link

@adrian-prantl adrian-prantl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I guess we could add unit tests for the cases in the switch if we want to.

return m_swift_ast_context->IsPossibleDynamicType(
ReconstructType(type), target_type, check_cplusplus, check_objc);
if (target_type)
target_type->Clear();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: This is a good example of some of LLDB's less than great APIs. I'm sure we're not very consistent in whether this should get cleared or not in the failure case...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In SwiftASTContext it is cleared unconditionally.

case Node::Kind::BuiltinTypeName: {
if (!node->hasText())
return false;
auto name = node->getText();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind spelling the type out here, so it's obvious that it isn't a char*? Otherwise the comparison below looks scary :-)

return true;
case Node::Kind::BuiltinTypeName: {
if (!node->hasText())
return false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: And here we should have an error channel ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by changing the return type of this function? or by logging?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just me rambling — you didn't design this API :-)

It would be best if all CompilerType operations had a way to signal an error case, so we aren't silently ignoring errors. Either by returning Optionals, Expected, and for the functions that return a new type, potentially even a special Error type in a special Error TypeSystem, so the operations still compose nicely.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree.

@adrian-prantl
Copy link

  1. I don't know how to translate hasArchetype() and hasOpaqueArchetype()

Surprise! HasArchetype() is probably always false. We should only see generic type parameters, but no Archetypes any more. I hope there is a testcase that breaks for the opaque archetype case.

  1. Some node kinds might be relevant, but I am not sure how to know, for example AnyProtocolConformanceList.

You could try and see what the type Proto1 & Proto2 mangles to.

I have other questions too, ex is ContainsGenericTypeParameter() equivalent to hasTypeParameter()? Hopefully tests will cover everything, but I'd also like to know how to know each of the two sides (swift types vs demangling/reflection) well enough to translate expressions between the two.

These two should be identical.

@adrian-prantl
Copy link

In func f<T>(_ t: T) {}

T: Archetype
tau_0_0: generic type parameter

Comment on lines 1794 to 1801
case Node::Kind::TypeAlias: {
if (node->getNumChildren() == 2) {
auto *module = node->getFirstChild();
auto *identifier = node->getLastChild();
return module->getKind() == Node::Kind::Module &&
identifier->getKind() == Node::Kind::Identifier &&
module->getText() == "Swift" &&
identifier->getText() == "AnyObject";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. $ss9AnyObjectaD caused a failure, and this block handles that specific case. I will investigate a better solution, probably a way to resolve the alias.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found AnyObject to be special before, too. Maybe this comes from SwiftASTContext but if it's in the compiler we probably just have to accept it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up replacing this with ResolveTypeAlias in 26fa282. Hopefully that's more reasonable.

auto *identifier = node->getLastChild();
return module->getKind() == Node::Kind::Module &&
identifier->getKind() == Node::Kind::Identifier &&
module->getText() == "Swift" &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: theres a swift::something_STDLIB_NAME constant in swift/strings.h that we should use here

@kastiglione
Copy link
Author

@swift-ci test

@kastiglione
Copy link
Author

It looks like this will have to go back to an explicit check for Swift.AnyObject type aliases (by reverting 26fa282).

The test failure is: $sSo18NSNotificationNameaD:

kind=Global
  kind=TypeMangling
    kind=Type
      kind=TypeAlias
        kind=Module, text="__C"
        kind=Identifier, text="NSNotificationName"

When resolving this alias, the result is a class (typedef NSString *NSNotificationName), and class types make IsPossibleDynamicType return true. However the SwiftASTContext implementation returns false.

@adrian-prantl
Copy link

It looks like this will have to go back to an explicit check for Swift.AnyObject type aliases (by reverting 26fa282).

The test failure is: $sSo18NSNotificationNameaD:

kind=Global
  kind=TypeMangling
    kind=Type
      kind=TypeAlias
        kind=Module, text="__C"
        kind=Identifier, text="NSNotificationName"

When resolving this alias, the result is a class (typedef NSString *NSNotificationName), and class types make IsPossibleDynamicType return true. However the SwiftASTContext implementation returns false.

Side note: There are a few Foundation types that are special-cased in ClangImporter and thus keep creating inconsistencies like this. I believe that this is on of them. You'll find mentions of it elsewhere, too.

In the overlay it is:

public typealias Name = NSNotification.Name

and as you said it points to just an NSString.I think we should return true here (since it is an ObjC class) and ignore this particular type in the comparison.

@kastiglione
Copy link
Author

I think we should return true here (since it is an ObjC class) and ignore this particular type in the comparison.

I wondered along these, whether this behavior is better.

  • It's an objc class (NSString), but a swift struct (String)
  • Similarly, in Swift Notification.Name is an alias for NSNotification.Name which in swift is a struct
  • Even when saying "this is a class", is there anything dynamic lldb needs to do? Intuitively I would say no, but maybe I'm wrong

These all lean toward saying the type is not dynamic, but I am just as fine doing what you said, and returning true.

@adrian-prantl
Copy link

I think we should return true here (since it is an ObjC class) and ignore this particular type in the comparison.

I wondered along these, whether this behavior is better.

  • It's an objc class (NSString), but a swift struct (String)

That's a good observation.

  • Similarly, in Swift Notification.Name is an alias for NSNotification.Name which in swift is a struct
  • Even when saying "this is a class", is there anything dynamic lldb needs to do? Intuitively I would say no, but maybe I'm wrong

These all lean toward saying the type is not dynamic, but I am just as fine doing what you said, and returning true.

Generally speaking, ObjC classes are dynamic, but NSString is special, because it is bridged to a Swift struct type.

@kastiglione
Copy link
Author

@swift-ci test

@kastiglione
Copy link
Author

I think we should return true here (since it is an ObjC class) and ignore this particular type in the comparison.

I went with this approach. The ignoring is done in 8552c56.

@kastiglione kastiglione marked this pull request as ready for review December 14, 2020 18:50
@kastiglione kastiglione merged commit c37c368 into swift/main Dec 14, 2020
@kastiglione kastiglione deleted the lldb-Implement-TypeSystemSwiftTypeRef-IsPossibleDynamicType branch December 14, 2020 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants