ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SE-0522] Source-Level Control Over Compiler Warnings
    Swift 2026. 5. 23. 08:45

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 SE-0522 — 소스 레벨에서의 컴파일러 경고 제어에 대해 정리해보겠습니다 🙋🏻


    Intro

    • Proposal: SE-0522
    • Authors: Artem Chikin, Doug Gregor, Holly Borla
    • Review Manager: Tony Allevato
    • Status: Accepted

    Motivation

    SE-0443에서는 커맨드라인 플래그로 컴파일러 경고를 제어할 수 있게 되었습니다.

    예를 들어 -Werror DeprecatedDeclaration을 사용하면 Deprecated 경고를 에러로 격상시킬 수 있죠.

    하지만 이 방식은 모듈 전체에 적용되는 블런트한 수단이에요.

    특정 선언에서만 예외를 두고 싶은 경우에는 대응할 방법이 없었습니다.

    // 모듈 전체에 -Werror DeprecatedDeclaration 적용 중
    func bridgeToLegacySystem() {
      oldAPI() // 🟥 error: 'oldAPI()' is deprecated [#Deprecated]
    }
    // 이 함수만 warning으로 낮추고 싶어도 방법이 없었어요

     

    마이그레이션 중인 코드베이스, 레거시 호환성 유지, strict 언어 모드의 점진적 도입 등 현실에서 "딱 이 선언만 예외"가 필요한 순간은 생각보다 많거든요!


    Proposed Solution

    새로운 선언 어트리뷰트 @diagnose를 도입합니다.

    어노테이션된 선언의 렉시컬 스코프 내에서 특정 diagnostic group의 경고 동작을 error, warning, ignored 세 가지 중 하나로 제어할 수 있어요.

     

    기본 문법

    @diagnose(group-identifier, as: error | warning | ignored)
    @diagnose(group-identifier, as: behavior, reason: "이유")

     

    동작 예시

    // 모듈 전체: -Werror DeprecatedDeclaration 적용 중
    
    func normalFunction() {
      oldAPI() // 🟥 error
    }
    
    @diagnose(DeprecatedDeclaration, as: warning,
               reason: "Must maintain compatibility until end of release cycle")
    func bridgeToLegacySystem() {
      oldAPI() // 🟨 warning: 'oldAPI()' is deprecated [#Deprecated]
    }

    ignored로 지정하면 해당 스코프에서 완전히 경고를 억제할 수 있어요.

     

    @diagnose(UnsafeImportedAPI, as: warning)
    struct LegacyFormatReader {
      func read(_ data: UnsafeRawPointer, _ count: Int) -> Header {
        c_read_bundle(data, count) // 🟨 warning
      }
    
      @diagnose(UnsafeImportedAPI, as: ignored, reason: "input is validated upstream")
      func readTrustedInput(_ data: UnsafeRawPointer, _ count: Int) -> Header {
        c_read_bundle(data, count) // 진단 없음
      }
    }

    서브그룹에도 개별적으로 적용이 가능합니다.

     

    // UnsafeImportedOwnership은 UnsafeImportedAPI의 서브그룹
    // 모듈 전체: -Werror UnsafeImportedAPI
    
    @diagnose(UnsafeImportedOwnership, as: warning)
    func createSession(_ ctx: OpaquePointer) -> Session {
      let session = c_create_session(ctx)
      // 🟨 warning: cannot infer ownership ... [#UnsafeImportedOwnership]
    
      c_bind_session(session, nil)
      // 🟥 error: call to imported function ... [#UnsafeImportedAPI]
    }

    Detailed Design

    적용 가능한 선언 종류

    @diagnose는 func, struct, class, enum, actor, protocol, extension, init, deinit, subscript, macro, typealias, associatedtype, get/set, willSet/didSet, import, enum case 등 대부분의 선언에 적용할 수 있습니다.

    @diagnose(DiagGroupID, as: ignored)
    import bar
    
    @diagnose(DiagGroupID, as: ignored, reason: "Proposal Example")
    func foo() { ... }
    
    struct Foo {
      var property: Int {
        @diagnose(DiagGroupID, as: ignored)
        get { ... }
        @diagnose(DiagGroupID, as: ignored)
        set { ... }
      }
    }

    여러 @diagnose 어트리뷰트 — 순서가 중요합니다!

    동일한 선언에 여러 @diagnose를 적용할 경우 마지막(렉시컬 순서상 가장 아래)에 있는 어트리뷰트가 우선합니다.

     

    @diagnose(DiagGroupID, as: error)
    @diagnose(DiagGroupID, as: warning) // 이 쪽이 최종 적용됨
    public func foo() { ... }
    Swift 어트리뷰트는 일반적으로 순서 무관하지만, @diagnose는 SE-0443의 커맨드라인 플래그 동작과 일관성을 맞추기 위해 순서가 중요합니다!

     

    -suppress-warnings와의 관계

    -suppress-warnings 플래그가 적용된 상태에서는 @diagnose가 완전히 무시됩니다.

    as: error를 지정해도 에러로 격상되지 않아요.

    이는 SE-0480에서 SwiftPM이 외부 패키지 의존성 빌드 시 경고 관련 플래그를 -suppress-warnings로 대체하는 방식과의 일관성을 위한 것입니다.

     

    매크로와의 상호작용

    @diagnose가 적용된 스코프 내에서 매크로가 코드를 생성하면, 그 생성된 코드에도 어트리뷰트의 효과가 적용됩니다.

    또한 매크로 작성자가 매크로 확장 코드 안에 직접 @diagnose를 생성할 수도 있어요.

    // 매크로 확장 결과물에 @diagnose가 포함될 수 있어요
    @diagnose(DeprecatedDeclaration, as: error)
    public func endpoint() -> UserProvidedType {
      // 🟥 error: 'UserProvidedType' is deprecated [#DeprecatedDeclaration]
    }

    단, 어트리뷰트가 적용된 선언에 peer 매크로가 붙어 있는 경우, @diagnose의 효과는 peer로 생성된 독립 선언에는 전파되지 않습니다.


    Source Compatibility / ABI

    • 이번 변경은 순수 additive 변경으로 기존 소스 호환성에 영향이 없습니다.
    • ABI에도 영향이 없으며, 텍스트 모듈 인터페이스에도 방출되지 않습니다.
    • 배포 제약 없이 자유롭게 도입하거나 제거할 수 있습니다.

    Alternatives Considered

    Region-based #pragma 스타일 지시문

    Clang 등 C계열 컴파일러처럼 임의 코드 영역에 #pragma clang diagnostic push/pop을 사용하는 방식도 고려되었어요.

    하지만 Swift는 메타데이터와 동작을 선언에 귀속시키는 방식을 선호하고, 영역의 끝을 수동으로 관리해야 한다는 점이 복잡한 버그로 이어질 수 있어 채택되지 않았습니다.

     

    매크로 생성 코드에서 @diagnose 금지하기

    모든 경고 제어가 개발자 소스 코드에서 명시적으로 보이도록 매크로 확장에서 @diagnose 생성을 금지하는 방법도 고려되었지만, 매크로 작성자가 자신이 생성하는 코드에 대한 진단 정책을 표현할 수 없게 되므로 허용하는 방향으로 결정되었습니다.

     

    -suppress-warnings 하에서도 as: error 적용하기

    무조건적인 소스 레벨 에러 격상을 위해 -suppress-warnings 하에서도 as: error를 적용하는 방법도 고려되었지만, SE-0443 및 SE-0480과의 일관성 유지를 위해 채택되지 않았습니다.


    Future Directions

    • 로컬 렉시컬 스코프 제어do {} 블록처럼 선언이 아닌 임의 스코프에도 적용하는 방향
    • 클로저 표현식 — 특정 클로저 body의 경고 동작 제어
    • 파일 스코프 제어using @diagnose(...) 형태의 파일 범위 제어
    • 서드파티 툴 통합@diagnose(rule, from: SwiftLint, as: ignored) 같은 형태로 린터 등과의 통합

    Conclusion

    커맨드라인 플래그로만 가능했던 경고 제어를 소스 레벨의 선언 단위로 세밀하게 다룰 수 있게 해주는 실용적인 변경입니다 🙌

    마이그레이션 중인 코드베이스, 레거시 호환성 유지, strict 언어 모드의 점진적 도입 등 현실적인 시나리오에서 개발자가 예외의 의도를 코드에 명확하게 문서화할 수 있게 됩니다.

    Swift가 더 엄격한 언어 모드와 정적 분석을 강화해가는 흐름 속에서, @diagnose는 그 여정을 훨씬 유연하게 해줄 도구라고 생각합니다 😄


    References

     

    swift-evolution/proposals/0522-source-warning-control.md at main · swiftlang/swift-evolution

    This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

    github.com

     

Designed by Tistory.