1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use syntax::ast::{Expression, Span};
use syntax::types::Type;
use crate::checker::Checker;
impl Checker<'_, '_> {
/// Validates that a cast from source_ty to target_ty is allowed.
/// Pushes a diagnostic if the cast is invalid.
///
/// Allowed conversions:
/// - Numeric types (int, uint, float families) to any other numeric type,
/// including types with numeric underlying types (e.g., `enum Duration: int64`)
/// - Integer <-> rune
/// - string <-> Slice<byte> / Slice<rune>, including types with byte/rune slice
/// underlying types (e.g., `type Bytes = Slice<byte>`)
///
/// Complex types (complex64, complex128) are explicitly excluded.
pub(crate) fn check_valid_cast(
&mut self,
raw_source_ty: &Type,
raw_target_ty: &Type,
span: Span,
) {
let source_ty = raw_source_ty.resolve();
let target_ty = raw_target_ty.resolve();
if source_ty.is_complex() || target_ty.is_complex() {
self.sink.push(diagnostics::infer::invalid_cast(
raw_source_ty,
raw_target_ty,
span,
));
return;
}
if source_ty.has_underlying_numeric_type() && target_ty.has_underlying_numeric_type() {
return;
}
if (source_ty.is_string() && target_ty.has_byte_or_rune_slice_underlying())
|| (target_ty.is_string() && source_ty.has_byte_or_rune_slice_underlying())
{
return;
}
if source_ty.is_byte_slice() && target_ty.is_byte_slice() {
return;
}
// Concrete type -> interface: allowed if source satisfies the interface.
// Used for explicit coercion before wrapping in generic containers,
// e.g. `Some(my_dog as Animal)` to get `Option<Animal>`.
if let Type::Constructor { id, params, .. } = &target_ty
&& let Some(interface) = self.store.get_interface(id).cloned()
&& self
.satisfies_interface(&source_ty, &interface, params, &span)
.is_ok()
{
return;
}
// Type alias <-> underlying type (e.g., fn as HandlerFunc, HandlerFunc as fn)
if let Some(underlying) = target_ty.get_underlying()
&& source_ty == *underlying
{
return;
}
if let Some(underlying) = source_ty.get_underlying()
&& target_ty == *underlying
{
return;
}
self.sink.push(diagnostics::infer::invalid_cast(
raw_source_ty,
raw_target_ty,
span,
));
}
pub(crate) fn check_redundant_cast(
&mut self,
raw_source_ty: &Type,
raw_target_ty: &Type,
span: Span,
) -> bool {
let source_ty = raw_source_ty.resolve();
if source_ty == raw_target_ty.resolve() {
self.sink
.push(diagnostics::infer::redundant_cast(&source_ty, span));
return true;
}
false
}
/// Checks for redundant casts on literals that would adapt to the target type anyway.
/// For example, `let x: int64 = 100 as int64` is redundant because the literal would adapt.
/// But `let x = 100 as int64` is NOT redundant - without the cast, x would be int.
/// Note: `65 as rune` is NOT redundant - it's a semantic conversion from number to character.
pub(crate) fn check_redundant_literal_cast(
&mut self,
expression: &Expression,
target_ty: &Type,
expected_ty: &Type,
span: Span,
) {
let target_resolved = target_ty.resolve();
let expected_resolved = expected_ty.resolve();
if expected_resolved.is_variable() {
return;
}
if expected_resolved != target_resolved {
return;
}
let inner_expression = unwrap_parens_and_negation(expression);
match inner_expression {
Expression::Literal {
literal: syntax::ast::Literal::Integer { .. },
..
} if target_resolved.is_numeric() && !target_resolved.is_rune() => {
self.sink
.push(diagnostics::infer::redundant_cast(&target_resolved, span));
}
Expression::Literal {
literal: syntax::ast::Literal::Float { .. },
..
} if target_resolved.is_float() => {
self.sink
.push(diagnostics::infer::redundant_cast(&target_resolved, span));
}
_ => {}
}
}
}
fn unwrap_parens_and_negation(expression: &Expression) -> &Expression {
match expression {
Expression::Paren { expression, .. } => unwrap_parens_and_negation(expression),
Expression::Unary {
operator: syntax::ast::UnaryOperator::Negative,
expression,
..
} => unwrap_parens_and_negation(expression),
_ => expression,
}
}