Skip to main content

harn_vm/
typecheck.rs

1//! Runtime type & arity validation, shared between user-defined function
2//! calls and registry-known builtin calls.
3//!
4//! Every call-site validation in the VM funnels through three entry points:
5//!
6//! - [`assert_value_matches_type`] — given a [`VmValue`] and a
7//!   [`TypeExpr`], decide whether the value satisfies the type. The
8//!   single source of runtime truth for `int`/`string`/`list<T>`/...
9//!   compatibility, mirroring the static [`TypeChecker::types_compatible`]
10//!   semantics on values rather than type expressions.
11//! - [`validate_user_call`] — arity check + per-arg declared-type
12//!   assertion for compiled user-defined functions
13//!   ([`crate::chunk::CompiledFunction`]).
14//! - [`validate_builtin_call`] — arity check + per-arg type assertion
15//!   for builtins, driven by the parser's
16//!   [`harn_parser::builtin_signatures`] registry. The runtime never
17//!   re-implements per-builtin validation; the registry is the contract.
18//!
19//! All three return [`crate::value::VmError`] variants
20//! ([`VmError::ArityMismatch`], [`VmError::ArgTypeMismatch`]) on failure
21//! so error UX is uniform. Callers may pass an optional
22//! [`harn_lexer::Span`] when they have a source location for the call
23//! site (e.g. derived from the chunk's PC→span table); when omitted the
24//! error renders without a positional suffix.
25
26use harn_lexer::Span;
27use harn_parser::builtin_signatures::{self, BuiltinSignature, TyExt};
28use harn_parser::typechecker::format_type;
29use harn_parser::TypeExpr;
30
31use crate::chunk::{CompiledFunction, ParamSlot};
32use crate::runtime_guards::RuntimeParamGuard;
33use crate::value::{ArgTypeMismatchError, ArityExpect, ArityMismatchError, VmError, VmValue};
34use crate::vm::CallArgs;
35
36/// Validate that `value` satisfies `expected`. Returns `Ok(())` when the
37/// value is acceptable, otherwise an [`VmError::ArgTypeMismatch`] tagged
38/// with `callee` / `param` / `span` for the caller's diagnostic.
39///
40/// The dispatch table mirrors the static checker's `types_compatible`
41/// rules:
42/// - `Named("any")` and the special generic-parameter sentinel skip
43///   validation (any value passes).
44/// - `Named("number")` accepts `int` or `float`.
45/// - `Optional<T>` / `T | nil` accepts the inner type or `Nil`.
46/// - `list<T>`, `dict<K, V>`, `iter<T>`, `Generator<T>`, `Stream<T>`
47///   check the container; element-level validation is per element when
48///   the value is a literal `VmValue::List` / `VmValue::Dict` whose
49///   contents are cheap to walk. For lazy iterators / streams we skip
50///   element validation (they may be infinite or expensive).
51/// - `Shape{...}` validates field presence and per-field types against
52///   `VmValue::Dict` and `VmValue::StructInstance`.
53/// - `Union(...)` accepts any matching alternative.
54/// - `Intersection(...)` accepts only when *every* alternative matches.
55/// - Literal types (`LitInt`, `LitString`) require value equality with
56///   the literal.
57/// - `Never` always rejects.
58pub fn assert_value_matches_type(
59    value: &VmValue,
60    expected: &TypeExpr,
61    callee: &str,
62    param: &str,
63    span: Option<Span>,
64) -> Result<(), VmError> {
65    assert_value_matches_type_with_generics(value, expected, callee, param, span, &[], &[])
66}
67
68fn assert_value_matches_type_with_generics(
69    value: &VmValue,
70    expected: &TypeExpr,
71    callee: &str,
72    param: &str,
73    span: Option<Span>,
74    type_params: &[String],
75    nominal_type_names: &[String],
76) -> Result<(), VmError> {
77    if matches_type_with_generics(value, expected, type_params, nominal_type_names) {
78        Ok(())
79    } else {
80        Err(VmError::ArgTypeMismatch(Box::new(ArgTypeMismatchError {
81            callee: callee.to_string(),
82            param: param.to_string(),
83            expected: format_type(expected),
84            got: value.type_name(),
85            span,
86        })))
87    }
88}
89
90fn user_param_for_arg(func: &CompiledFunction, index: usize) -> Option<&ParamSlot> {
91    if func.has_rest_param && index >= func.params.len().saturating_sub(1) {
92        func.params.last()
93    } else {
94        func.params.get(index)
95    }
96}
97
98fn builtin_param_for_arg(
99    sig: &BuiltinSignature,
100    index: usize,
101) -> Option<&harn_parser::builtin_signatures::Param> {
102    if sig.has_rest && index >= sig.params.len().saturating_sub(1) {
103        sig.params.last()
104    } else {
105        sig.params.get(index)
106    }
107}
108
109/// Recursive predicate driving [`assert_value_matches_type`]. Kept
110/// internal so the public API only exposes `Result`-returning forms.
111#[cfg(test)]
112fn matches_type(value: &VmValue, expected: &TypeExpr) -> bool {
113    matches_type_with_generics(value, expected, &[], &[])
114}
115
116fn matches_type_with_generics(
117    value: &VmValue,
118    expected: &TypeExpr,
119    type_params: &[String],
120    nominal_type_names: &[String],
121) -> bool {
122    match expected {
123        TypeExpr::Named(name) => match name.as_str() {
124            _ if type_params.iter().any(|param| param == name) => true,
125            "any" | "unknown" => true,
126            "int" => matches!(value, VmValue::Int(_)),
127            "float" => matches!(value, VmValue::Float(_) | VmValue::Int(_)),
128            // `decimal` is strict (no Int/Float coercion) to match its
129            // equality/arithmetic island semantics; convert with `decimal(x)`.
130            "decimal" => matches!(value, VmValue::Decimal(_)),
131            "number" => matches!(value, VmValue::Int(_) | VmValue::Float(_)),
132            "string" => matches!(value, VmValue::String(_)),
133            "bool" => matches!(value, VmValue::Bool(_)),
134            "nil" => matches!(value, VmValue::Nil),
135            "list" => matches!(value, VmValue::List(_)),
136            "dict" => matches!(value, VmValue::Dict(_)),
137            "bytes" => matches!(value, VmValue::Bytes(_)),
138            "duration" => matches!(value, VmValue::Duration(_)),
139            "set" => matches!(value, VmValue::Set(_)),
140            "range" => matches!(value, VmValue::Range(_)),
141            "iter" => matches!(value, VmValue::Iter(_)),
142            "generator" | "Generator" => matches!(value, VmValue::Generator(_)),
143            "stream" | "Stream" => matches!(value, VmValue::Stream(_)),
144            "channel" => matches!(value, VmValue::Channel(_)),
145            "task_handle" => matches!(value, VmValue::TaskHandle(_)),
146            "atomic" => matches!(value, VmValue::Atomic(_)),
147            "rng" => matches!(value, VmValue::Rng(_)),
148            "sync_permit" => matches!(value, VmValue::SyncPermit(_)),
149            "mcp_client" => matches!(value, VmValue::McpClient(_)),
150            "pair" => matches!(value, VmValue::Pair(_)),
151            "enum" => matches!(value, VmValue::EnumVariant(_)),
152            "struct" => matches!(value, VmValue::StructInstance(_)),
153            "closure" => matches!(
154                value,
155                VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
156            ),
157            _ => {
158                if !nominal_type_names.iter().any(|ty| ty == name) {
159                    true
160                } else {
161                    value
162                        .struct_name()
163                        .is_some_and(|struct_name| struct_name == name)
164                        || matches!(value, VmValue::EnumVariant(enum_variant) if enum_variant.has_enum_name(name))
165                }
166            }
167        },
168        TypeExpr::Union(members) => members
169            .iter()
170            .any(|m| matches_type_with_generics(value, m, type_params, nominal_type_names)),
171        TypeExpr::Intersection(members) => members
172            .iter()
173            .all(|m| matches_type_with_generics(value, m, type_params, nominal_type_names)),
174        TypeExpr::List(inner) => match value {
175            VmValue::List(items) => items
176                .iter()
177                .all(|v| matches_type_with_generics(v, inner, type_params, nominal_type_names)),
178            _ => false,
179        },
180        TypeExpr::DictType(_, vt) => match value {
181            VmValue::Dict(map) => map
182                .values()
183                .all(|v| matches_type_with_generics(v, vt, type_params, nominal_type_names)),
184            _ => false,
185        },
186        TypeExpr::Iter(_) | TypeExpr::Generator(_) | TypeExpr::Stream(_) => match value {
187            // Lazy / async sequences: only check the container shape;
188            // element-level validation would force evaluation.
189            VmValue::List(_) | VmValue::Generator(_) | VmValue::Stream(_) => true,
190            _ => false,
191        },
192        // An open shape `{a: T, ...R}` checks its explicit fields exactly like
193        // a closed shape; the row tail just means "and possibly more fields,"
194        // which any dict already satisfies — so no extra runtime constraint.
195        TypeExpr::Shape(fields) | TypeExpr::OpenShape { fields, .. } => match value {
196            VmValue::Dict(map) => fields.iter().all(|f| match map.get(f.name.as_str()) {
197                // Optional fields treat an explicit `nil` value the same as
198                // "field missing" so callers can write `{flag: nil}` to mean
199                // "default" without having to omit the key entirely.
200                Some(VmValue::Nil) if f.optional => true,
201                Some(v) => {
202                    matches_type_with_generics(v, &f.type_expr, type_params, nominal_type_names)
203                }
204                None => f.optional,
205            }),
206            VmValue::StructInstance(_) => {
207                fields.iter().all(|f| match value.struct_field(&f.name) {
208                    Some(VmValue::Nil) if f.optional => true,
209                    Some(v) => {
210                        matches_type_with_generics(v, &f.type_expr, type_params, nominal_type_names)
211                    }
212                    None => f.optional,
213                })
214            }
215            _ => false,
216        },
217        TypeExpr::Applied { name, args } => match (name.as_str(), args.as_slice()) {
218            ("list", [inner]) => matches_type_with_generics(
219                value,
220                &TypeExpr::List(Box::new(inner.clone())),
221                type_params,
222                nominal_type_names,
223            ),
224            ("dict", [k, v]) => matches_type_with_generics(
225                value,
226                &TypeExpr::DictType(Box::new(k.clone()), Box::new(v.clone())),
227                type_params,
228                nominal_type_names,
229            ),
230            ("Option", [inner]) => {
231                matches!(value, VmValue::Nil)
232                    || matches_type_with_generics(value, inner, type_params, nominal_type_names)
233            }
234            // Result<T, E>, custom user-applied generics, Schema<T>, etc.
235            // fall through to permissive — runtime can't determine the
236            // active variant without more semantic knowledge.
237            _ => true,
238        },
239        TypeExpr::FnType { .. } => matches!(
240            value,
241            VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
242        ),
243        TypeExpr::Never => false,
244        TypeExpr::LitString(s) => matches!(value, VmValue::String(rs) if rs.as_str() == s),
245        TypeExpr::LitInt(i) => matches!(value, VmValue::Int(rv) if rv == i),
246        TypeExpr::Owned(inner) => {
247            // `owned<T>` is transparent at runtime — only the wrapped type
248            // shape is checked. Ownership semantics live in lints and the
249            // compiler's auto-drop lowering.
250            matches_type_with_generics(value, inner, type_params, nominal_type_names)
251        }
252    }
253}
254
255/// Validate a user-defined function call: arity (respecting defaults +
256/// rest), then per-parameter declared-type assertion for parameters
257/// that carry a [`TypeExpr`] in their [`crate::chunk::ParamSlot`].
258pub fn validate_user_call(
259    func: &CompiledFunction,
260    args: &[VmValue],
261    span: Option<Span>,
262) -> Result<(), VmError> {
263    validate_user_call_args(func, &CallArgs::Slice(args), span)
264}
265
266pub(crate) fn validate_user_call_args(
267    func: &CompiledFunction,
268    args: &CallArgs<'_>,
269    span: Option<Span>,
270) -> Result<(), VmError> {
271    let required = func.minimum_arg_count();
272    let got = args.len();
273
274    if got < required {
275        let expected = arity_expect_for(func);
276        return Err(VmError::ArityMismatch(Box::new(ArityMismatchError {
277            callee: func.name.clone(),
278            expected,
279            got,
280            span,
281        })));
282    }
283
284    if !func.has_runtime_type_checks {
285        return Ok(());
286    }
287
288    for (i, value) in args.iter().enumerate() {
289        let Some(slot) = user_param_for_arg(func, i) else {
290            continue;
291        };
292        let Some(expected) = &slot.type_expr else {
293            continue;
294        };
295        if let Some(guard) = &slot.runtime_guard {
296            validate_with_runtime_guard(value, guard, func, slot, span)?;
297            continue;
298        }
299        validate_uncached_type_expr(value, expected, func, slot, span)?;
300    }
301
302    Ok(())
303}
304
305fn validate_with_runtime_guard(
306    value: &VmValue,
307    guard: &RuntimeParamGuard,
308    func: &CompiledFunction,
309    slot: &ParamSlot,
310    span: Option<Span>,
311) -> Result<(), VmError> {
312    match guard {
313        RuntimeParamGuard::CanonicalSchema(schema) => {
314            crate::schema::schema_assert_canonical_param(value, &slot.name, schema)
315        }
316        RuntimeParamGuard::InvalidSchema(error) => Err(VmError::TypeError(format!(
317            "parameter '{}': {}",
318            slot.name, error
319        ))),
320        RuntimeParamGuard::TypeExpr(expected) => {
321            validate_type_expr_without_schema(value, expected, func, slot, span)
322        }
323    }
324}
325
326fn validate_uncached_type_expr(
327    value: &VmValue,
328    expected: &TypeExpr,
329    func: &CompiledFunction,
330    slot: &ParamSlot,
331    span: Option<Span>,
332) -> Result<(), VmError> {
333    if matches!(expected, TypeExpr::Named(name) if func.declares_type_param(name)) {
334        return Ok(());
335    }
336    if let Some(schema) = crate::compiler::Compiler::type_expr_to_schema_value(expected) {
337        crate::schema::schema_assert_param(value, &slot.name, &schema)?;
338        return Ok(());
339    }
340    validate_type_expr_without_schema(value, expected, func, slot, span)
341}
342
343fn validate_type_expr_without_schema(
344    value: &VmValue,
345    expected: &TypeExpr,
346    func: &CompiledFunction,
347    slot: &ParamSlot,
348    span: Option<Span>,
349) -> Result<(), VmError> {
350    assert_value_matches_type_with_generics(
351        value,
352        expected,
353        &func.name,
354        &slot.name,
355        span,
356        &func.type_params,
357        &func.nominal_type_names,
358    )
359}
360
361/// Validate a builtin call against the parser's signature registry.
362/// Returns `Ok(())` when the builtin is unknown to the registry — the
363/// alignment guarantee enforced at registration time means unknown
364/// names are necessarily internal/special-purpose builtins
365/// (e.g. compiler-synthesized `__*`) that don't need runtime
366/// validation.
367pub fn validate_builtin_call(
368    name: &str,
369    args: &[VmValue],
370    span: Option<Span>,
371) -> Result<(), VmError> {
372    let Some(sig) = builtin_signatures::lookup(name) else {
373        return Ok(());
374    };
375    validate_against_signature(name, sig, args, span)
376}
377
378/// Shared implementation for [`validate_builtin_call`] (and any future
379/// callers that already have a signature in hand). Public so test
380/// harnesses can drive it directly with synthetic signatures.
381pub fn validate_against_signature(
382    name: &str,
383    sig: &BuiltinSignature,
384    args: &[VmValue],
385    span: Option<Span>,
386) -> Result<(), VmError> {
387    let total = sig.params.len();
388    let required = sig.required_params();
389    let got = args.len();
390
391    let arity_ok = if sig.has_rest {
392        got >= total.saturating_sub(1)
393    } else {
394        got >= required && got <= total
395    };
396
397    if !arity_ok {
398        let expected = if sig.has_rest {
399            ArityExpect::AtLeast(total.saturating_sub(1))
400        } else if required == total {
401            ArityExpect::Exact(total)
402        } else {
403            ArityExpect::Range {
404                min: required,
405                max: total,
406            }
407        };
408        return Err(VmError::ArityMismatch(Box::new(ArityMismatchError {
409            callee: name.to_string(),
410            expected,
411            got,
412            span,
413        })));
414    }
415
416    for (i, value) in args.iter().enumerate() {
417        let Some(param) = builtin_param_for_arg(sig, i) else {
418            continue;
419        };
420        if param.optional && matches!(value, VmValue::Nil) {
421            continue;
422        }
423        // Generic type parameters inside builtin signatures are not
424        // resolvable at the value level — the static checker handles
425        // them. Skip type-param positions at runtime to avoid bogus
426        // mismatches.
427        let expected = param.ty.to_type_expr();
428        if matches!(&expected, TypeExpr::Named(n) if sig.is_type_param(n.as_str())) {
429            continue;
430        }
431        // `any` is always satisfied; format_type would render "any"
432        // and the runtime predicate accepts everything anyway.
433        if param.ty.is_any() {
434            continue;
435        }
436        if matches!(param.ty, harn_parser::builtin_signatures::Ty::SchemaOf(_)) {
437            continue;
438        }
439        assert_value_matches_type(value, &expected, name, param.name, span)?;
440    }
441
442    Ok(())
443}
444
445/// Compute the [`ArityExpect`] to embed in an [`VmError::ArityMismatch`]
446/// for a user-defined function. Respects defaults and rest-param flags
447/// so the message reads naturally.
448fn arity_expect_for(func: &CompiledFunction) -> ArityExpect {
449    ArityExpect::AtLeast(func.minimum_arg_count())
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455    use crate::chunk::Chunk;
456    use std::sync::Arc;
457
458    fn vm_int(n: i64) -> VmValue {
459        VmValue::Int(n)
460    }
461
462    fn vm_string(s: &str) -> VmValue {
463        VmValue::String(arcstr::ArcStr::from(s))
464    }
465
466    fn ty_int() -> TypeExpr {
467        TypeExpr::Named("int".into())
468    }
469
470    fn ty_string() -> TypeExpr {
471        TypeExpr::Named("string".into())
472    }
473
474    fn param_slot(name: &str, type_expr: Option<TypeExpr>) -> ParamSlot {
475        ParamSlot {
476            name: name.to_string(),
477            runtime_guard: type_expr.as_ref().map(RuntimeParamGuard::from_type_expr),
478            type_expr,
479            has_default: false,
480        }
481    }
482
483    fn compiled_function(params: Vec<ParamSlot>) -> CompiledFunction {
484        let has_runtime_type_checks = CompiledFunction::has_runtime_type_checks_for_params(&params);
485        CompiledFunction {
486            name: "f".to_string(),
487            type_params: Vec::new(),
488            nominal_type_names: Vec::new(),
489            params,
490            default_start: None,
491            chunk: Arc::new(Chunk::new()),
492            is_generator: false,
493            is_stream: false,
494            has_rest_param: false,
495            has_runtime_type_checks,
496        }
497    }
498
499    #[test]
500    fn matches_primitive_types() {
501        assert!(matches_type(&vm_int(42), &ty_int()));
502        assert!(!matches_type(&vm_int(42), &ty_string()));
503        assert!(matches_type(&vm_string("x"), &ty_string()));
504        assert!(matches_type(
505            &VmValue::Bool(true),
506            &TypeExpr::Named("bool".into())
507        ));
508        assert!(matches_type(&VmValue::Nil, &TypeExpr::Named("nil".into())));
509    }
510
511    #[test]
512    fn float_accepts_int_promotion() {
513        // Mirrors the static rule: `int` is assignable to `float`.
514        assert!(matches_type(&vm_int(3), &TypeExpr::Named("float".into())));
515        assert!(matches_type(
516            &VmValue::Float(3.0),
517            &TypeExpr::Named("float".into())
518        ));
519    }
520
521    #[test]
522    fn union_accepts_any_member() {
523        let union = TypeExpr::Union(vec![ty_int(), ty_string()]);
524        assert!(matches_type(&vm_int(1), &union));
525        assert!(matches_type(&vm_string("y"), &union));
526        assert!(!matches_type(&VmValue::Bool(true), &union));
527    }
528
529    #[test]
530    fn optional_accepts_nil() {
531        let opt = TypeExpr::Union(vec![ty_string(), TypeExpr::Named("nil".into())]);
532        assert!(matches_type(&VmValue::Nil, &opt));
533        assert!(matches_type(&vm_string("x"), &opt));
534        assert!(!matches_type(&vm_int(1), &opt));
535    }
536
537    #[test]
538    fn list_validates_elements() {
539        let list_int = TypeExpr::List(Box::new(ty_int()));
540        let good = VmValue::List(std::sync::Arc::new(vec![vm_int(1), vm_int(2)]));
541        let bad = VmValue::List(std::sync::Arc::new(vec![vm_int(1), vm_string("x")]));
542        assert!(matches_type(&good, &list_int));
543        assert!(!matches_type(&bad, &list_int));
544    }
545
546    #[test]
547    fn shape_validates_required_fields() {
548        let shape = TypeExpr::Shape(vec![harn_parser::ShapeField {
549            name: "x".into(),
550            type_expr: ty_int(),
551            optional: false,
552        }]);
553        let mut good = std::collections::BTreeMap::new();
554        good.insert("x".to_string(), vm_int(7));
555        assert!(matches_type(&VmValue::dict(good), &shape));
556        assert!(!matches_type(
557            &VmValue::dict_map(Default::default()),
558            &shape
559        ));
560    }
561
562    #[test]
563    fn named_type_matches_user_struct_name() {
564        let custom = TypeExpr::Named("MyStruct".into());
565        assert!(!matches_type_with_generics(
566            &vm_int(1),
567            &custom,
568            &[],
569            &["MyStruct".to_string()]
570        ));
571        assert!(matches_type_with_generics(
572            &VmValue::struct_instance("MyStruct", Default::default()),
573            &custom,
574            &[],
575            &["MyStruct".to_string()]
576        ));
577    }
578
579    #[test]
580    fn lit_int_requires_value_equality() {
581        assert!(matches_type(&vm_int(42), &TypeExpr::LitInt(42)));
582        assert!(!matches_type(&vm_int(7), &TypeExpr::LitInt(42)));
583    }
584
585    #[test]
586    fn assert_value_returns_arg_type_mismatch_on_fail() {
587        let err =
588            assert_value_matches_type(&vm_string("abc"), &ty_int(), "myFn", "n", None).unwrap_err();
589        match err {
590            VmError::ArgTypeMismatch(err) => {
591                assert_eq!(err.callee, "myFn");
592                assert_eq!(err.param, "n");
593                assert_eq!(err.expected, "int");
594                assert_eq!(err.got, "string");
595                assert!(err.span.is_none());
596            }
597            other => panic!("expected ArgTypeMismatch, got {other:?}"),
598        }
599    }
600
601    #[test]
602    fn validate_user_call_skips_param_walk_for_untyped_function() {
603        let func = compiled_function(vec![param_slot("value", None)]);
604
605        validate_user_call(&func, &[vm_string("anything")], None).unwrap();
606
607        let err = validate_user_call(&func, &[], None).unwrap_err();
608        assert!(matches!(err, VmError::ArityMismatch(_)));
609    }
610
611    #[test]
612    fn validate_user_call_checks_typed_function() {
613        let func = compiled_function(vec![param_slot("value", Some(ty_int()))]);
614
615        validate_user_call(&func, &[vm_int(1)], None).unwrap();
616
617        let err = validate_user_call(&func, &[vm_string("bad")], None).unwrap_err();
618        assert!(matches!(err, VmError::Runtime(_) | VmError::TypeError(_)));
619    }
620
621    #[test]
622    fn validate_user_call_uses_cached_runtime_guard_metadata() {
623        let string_schema = VmValue::dict(std::collections::BTreeMap::from([(
624            "type".to_string(),
625            VmValue::String(arcstr::ArcStr::from("string")),
626        )]));
627        let guard = RuntimeParamGuard::CanonicalSchema(
628            crate::schema::canonical_param_schema(&string_schema).unwrap(),
629        );
630        let func = compiled_function(vec![ParamSlot {
631            name: "value".to_string(),
632            type_expr: Some(ty_int()),
633            runtime_guard: Some(guard),
634            has_default: false,
635        }]);
636
637        validate_user_call(&func, &[vm_string("cached")], None).unwrap();
638        validate_user_call(&func, &[vm_string("guard")], None).unwrap();
639
640        let err = validate_user_call(&func, &[vm_int(1)], None).unwrap_err();
641        assert!(matches!(err, VmError::Runtime(_) | VmError::TypeError(_)));
642    }
643}