swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.2k stars 10.32k forks source link

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

Open zdtorok opened 1 year ago

zdtorok commented 1 year ago

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

AnthonyLatsis commented 1 year ago

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()
  }
}
zdtorok commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

AnthonyLatsis commented 1 month ago

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.

petermauger commented 1 month ago

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