Burt.K

Awesome Discovery

[SE-0037] 주석과 연산자 간의 상호작용 명확화

작성일 — 2025년 3월 9일

Table of Contents

주석과 연산자 간의 상호작용 명확화

소개

연산자가 접두사, 접미사, 중위 연산자인지 판단할 때 주석을 처리하는 방식에 여러 불일치가 있다. 주석은 때로는 공백처럼 처리되기도 하고, 때로는 공백이 아닌 것으로 처리되기도 한다. 이러한 차이는 주석이 연산자의 왼쪽에 있는지 오른쪽에 있는지, 그리고 주석의 내용에 따라 달라진다. 이 제안은 이러한 경우를 어떻게 파싱할지에 대한 일관된 규칙을 제시한다.

Swift-evolution 스레드: 여기서 시작

초안 구현은 여기에서 확인할 수 있다.

리뷰

배경

현재 Swift에서는 연산자가 접두사(prefix), 접미사(postfix), 이항(binary) 연산자인지 판단할 때 연산자 옆의 주석을 공백이 아닌 것으로 처리한다. 이로 인해 다음과 같은 코드가 컴파일되지 않는 문제가 발생한다(SR-186):

if /* comment */!foo { ... }

여기서 “!“는 접두사 연산자로 처리되지 않고, 양쪽에 공백이 없기 때문에 이항 연산자로 파싱된다. 이는 바람직하지 않은 동작이다. 하지만 이러한 동작이 항상 일관되게 적용되지는 않는다. 예를 들어, 다음 코드는 현재 정상적으로 동작한다:

1 +/* comment */2

이 경우 “+/*“가 하나의 토큰으로 처리되고, 양쪽에 공백이 있기 때문에 이항 연산자로 파싱된다.

이와 관련된 문제를 해결하기 위해, 이 제안은 예상되는 동작에 대한 일반적인 규칙을 제시한다.

제안하는 해결책

주석은 Swift 언어 참조의 “연산자” 섹션에서 설명하는 모든 목적에서 공백으로 처리해야 한다. 이는 연산자가 바이너리, 접두사, 접미사인지 판단하는 규칙과 “!” 및 “?” 사전 정의 연산자에 대한 특수 규칙을 포함한다.

즉, 주석을 공백으로 교체해도 인접한 연산자 토큰이 접두사, 접미사, 바이너리로 처리되는 방식이 변경되지 않아야 한다(주석 내용과 상관없이). 이는 연산자와 피연산자 사이의 공백/주석도 포함한다.

예를 들어, 다음 두 코드는 동일하게 처리되어야 한다:

if/* comment */!foo { ... }
if !foo { ... }

다음 코드도 마찬가지로 동일해야 한다:

// 양쪽에 공백
1 + 2
1 +/* comment */2

이 모델은 설명하기 쉽고, Swift 언어 참조에 이미 존재하는 “주석은 공백으로 처리된다”는 일반 규칙과 잘 맞는다.

상세 설계

연산자 토큰을 파싱할 때, 오른쪽이나 왼쪽에 공백 문자가 있는지 확인하려면, 인접한 슬래시-슬래시(//) 또는 슬래시-스타(/*) 주석을 공백으로 처리해야 한다. 이 주석의 내용(예: 주석 자체에 공백이 포함되어 있는지 여부)은 아무런 영향을 미치지 않아야 한다.

언어 레퍼런스도 업데이트하여 이러한 목적을 위해 주석이 공백으로 처리된다는 점을 명확히 해야 한다.

기존 코드에 미치는 영향

연산자 바로 옆에 주석이 있는 코드만 영향을 받는다. 이런 경우는 흔하지 않으며, 공백을 추가하거나 주석을 표현식 밖으로 옮기는 방식으로 수정할 수 있다. 이러한 문제에 대한 자동 수정 기능도 제공될 가능성이 있다. 다음은 변경 사항의 예시다.

이전에는 동작했지만 이제는 오류가 발생하는 경우(호환성이 깨지는 변경 사항):

foo/* */?.description
foo/* */!
1/**/+2
1+/**/2

이전에는 오류였지만 이제는 동작하는 경우:

/* */!foo
1/**/+ 2
1 /**/+ 2
1 +/**/2

여전히 오류가 발생하는 경우:

!/* */foo
1+/* */2

여전히 동작하는 경우:

foo!// comment

1 +/**/ 2
1 +/* */2

고려한 대안들

주석을 없는 것처럼 처리하기

주석을 마치 존재하지 않는 것처럼 처리하는 방식을 고려할 수 있다. 즉, 주석을 지나쳐서 그 뒤에 공백이 있는지 확인하는 방식이다. 이 방식은 일부 사람들의 주석에 대한 생각과 더 잘 맞는다. 또한 더 유연한데, 위에서 제안한 규칙에서는 금지된 곳에서도 주석을 허용한다. (예: !/* */foo)

하지만 이 규칙은 설명하기 더 어렵다. 주석이 완전히 무시되지 않고 토큰을 분리하기 때문이다. 또한 언어 참조에서 주석을 공백으로 처리한다는 일반적인 규칙과도 맞지 않는다.

또 다른 단점은 연산자 주변에 공백이 있는지 확인하기 위해 주석의 반대쪽을 확인해야 한다는 점이다. 예를 들어:

a = 1 +/* 매우 긴 주석 */~x

”+” 부근만 보고 이 연산자가 이항 연산자인지 접두사 연산자인지 알 수 없다. (그리고 사실 주석을 단순히 없는 것처럼 처리하면 이 코드는 파싱에 실패할 것이다.)

또 다른 대안은 언어 전체에서 주석을 어떻게 처리할지에 대한 더 일반적인 규칙을 적용하는 것이다(예: 주석을 공백 문자로 바꿔도 아무런 영향을 미치지 않아야 함). 이 방법은 다른 모호함을 해결할 수 있다는 장점이 있지만, 다양한 엣지 케이스에서 광범위한 영향을 미칠 가능성이 있다. 이러한 엣지 케이스는 완전히 열거하기 어렵다(예: 여러 줄 주석, 문자열 리터럴 내부의 보간된 시퀀스 안에 있는 주석, “#” 지시문이 포함된 줄의 주석 등).