invalid_case_patterns: Dart 3.0에서 유효하지 않은 case 표현식 사용을 피하세요
invalid_case_patterns: Dart 3.0에서 유효하지 않은 case 표현식 사용을 피하세요
invalid_case_patterns 린트는 Dart 3.0으로 마이그레이션할 때 문제가 될 Switch case 표현식을 알려주는 규칙이에요. 실험적(Experimental) 규칙으로 Dart 3.0에서 도입됐어요. Dart 2.19 이하에서 유효했던 case 표현식 중 일부는 라이브러리가 3.0으로 올라가면 오류가 되거나 의미가 달라져요.
본문
설명
Dart 2.19 이하에서 유효했던 일부 case 표현식은 라이브러리가 3.0으로 업그레이드되면 오류가 되거나 의미가 바뀌어요. 이 린트는 Dart 3.0 마이그레이션을 쉽게 하기 위해 그런 표현식들을 표시해요.
2.19에서 유효했던 switch case 중 일부는 Dart 3.0에서 컴파일 오류가 돼요:
- 집합 리터럴(Set literals)
- 괄호로 감싼 표현식(Parenthesized expressions)
identical()호출- 단항 연산자 표현식
!,-,~(단, 정수 리터럴 앞의-는 유효한 패턴이므로 괜찮아요) - 이항 연산자 표현식
!=,==,&,|,^,~/,>>,>>>,<<,+,-,*,/,%,<,<=,>,>=,?? - 조건 연산자
?: - 문자열에 대한
.length호출 is와is!표현식
그 예들은 이렇습니다:
switch (obj) {
case {1}: // Set literal.
case (1): // Parenthesized expression.
case identical(1, 2): // `identical()` call.
case -pi: // Unary operator.
case 1 + 2: // Binary operator.
case true ? 1 : 2: // Conditional operator.
case 'hi'.length: // .length call.
case i is int: // is expression.
}
2.19에서 유효했던 일부 switch case는 문법적으로도 유효한 패턴이지만, 패턴 매칭 동작이 기존의 상수 동등성(constant equality) 동작과 다를 수 있어요. 그것들은 다음과 같아요.
리스트와 맵 리터럴. 리스트나 맵 리터럴은 case에서 상수로 등장할 수 있어요:
switch (obj) {
case [1, 2]: ...
case {'k': 'v'}: ...
}
현재 case는 오직 들어오는 값이 상수와 같은 정체성(identity)을 가질 때만 매치돼요. 그래서:
test(List<int> list) {
switch (list) {
case [1, 2]: print('Matched'); break;
default: print('Did not match'); break;
}
}
main() {
test(const [1, 2]); // Prints "Matched".
test([1, 2]); // Prints "Did not match".
}
패턴을 쓰면 리스트나 맵 리터럴은 리스트/맵 패턴이 돼요. 그 패턴은 들어오는 객체를 구조분해(destructure)하고 하위 패턴들이 모두 매치되면 매치돼요. 즉 리스트와 맵 패턴은 deep equality에 가까운 방식으로 매치돼요.
Dart 3.0에서는 위 프로그램이 두 번 "Matched"를 출력해요.
상수 생성자 호출. 컬렉션과 비슷하게, case에서 클래스의 상수 인스턴스를 만들 수 있어요:
class Point {
final int x;
final int y;
const Point({this.x, this.y});
}
test(Point p) {
switch (p) {
case Point(x: 1, y: 2): print('Matched'); break;
default: print('Did not match'); break;
}
}
main() {
test(const Point(1, 2)); // Prints "Matched".
test(Point(1, 2)); // Prints "Did not match".
}
컬렉션과 마찬가지로, case는 현재 들어오는 값이 같은 정체성을 가질 때만 매치돼요. 패턴에서는 Point(...) 문법이 객체 패턴(object pattern)이 되어 들어오는 Point를 구조분해하고, x와 y 게터를 호출한 다음 그 결과를 해당 하위 패턴들과 비교해요.
이 예시에서는 "Matched"가 두 번 출력돼요.
객체 패턴은 오직 명명된 필드(named fields)만 지원한다는 점을 기억하세요. 그래서 오늘날 위치 인자(positional arguments)가 있는 case의 상수 생성자는 패턴으로 파싱되면 컴파일 타임 오류가 돼요. 인자가 없는 상수 생성자 호출은 유효한 객체 패턴이며 타입 테스트만 수행해요:
class Thing {
const Thing();
}
test(Thing t) {
switch (t) {
case Thing(): print('Matched'); break;
default: print('Did not match'); break;
}
}
main() {
test(const Thing()); // Prints "Matched".
test(Thing()); // Prints "Did not match".
}
패턴으로 해석하면 이 코드는 "Matched"를 두 번 출력해요.
와일드카드. 오늘날 _라는 이름의 상수를 가질 수 있어요:
test(int n) {
const _ = 3;
switch (n) {
case _: print('Matched'); break;
default: print('Did not match'); break;
}
}
main() {
test(3); // Prints "Matched".
test(5); // Prints "Did not match".
}
패턴에서는 식별자 _가 모든 값을 매치하는 패턴으로 취급되므로, 이 코드는 "Matched"를 두 번 출력해요.
논리 연산자. 논리 연산자 &&와 ||는 유효한 상수 표현식이면서 유효한 패턴이에요. 상수 표현식으로는 단순히 표현식을 불리언으로 평가하고 들어오는 값이 그 불리언 값과 같으면 매치돼요. 그래서:
test(bool b) {
switch (b) {
case true && false: print('Matched'); break;
default: print('Did not match'); break;
}
}
main() {
test(false); // Prints "Matched".
test(true); // Prints "Did not match".
}
Dart 3.0에서는 이것들이 패턴이 돼요. 위 예시는 어떤 불리언 값도 true와 false를 동시에 만족할 수 없으므로 "Did not match"를 두 번 출력해요.
유효하지 않은 case의 대부분은 오늘날의 Dart에서 유효하고 Dart 3.0에서도 같은 의미를 가진 무언가로 기계적으로 바꿀 수 있어요.
괄호로 감싼 표현식: 안쪽 표현식이 Dart 3.0에서 깨지지 않는 것이라면 괄호를 그냥 버리면 돼요.
리스트 리터럴, 맵 리터럴, 집합 리터럴, 상수 생성자 호출: 리터럴이나 호출 앞에 const를 붙여요. 그러면 현재 동작을 유지하는 상수 패턴이 돼요.
나쁜 예:
case [1, 2]:
case {'k': 'v'}:
case {1, 2}:
case Point(1, 2):
좋은 예:
case const [1, 2]:
case const {'k': 'v'}:
case const {1, 2}:
case const Point(1, 2):
와일드카드: 상수 이름을 _에서 다른 이름으로 바꿔요. 이름이 private이므로 다른 코드에 영향을 주지 않고 라이브러리 안에서 로컬로 수정할 수 있어요.
그 외의 것: 다른 모든 유효하지 않은 표현식은 표현식을 새로운 명명된 상수로 끌어올려야(hoist) 해요. 예를 들어 이런 코드가 있다면:
나쁜 예:
switch (n) {
case 1 + 2: ...
}
이렇게 바꾸면 고쳐져요.
좋은 예:
const three = 1 + 2;
switch (n) {
case three: ...
}
활성화(Enable)
invalid_case_patterns 규칙을 활성화하려면 analysis_options.yaml 파일의 linter > rules 아래에 invalid_case_patterns를 추가해요.
linter:
rules:
- invalid_case_patterns
대신 YAML 맵 문법으로 린터 규칙을 설정한다면, linter > rules 아래에 invalid_case_patterns: true를 추가해요.
linter:
rules:
invalid_case_patterns: true