ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SE-0531] Literal Expressions
    Swift 2026. 6. 21. 05:47

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

    이번 포스팅에서는 SE-0531 — Literal Expressions에 대해 정리해보겠습니다 🙋🏻


    Intro

    • Proposal: SE-0531
    • Authors: Artem Chikin, Doug Gregor
    • Review Manager: Ben Cohen
    • Status: Active Review (May 18...29, 2026)

    Motivation

    Swift에는 정수 리터럴 값만 허용하는 세 가지 문법 컨텍스트가 있습니다.

    generic value arguments(SE-0452), @section 변수(SE-0492), enum raw value.

    지금까지는 개발자가 직접 값을 계산해서 bare literal로 손수 옮겨 적어야 했어요.

    이 과정에서 값이 의미하는 바가 사라지고, 코드 전반에 "매직 넘버"가 퍼지게 됩니다.

     

    @section과 컴파일타임 초기화

    @section은 페이지 크기, 레지스터 오프셋처럼 다른 상수로부터 값이 파생되는 시스템/임베디드 환경을 겨냥한 어트리뷰트입니다.

    하지만 지금은 연산자나 변수 참조를 전혀 허용하지 않아요.

    @section("__DATA,config") let pageSize = 4096
    @section("__DATA,config") let bufferSize = 65536
    @section("__DATA,config") let c = 1 + 1  // ❌ error: operators not allowed

    40964 × 1024라는 사실도, bufferSize16 × pageSize라는 관계도 코드만 봐서는 전혀 알 수 없었어요.

     

    Enum raw value

    Swift 1.0부터 enum raw value는 순수 리터럴만 허용했습니다. bit-flag enum처럼 shift 표현이 자연스러운 경우조차 표현할 방법이 없었어요.

    enum Permissions: Int {
      case read = 1
      case write = 2
      case execute = 4
    }
    // 1 << 0, 1 << 1, 1 << 2 라고 쓰는 게 훨씬 의도가 명확한데도 불가능했어요

     

    정수 제네릭 인자

    SE-0452가 도입한 정수 제네릭 파라미터도 마찬가지로 bare integer literal만 허용합니다.

    let schemaRowSize = 32
    
    // 원하는 것: InlineArray<(2 * schemaRowSize), UInt8>
    let buffer: InlineArray<64, UInt8>  // 64 == 2 * 32 이길 바랄 뿐...

    세 가지 컨텍스트 모두 같은 근본적인 제약을 공유합니다.

    개발자가 사람 계산기 역할을 해야 했고, 그 값이 왜 그 숫자인지에 대한 의도는 사라져버렸어요 🥲


    Proposed Solution

    이 제안은 Literal Expression(리터럴 표현식)을 도입합니다.

    표준 라이브러리 정수 타입의 리터럴, 산술/비트 연산자, 그리고 컴파일타임에 알려진 다른 정수 변수 참조로 구성된 표현식이에요.

    이 표현식은 컴파일 타임에 단일 리터럴 값으로 상수 폴딩됩니다.

    // @section — 연산과 변수 참조 모두 가능
    @section("__DATA,config") let pageSize = 4 * 1024
    @section("__DATA,config") let bufferSize = 16 * pageSize
    
    // enum raw value — shift 표현이 가능
    enum Permissions: Int {
      case read    = 1 << 0
      case write   = 1 << 1
      case execute = 1 << 2
    }
    
    // 정수 제네릭 — 괄호로 감싼 표현식 허용
    let schemaRowSize = 32
    let buffer: InlineArray<(2 * schemaRowSize), UInt8>

    중요한 점은, literal expression을 사용해 컴파일된 모듈은 개발자가 직접 미리 계산한 리터럴을 쓴 모듈과 완전히 동일한 산출물을 만들어낸다는 거예요.


    Detailed Design

    Literal Expression의 문법

    literal-expression → integer-literal
    literal-expression → unary-operator literal-expression
    literal-expression → literal-expression binary-operator literal-expression
    literal-expression → '(' literal-expression ')'
    literal-expression → identifier

    지원하는 연산자: 산술(+ - * / %), wrapping(&+ &- &*), 비트(& | ^), shift(<< >>), masking shift(&<< &>>), 단항(+ - ~).

    일반 산술 연산자는 오버플로우를 컴파일 타임에 진단하고, wrapping 연산자는 결과를 타입의 비트 너비로 silent하게 모듈러 처리합니다.

    let a = 4 * 1024                  // ✅ 산술
    let b = 1 << 12                   // ✅ 비트 시프트
    let c = (0xFF & mask) | base      // ✅ 비트 연산 + 괄호
    let d = -1                        // ✅ 단항 부정
    let w: UInt8 = 250 &+ 10          // ✅ wrapping 덧셈, 4로 폴딩
    let e = Int.max / 2               // ❌ 프로퍼티 접근 불가
    let f = a +% b                    // ❌ 사용자 정의 연산자 불가

    연산자 lookup은 Swift의 일반 name lookup 규칙을 그대로 따릅니다. 만

    약 lookup이 사용자 정의 오버로드로 해석되면, 표준 라이브러리 오버로드가 스코프에 같이 있더라도 전체 표현식이 거부됩니다.

     

    Literal Expression 내 변수 참조

    literal expression은 다른 변수를 이름으로 참조할 수 있습니다.

    단, 그 변수가 let 바인딩이고, 기본 초기화 값 자체도 literal expression이어야 해요.

    let pageSize = 4 * 1024
    let bufferSize = 16 * pageSize             // ✅ pageSize 참조
    
    import CSystem
    let systemBuffer = 4 * SYSTEM_PAGE_SIZE    // ✅ C 상수
    
    var mutableSize = 4096
    let derived = 2 * mutableSize              // ❌ var는 참조 불가
    
    let computed: Int = { 4096 }()             // ❌ 초기화식이 literal expression이 아님
    let derived2 = 2 * computed

    주의할 점public, package, open 접근 수준의 변수는 literal expression에서 참조할 수 없습니다.

    폴딩이 일어나면 해당 변수의 초기화식이 모듈의 ABI 표면에 노출되는 셈인데, 이는 이 제안의 "ABI 변경 없음" 원칙과 충돌하기 때문이에요.

     

    @section 변수 초기화식

    @section("__TEXT,config") let pageSize = 4 * 1024
    @section("__TEXT,config") let bufferSize = 16 * pageSize
    @section("__TEXT,config") let systemPage = PAGE_SIZE    // C 상수

    원본 표현식은 진단과 IDE 인덱싱을 위해 AST에 보존되지만, .swiftinterface 파일에는 출력되지 않습니다.

     

    Enum raw value

    enum Permissions: UInt8 {
      case read    = 1 << 0    // 1
      case write   = 1 << 1    // 2
      case execute = 1 << 2    // 4
    }
    
    enum Example: Int {
      case a = 2 + 2    // 4
      case b            // 5
    }

     

    정수 제네릭 인자

    generic-argument → type
    generic-argument → '-'? integer-literal
    generic-argument → '(' literal-expression ')'   // 새로 추가된 형태

    제네릭 인자 위치에서는 <, >, , 토큰이 구분자 역할을 하기 때문에, literal expression은 반드시 괄호로 감싸야 합니다.

    let schemaRowSize = 32
    let buffer: InlineArray<(2 * schemaRowSize), UInt8>    // ✅
    let flags: InlineArray<(1 << 4), Bool>                 // ✅
    let small: InlineArray<5, Int>                         // ✅ 기존처럼 괄호 없이도 동작

    폴딩된 값이 곧 타입 동일성을 결정합니다.

    InlineArray<(2 + 3), Int>InlineArray<5, Int>는 동일한 타입이에요.

     

    파일타임 진단

    let x: UInt8 = 100 * 3    // ❌ error: integer overflow
    let y = 10 / 0           // ❌ error: division by zero
    let z = 10 % 0           // ❌ error: division by zero
    
    @section("__TEXT,data") let a = abs(-1)    // ❌ error: not supported in a literal expression

    Source Compatibility

    이번 변경은 세 가지 표현식 컨텍스트를 확장할 뿐, 다른 곳에서 받아들여지던 표현식 형태를 제거하거나 바꾸지 않습니다.

    흥미로운 케이스 하나는 제네릭 인자 목록의 모호성 해소입니다.

    컴파일러는 괄호 안의 내용을 먼저 타입 표현식(튜플)으로 해석을 시도하고, 실패할 때만 literal expression으로 해석합니다.

    예를 들어 Foo<(a < b, c > .d)>(x) 같은 코드는 기존의 체이닝 비교 연산으로 폴백되어 의미가 바뀌지 않습니다.


    ABI Compatibility

    • literal expression은 컴파일러 프론트엔드 내에서 완전히 폴딩되며, 코드 생성·런타임·심볼 맹글링에 변화가 없습니다.
    • 정수 제네릭 인자의 경우 폴딩된 값이 모듈 인터페이스에 타입의 일부로 나타납니다 (예: InlineArray<5, Int>).
    • @section 변수와 enum raw value는 원본 표현식도, 폴딩된 상수도 모듈 인터페이스에 나타나지 않습니다.

    Implications on Adoption

    이 기능은 LiteralExpressions 실험적 기능 플래그로 게이팅되어 있습니다.

    폴딩이 컴파일 타임에 일어나고 생성되는 코드가 직접 작성한 리터럴과 동일하기 때문에, 최소 배포 타겟 제약이 없습니다.


    Future Directions

    • 괄호 제약 완화 — 제네릭 인자 위치에서 >, ==, , 를 stop 토큰으로 처리해 InlineArray<2 + 3, Int>처럼 괄호 없이도 파싱 가능하게 만드는 방향
    • 부동소수점 literal expression — Float, Double에 대한 산술 연산 지원
    • 문자열 literal expression — 컴파일타임 문자열 연결 및 보간
    • 비교 연산자 및 표준 함수 지원 확대 — ==, min(), max() 등, 나아가 컴파일타임 Bool을 활용한 삼항 연산자 및 if/else 표현식
    • 컴파일타임 프로그래밍 — SE-0359와 연계해 사용자 정의 순수 함수, 풍부한 데이터 타입까지 포괄하는 일반적인 컴파일타임 프로그래밍 모델로의 확장

    Alternatives Considered

    참조되는 모든 변수에 명시적 어노테이션 요구

    @const 같은 명시적 어노테이션을 요구하는 방식도 고려되었습니다.

    하지만 SE-0359 리뷰에서 이런 어노테이션의 "전염성(virality)"이 핵심 우려로 지적된 바 있어요.

    literal expression의 경우 어노테이션이 컴파일러가 이미 알고 있는 정보 이상을 제공하지 않기 때문에, 추론만으로 충분하다고 판단해 채택하지 않았습니다.

     

    "Constant Expression" 대신 "Literal Expression" 용어 사용

    C/C++의 전통을 따라 "constant expression"이라는 용어를 쓰는 방법도 있었지만, 스코프와 과대 약속의 위험이라는 두 가지 이유로 "literal expression"을 선택했습니다.

    Swift가 향후 더 큰 컴파일타임 개념을 도입할 때를 위해 "constant expression"이라는 이름을 아껴두는 것이 합리적이라고 판단했습니다.


    Conclusion

    지금까지 @section, enum raw value, 정수 제네릭 인자는 모두 "사람이 직접 계산해서 bare literal로 옮겨 적어야 하는" 동일한 제약을 공유하고 있었어요 🙌

    Literal Expression은 이 세 컨텍스트에 산술·비트·단항 연산과 변수 참조를 허용함으로써, 코드에 값의 의도를 직접 드러낼 수 있게 해줍니다.

    그러면서도 컴파일 타임에 완전히 폴딩되기 때문에 ABI나 런타임에는 전혀 영향이 없다는 점이 인상적이에요.

    매직 넘버 대신 16 * pageSize, 1 << 2처럼 의미가 드러나는 코드를 쓸 수 있게 되는, 작지만 가독성 측면에서 정말 실용적인 변화라고 생각합니다 😄


    References

     

    swift-evolution/proposals/0531-literal-expressions.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.