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 crashes during type checking when using protocol and associated type #62219

Open
zdtorok opened this issue Nov 22, 2022 · 7 comments · Fixed by #69950
Open

Compiler crashes during type checking when using protocol and associated type #62219

zdtorok opened this issue Nov 22, 2022 · 7 comments · Fixed by #69950
Assignees
Labels
assertion failure Bug → crash: An assertion failure bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself crash Bug: A crash, i.e., an abnormal termination of software existential member accesses Feature → existentials: existential member accesses existentials Feature: values of types like `any Collection`, `Any` and `AnyObject`; type-erased values swift 6.0 type checker Area → compiler: Semantic analysis

Comments

@zdtorok
Copy link

zdtorok commented Nov 22, 2022

Description

Compiler crashes during type checking when using protocol and associated type. Xcode gives no errors or warnings on the source code.
The following code will reproduce the issue:

struct Action {
    var intVar: Int
    var strVar: String
}

protocol TestDelegate: AnyObject {
    associatedtype ActionType
    var actions: [ActionType] { get set }
}

class TestDelegateImpl: TestDelegate {
    typealias ActionType = Action
    var actions: [Action] = []
}

class TestViewController {
    var testDelegate: (any TestDelegate)?
    func testFunc() {
        testDelegate?.actions.removeAll() // Crash happens because of this line
    }
}

let tVC = TestViewController()
tVC.testDelegate = TestDelegateImpl()
tVC.testFunc()

The stack dump:

1.	Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
2.	Compiling with the current language version
3.	While evaluating request TypeCheckSourceFileRequest(source_file "Contents.swift")
4.	While evaluating request TypeCheckFunctionBodyRequest(Contents.(file).TestViewController.testFunc()@Contents.swift:18:10)
5.	While type-checking statement at [Contents.swift:18:21 - line:20:5] RangeText="{
        testDelegate?.actions.removeAll()
    "
6.	While type-checking expression at [Contents.swift:19:9 - line:19:41] RangeText="testDelegate?.actions.removeAll("
7.	While type-checking-target starting at Contents.swift:19:31
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x00000001078135b0 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x00000001078125b4 llvm::sys::RunSignalHandlers() + 112
2  swift-frontend           0x0000000107813c34 SignalHandler(int) + 344
3  libsystem_platform.dylib 0x00000001a29542a4 _sigtramp + 56
4  swift-frontend           0x0000000103939b38 (anonymous namespace)::ExprRewriter::coerceToType(swift::Expr*, swift::Type, swift::constraints::ConstraintLocatorBuilder, llvm::Optional<swift::Pattern*>) + 16868
5  swift-frontend           0x00000001039388c8 (anonymous namespace)::ExprRewriter::coerceToType(swift::Expr*, swift::Type, swift::constraints::ConstraintLocatorBuilder, llvm::Optional<swift::Pattern*>) + 12148
6  swift-frontend           0x000000010395c5e8 (anonymous namespace)::ExprRewriter::closeExistential(swift::Expr*&, swift::constraints::ConstraintLocatorBuilder, bool) + 340
7  swift-frontend           0x00000001039547c0 (anonymous namespace)::ExprRewriter::buildMemberRef(swift::Expr*, swift::SourceLoc, swift::constraints::SelectedOverload, swift::DeclNameLoc, swift::constraints::ConstraintLocatorBuilder, swift::constraints::ConstraintLocatorBuilder, bool, swift::AccessSemantics) + 11204
8  swift-frontend           0x0000000103941f50 swift::ASTVisitor<(anonymous namespace)::ExprRewriter, swift::Expr*, void, void, void, void, void>::visit(swift::Expr*) + 8156
9  swift-frontend           0x000000010393cd58 (anonymous namespace)::ExprWalker::walkToExprPost(swift::Expr*) + 24
10 swift-frontend           0x0000000103f944e8 swift::ASTVisitor<(anonymous namespace)::Traversal, swift::Expr*, swift::Stmt*, bool, swift::Pattern*, bool, void>::visit(swift::Expr*) + 268
11 swift-frontend           0x0000000103f94538 swift::ASTVisitor<(anonymous namespace)::Traversal, swift::Expr*, swift::Stmt*, bool, swift::Pattern*, bool, void>::visit(swift::Expr*) + 348
12 swift-frontend           0x0000000103f9445c swift::ASTVisitor<(anonymous namespace)::Traversal, swift::Expr*, swift::Stmt*, bool, swift::Pattern*, bool, void>::visit(swift::Expr*) + 128
13 swift-frontend           0x0000000103932a78 (anonymous namespace)::ExprWalker::rewriteTarget(swift::constraints::SolutionApplicationTarget) + 376
14 swift-frontend           0x00000001039325ec swift::constraints::ConstraintSystem::applySolution(swift::constraints::Solution&, swift::constraints::SolutionApplicationTarget) + 5852
15 swift-frontend           0x0000000103b9a104 swift::TypeChecker::typeCheckTarget(swift::constraints::SolutionApplicationTarget&, swift::OptionSet<swift::TypeCheckExprFlags, unsigned int>) + 688
16 swift-frontend           0x0000000103b99de4 swift::TypeChecker::typeCheckExpression(swift::constraints::SolutionApplicationTarget&, swift::OptionSet<swift::TypeCheckExprFlags, unsigned int>) + 400
17 swift-frontend           0x0000000103c74564 (anonymous namespace)::StmtChecker::typeCheckASTNode(swift::ASTNode&) + 300
18 swift-frontend           0x0000000103c77a64 swift::ASTVisitor<(anonymous namespace)::StmtChecker, void, swift::Stmt*, void, void, void, void>::visit(swift::Stmt*) + 328
19 swift-frontend           0x0000000103c76540 bool (anonymous namespace)::StmtChecker::typeCheckStmt<swift::BraceStmt>(swift::BraceStmt*&) + 300
20 swift-frontend           0x0000000103c7538c swift::TypeCheckFunctionBodyRequest::evaluate(swift::Evaluator&, swift::AbstractFunctionDecl*) const + 1276
21 swift-frontend           0x000000010406ee64 llvm::Expected<swift::TypeCheckFunctionBodyRequest::OutputType> swift::Evaluator::getResultUncached<swift::TypeCheckFunctionBodyRequest>(swift::TypeCheckFunctionBodyRequest const&) + 588
22 swift-frontend           0x0000000104001e64 swift::TypeCheckFunctionBodyRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckFunctionBodyRequest>(swift::Evaluator&, swift::TypeCheckFunctionBodyRequest, swift::TypeCheckFunctionBodyRequest::OutputType) + 116
23 swift-frontend           0x0000000103cb1db0 swift::TypeCheckSourceFileRequest::evaluate(swift::Evaluator&, swift::SourceFile*) const + 792
24 swift-frontend           0x0000000103cb4960 llvm::Expected<swift::TypeCheckSourceFileRequest::OutputType> swift::Evaluator::getResultUncached<swift::TypeCheckSourceFileRequest>(swift::TypeCheckSourceFileRequest const&) + 576
25 swift-frontend           0x0000000103cb1a44 swift::performTypeChecking(swift::SourceFile&) + 120
26 swift-frontend           0x0000000102ed2b8c bool llvm::function_ref<bool (swift::SourceFile&)>::callback_fn<swift::CompilerInstance::performSema()::$_6>(long, swift::SourceFile&) + 16
27 swift-frontend           0x0000000102ecd300 swift::CompilerInstance::forEachFileToTypeCheck(llvm::function_ref<bool (swift::SourceFile&)>) + 288
28 swift-frontend           0x0000000102ecd1a4 swift::CompilerInstance::performSema() + 148
29 swift-frontend           0x0000000102e602ac swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 4364
30 swift-frontend           0x0000000102e013c0 swift::mainEntry(int, char const**) + 3940
31 dyld                     0x00000001a25fbe50 start +

Steps to reproduce

Simply compile the above code from command line or in a playground/Xcode.

Expected behavior

I would expect that the compiler deducts the type of testDelegate?.actions properly and the code builds without errors.

Environment

  • Swift compiler version info:
    swift-driver version: 1.62.15 Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
    Target: arm64-apple-macosx13.0
  • Xcode version info: Xcode Version 14.1 (14B47b)
  • Deployment target: iOS 16.1
@zdtorok zdtorok added the bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. label Nov 22, 2022
@AnthonyLatsis AnthonyLatsis added compiler The Swift compiler itself compiler crash crash Bug: A crash, i.e., an abnormal termination of software type checker Area → compiler: Semantic analysis labels Nov 23, 2022
@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Nov 23, 2022

Reduced:

protocol P {
  associatedtype A
  var s: [A] { get set }
}

var p: any P
let s = p.s

We should be we rejecting this: SE-0309 is not implemented for settable storage requirements yet. You can work around it with a protocol extension:

extension TestDelegate {
  func removeAllActions() {
    self.actions.removeAll()
  }
}

@AnthonyLatsis AnthonyLatsis added the existentials Feature: values of types like `any Collection`, `Any` and `AnyObject`; type-erased values label Nov 23, 2022
@AnthonyLatsis AnthonyLatsis self-assigned this Nov 23, 2022
@zdtorok
Copy link
Author

zdtorok commented Nov 24, 2022

Thanks, your workaround seems to work, but what if I have to e.g. insert a new element into this array? Like the following:

protocol ActionType {
    associatedtype T
    var value: T? { get set }
}

protocol P {
    associatedtype AType: ActionType
    var actions: [AType] { get set }

    func removeElements()
    func addElement(_ action: AType)
}

class ActionTypeImpl: ActionType {
    typealias T = Int
    var value: Int?

    init(value: Int? = nil) {
      self.value = value
    }
}

class PImpl: P {
    typealias AType = ActionTypeImpl
    var actions: [ActionTypeImpl] = []

    func removeElements() {
        actions.removeAll()
    }
    func addElement(_ action: ActionTypeImpl) {
        actions.append(action)
    }
}

class TestClass {
    var pImpl: (any P)?
}

let testClass = TestClass()
testClass.pImpl = PImpl()

testClass.pImpl?.removeElements() // <-- This works

let newAction = ActionTypeImpl()
testClass.pImpl?.addElement(newAction) // <-- Member 'addElement' cannot be used on value of type 'any P'; consider using a generic constraint instead

With removeElements() it's working because I don't pass any actual object, but is there a workaround for this case too?
As I understand it's a similar issue, at the end it cannot determine the underlying type of T maybe, which is now an Int?

@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Nov 24, 2022

any P (similar to a generic parameter <T : P>) is an abstraction over the underlying conforming type, so in itself it does not allow you to make such assumptions about the types that depend on the conforming type (associated types). The compiler does not "see" beyond the current expression when it comes to type safety, i.e., it doesn’t care that you assigned pImpl to an instance of PImpl earlier when type-checking the call to addElement. Speaking of solutions, if you want to keep the run-time abstraction in order to retain the ability of assigning values of different conforming types to pImpl, you can use a constrained existential, like so:

class TestClass<T: ActionType> {
    var pImpl: (any P<T>)?
}

Note that this is a new feature that requires runtime support and it may not suit your deployment target. Though, is the regular generic approach not an option in your case?

class TestClass<T: P> {
    var pImpl: T?
}

This is a tricky and subtle topic, so feel free to ask questions!

@zdtorok
Copy link
Author

zdtorok commented Dec 6, 2022

Thank you for your explanation. This constrained existential might work for my case, but after some rework of our code, it came out that we could simplify it, so at the end this level of abstraction was not needed anymore.
Anyhow it's good that there would have been a solution if I really needed this.
Thanks again for your help!

@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Dec 7, 2022

Good to hear! This kind of abstraction often tends to be the wrong answer.

@AnthonyLatsis AnthonyLatsis added swift 5.9 existential member accesses Feature → existentials: existential member accesses labels Apr 14, 2023
slavapestov added a commit to slavapestov/swift that referenced this issue Nov 17, 2023
…rased result type

- Fixes swiftlang#62219
- Fixes swiftlang#60619,
- Fixes rdar://104230391
- Fixes rdar://118247162.
slavapestov added a commit to slavapestov/swift that referenced this issue Nov 17, 2023
…rased result type

- Fixes swiftlang#62219
- Fixes swiftlang#60619,
- Fixes rdar://104230391
- Fixes rdar://118247162.
slavapestov added a commit to slavapestov/swift that referenced this issue Nov 27, 2023
…rased result type

- Fixes swiftlang#62219
- Fixes swiftlang#60619,
- Fixes rdar://104230391
- Fixes rdar://118247162.
@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Jul 5, 2024

This either regressed or was accidentally closed.

Edit: Ah, no, it just stopped crashing for a while, but has never errored out, which is what it is supposed to be doing.

@AnthonyLatsis AnthonyLatsis reopened this Jul 5, 2024
@AnthonyLatsis AnthonyLatsis added swift 6.0 assertion failure Bug → crash: An assertion failure and removed swift 5.9 labels Jul 5, 2024
@petermauger
Copy link

Yep, have definitely run into this problem with swift 6. Will look at refactoring my code :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assertion failure Bug → crash: An assertion failure bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler itself crash Bug: A crash, i.e., an abnormal termination of software existential member accesses Feature → existentials: existential member accesses existentials Feature: values of types like `any Collection`, `Any` and `AnyObject`; type-erased values swift 6.0 type checker Area → compiler: Semantic analysis
Projects
None yet
3 participants