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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! The type checker: each expression's type found, and what doesn't fit
//! reported — a value of one type where another is wanted, `null` where it
//! can't be, a member a type doesn't have, a call that doesn't match, a
//! game class of the other side. The game's types come from the class
//! index of a targeted version (C#'s names over Java's); what neither the
//! source nor the index knows (the library's) is not checked yet.
mod arrays;
mod attributes;
mod awaits;
mod betterness;
mod bits;
mod builtin;
mod callees;
mod calls;
mod collections;
pub mod commands;
mod constraints;
mod content;
mod content_ids;
mod context;
mod copy;
mod ctors;
mod decls;
mod delegates;
mod exprs;
mod game;
mod generics;
mod groups;
mod indexers;
mod inherit;
mod iterators;
mod list_patterns;
mod lambdas;
mod lineage;
mod macro_calls;
mod members;
mod messages;
mod model;
mod mutation;
mod names;
mod nulls;
mod operators;
mod overloads;
mod own;
mod passing;
mod pick;
mod shapes;
mod required;
mod saved;
mod stmts;
mod templates;
pub(crate) mod surface;
mod threads;
mod tuples;
mod types;
mod variance;
mod variants;
mod written;
use std::collections::HashMap;
pub use content::content_path;
pub use iterators::{body_yields, item_of};
pub use model::Type;
pub use nulls::leaves;
pub use list_patterns::list_item;
pub use templates::{MessageTemplate, holes};
pub use types::{Message, MethodUse, OperatorUse, Quoted, Types};
use crate::ast::File;
use crate::index::{ClassIndex, Lookup, Side};
use own::OwnTypes;
use yog_analysis::Report;
/// A local or a parameter: its type, and whether it's read-only.
struct Slot {
ty: Type,
fixed: bool,
}
pub(crate) struct Checker<'r, 's, 'i> {
report: &'r mut Report<'s>,
lookup: Option<Lookup<'i>>,
own: OwnTypes,
/// Locals and parameters, innermost scope last.
scopes: Vec<HashMap<String, Slot>>,
/// The code is a `readonly` member's: `this` is lent read-only.
/// The members being checked are a content declaration's: its
/// properties its kind's (`content.rs`).
in_content: bool,
readonly_this: bool,
/// The type whose members the code is in (`this`).
this: Option<Type>,
/// The side the code runs on.
side: Side,
/// What a `return` in the code must give (`None`: not in a method).
returns: Option<Type>,
/// An iterator's item type, while its body is checked (`yield`).
yields: Option<Type>,
/// What the `switch` statements around the code match, innermost last.
switched: Vec<Type>,
/// The type being checked owns state: its members may be `saved` (`saved.rs`).
state_owner: bool,
/// While a lambda's result is inferred: what its `return`s give.
inferred: Option<Vec<Type>>,
/// The file's `using` directives.
directives: names::Directives,
/// The namespace the code is in.
ns: Vec<String>,
/// A `.yns`: the game's classes reachable.
nanites: bool,
/// Its mod says it sends to the server.
to_server: bool,
/// The mod's content files, each (kind, id); `None`: not known.
content: Option<Vec<(String, String)>>,
/// The type parameters in scope, innermost last.
type_params: Vec<generics::Bound>,
/// Each expression's type found.
types: Types,
/// Each own type's bases, typed.
bases: lineage::Bases,
/// Each declared delegate's signature, typed.
delegates: delegates::Delegates,
/// Each type's operators, typed.
operators: operators::Operators,
/// The lambdas being checked, innermost last: each by its expression,
/// and the scopes outside it (a local of one of those it captures).
lambdas: Vec<(usize, usize)>,
/// The `unsafe` blocks the code is in: the threads checks skipped.
unsafe_code: usize,
/// The type parameters' `where` constraints in scope, innermost last.
constraints: Vec<constraints::Constraints>,
/// Constraints are being checked (a bound's own type isn't, again).
meeting: bool,
/// The names known not null (`nulls.rs`), each with how many scopes
/// deep it was proven: it holds for locals of the scopes outside that.
non_null: Vec<(usize, String)>,
/// The local functions in reach, each with how many scopes deep its
/// block is (`stmts.rs`).
local_fns: Vec<(usize, crate::ast::Method)>,
/// An index's arguments are being checked: `^n` counts from the end.
indexing: bool,
}
/// What a source may reach: the game's classes (a `.yns`), sending to the
/// server (its mod says so).
pub(crate) struct Allowed {
pub nanites: bool,
pub to_server: bool,
/// The mod's content files, each (kind, id), when known: what its
/// content declarations bind to.
pub content: Option<Vec<(String, String)>>,
}
/// The built-in type its keyword names (`i32`, `string`): the one table
/// of them, for whatever lowers types too.
pub fn builtin_type(word: &str) -> Option<Type> {
builtin::by_word(word)
}
/// The built-in type `System`'s type `name` is (`Int32` is `i32`, `Void`
/// `void`): the same table read the other way.
pub fn system_type(name: &str) -> Option<Type> {
builtin::by_system_name(name)
.or_else(|| content_ids::content_id_kind(name).map(|k| Type::Content(k.to_string())))
}
/// A built-in type's type in `System` (`string`'s `String`), which a
/// `[Host]` declaration gives its members.
pub fn system_name(t: &Type) -> Option<&'static str> {
builtin::system_name(t)
}
/// `file`'s types checked against `index` (a targeted version's) and its
/// project's `others`, what doesn't fit reported.
pub(crate) fn check<'d>(
file: &'d File,
allowed: Allowed,
(index, others): (Option<&ClassIndex>, &[&'d [crate::ast::Decl]]),
report: &mut Report<'_>,
) -> Types {
let mut own = OwnTypes::collect_project(&crate::system::DECLS, others, &file.decls);
for decls in others {
own.extensions(decls);
}
own.extensions(&file.decls);
let mut c = Checker {
report,
lookup: index.map(ClassIndex::lookup),
own,
scopes: vec![HashMap::new()],
this: None,
side: Side::Shared,
returns: None,
yields: None,
switched: Vec::new(),
state_owner: false,
inferred: None,
directives: names::Directives::collect(&file.decls),
ns: Vec::new(),
nanites: allowed.nanites,
to_server: allowed.to_server,
content: allowed.content,
type_params: Vec::new(),
readonly_this: false,
in_content: false,
types: Types::default(),
bases: lineage::Bases::new(),
delegates: delegates::Delegates::new(),
operators: operators::Operators::new(),
lambdas: Vec::new(),
unsafe_code: 0,
constraints: Vec::new(),
meeting: false,
non_null: Vec::new(),
local_fns: Vec::new(),
indexing: false,
};
c.type_bases();
c.type_delegates();
c.type_operators();
c.decls(&file.decls);
inherit::check((&crate::system::DECLS, others), &file.decls, c.report);
c.types
}
impl Checker<'_, '_, '_> {
/// Runs `f` in a scope of its own.
fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
self.scopes.push(HashMap::new());
let out = f(self);
self.scopes.pop();
self.scopes_left(self.scopes.len());
let depth = self.scopes.len();
self.local_fns.retain(|(d, _)| *d <= depth);
out
}
fn bind(&mut self, name: &str, t: Type) {
self.bind_as(name, t, false);
}
/// `name` bound to a `t`; `fixed`: what it holds is read-only here (a
/// `let`, an `in` parameter, a `foreach` item, a pattern's name).
fn bind_as(&mut self, name: &str, ty: Type, fixed: bool) {
// A new local of the name: nothing proven of an older one holds.
self.non_null.retain(|(_, n)| n != name);
if let Some(s) = self.scopes.last_mut() {
s.insert(name.to_string(), Slot { ty, fixed });
}
}
fn local(&self, name: &str) -> Option<&Type> {
self.slot(name).map(|s| &s.ty)
}
fn slot(&self, name: &str) -> Option<&Slot> {
self.scopes.iter().rev().find_map(|s| s.get(name))
}
/// Whether a `got` goes where `wanted` is: as it is, or through an
/// implicit conversion one of them declares.
fn converts(&self, got: &Type, wanted: &Type) -> bool {
self.converts_plainly(got, wanted) || self.implicit(got, wanted).is_some()
}
/// Whether a `got` goes where `wanted` is as it is: a built-in
/// conversion, or an own type that is the one wanted (inherits or
/// implements it).
fn converts_plainly(&self, got: &Type, wanted: &Type) -> bool {
if got.converts_to(wanted) {
return true;
}
if got.is_nullable() && !wanted.is_nullable() {
return false;
}
match (got.non_null(), wanted.non_null()) {
// An own type, or one it inherits, given the arguments wanted.
(Type::Own(a, xs), Type::Own(b, ys)) => self.own_converts(a, xs, (b, ys), true),
(Type::Array(..), wanted) => self.array_converts(got.non_null(), wanted),
// An `ItemId` is a `ContentId<T>` (generic code's), a tag a `TagId<T>` too.
(got @ (Type::Content(_) | Type::Tag(_)), wanted) => content_ids::id_converts(got, wanted),
(Type::Tuple(got, _), Type::Tuple(wanted, _)) => self.tuple_converts(got, wanted),
// A lambda or a method where a delegate of its shape is wanted.
(Type::Fn(..), Type::Own(..)) => {
match (self.delegate_sig(got), self.delegate_sig(wanted)) {
(Some((gp, gr)), Some((wp, wr))) => {
gp.len() == wp.len()
&& wp.iter().zip(&gp).all(|(w, g)| self.converts(w, g))
&& self.converts(&gr, &wr)
}
_ => false,
}
}
_ => false,
}
}
/// `base`: `this` as the class it inherits.
fn base(&self) -> Type {
match &self.this {
Some(Type::Own(name, _)) => self
.own
.base_class(name)
.map_or(Type::Unknown, |b| Type::own(b.to_string())),
_ => Type::Unknown,
}
}
/// Runs `f` with the code's `this`, side and expected result set.
fn within<R>(
&mut self,
this: Option<Type>,
side: Side,
returns: Option<Type>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let saved = (
std::mem::replace(&mut self.this, this),
self.side,
std::mem::replace(&mut self.returns, returns),
);
self.side = side;
let out = self.scoped(f);
(self.this, self.side, self.returns) = saved;
out
}
}