diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index 7d3e9c11198..0dbc7780c9e 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -2077,759 +2077,826 @@ impl<'a> TypeChecker<'a> { fn type_check_exp(&mut self, exp: &Expr, env: &mut SymbolEnv) -> (TypeRef, Phase) { CompilationContext::set(CompilationPhase::TypeChecking, &exp.span); - let (mut t, phase) = |exp: &Expr, env: &mut SymbolEnv| -> (TypeRef, Phase) { - match &exp.kind { - ExprKind::Literal(lit) => match lit { - Literal::String(_) => (self.types.string(), Phase::Independent), - Literal::NonInterpolatedString(_) => (self.types.string(), Phase::Independent), - Literal::Nil => (self.types.nil(), Phase::Independent), - Literal::InterpolatedString(s) => { - let mut phase = Phase::Independent; - s.parts.iter().for_each(|part| { - if let InterpolatedStringPart::Expr(interpolated_expr) = part { - let (exp_type, p) = self.type_check_exp(interpolated_expr, env); - phase = combine_phases(phase, p); - self.validate_type_is_stringable(exp_type, interpolated_expr); - } - }); - (self.types.string(), phase) + let (mut t, phase) = match &exp.kind { + ExprKind::Literal(lit) => self.type_check_lit(lit, env), + ExprKind::Binary { op, left, right } => self.type_check_binary_op(left, env, right, op, exp), + ExprKind::Unary { op, exp: unary_exp } => self.type_check_unary_op(unary_exp, env, op), + ExprKind::Range { start, end, .. } => self.type_check_range(start, env, end), + ExprKind::Reference(_ref) => self.type_check_reference(_ref, env), + ExprKind::New(new_expr) => self.type_check_new(new_expr, env, exp), + ExprKind::Call { callee, arg_list } => self.type_check_call(arg_list, env, callee, exp), + ExprKind::ArrayLiteral { type_, items } => self.type_check_array_lit(type_, env, exp, items), + ExprKind::MapLiteral { fields, type_ } => self.type_check_map_lit(type_, env, fields, exp), + ExprKind::SetLiteral { type_, items } => self.type_check_set_lit(type_, env, items, exp), + ExprKind::StructLiteral { type_, fields } => self.type_check_struct_lit(type_, env, fields, exp), + ExprKind::JsonLiteral { is_mut, element } => self.type_check_json_lit(is_mut, element, env, exp), + ExprKind::JsonMapLiteral { fields } => self.type_check_json_map_lit(fields, env, exp), + ExprKind::FunctionClosure(func_def) => self.type_check_closure(func_def, env), + ExprKind::CompilerDebugPanic => { + // Handle the debug panic expression (during type-checking) + dbg_panic!(); + ( + self.type_error(TypeError { + message: "Panic expression".to_string(), + span: exp.span.clone(), + annotations: vec![], + }), + env.phase, + ) + } + }; + + // If we're inflight but the expression is a lifted (preflight) expression then make it immutable + if env.phase == Phase::Inflight && phase == Phase::Preflight { + t = self.make_immutable(t); + } + + self.types.assign_type_to_expr(exp, t, phase); + + // In case any type inferences were updated during this check, ensure all related inferences are updated + self.update_known_inferences(&mut t, &exp.span); + + (t, phase) + } + + fn type_check_lit(&mut self, lit: &Literal, env: &mut SymbolEnv) -> (TypeRef, Phase) { + match lit { + Literal::String(_) => (self.types.string(), Phase::Independent), + Literal::NonInterpolatedString(_) => (self.types.string(), Phase::Independent), + Literal::Nil => (self.types.nil(), Phase::Independent), + Literal::InterpolatedString(s) => { + let mut phase = Phase::Independent; + s.parts.iter().for_each(|part| { + if let InterpolatedStringPart::Expr(interpolated_expr) = part { + let (exp_type, p) = self.type_check_exp(interpolated_expr, env); + phase = combine_phases(phase, p); + self.validate_type_is_stringable(exp_type, interpolated_expr); } - Literal::Number(_) => (self.types.number(), Phase::Independent), - Literal::Boolean(_) => (self.types.bool(), Phase::Independent), - }, - ExprKind::Binary { op, left, right } => { - let (ltype, ltype_phase) = self.type_check_exp(left, env); - let (rtype, rtype_phase) = self.type_check_exp(right, env); - - // Resolve the phase - let phase = combine_phases(ltype_phase, rtype_phase); - - match op { - BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { - self.validate_type(ltype, self.types.bool(), left); - self.validate_type(rtype, self.types.bool(), right); - (self.types.bool(), phase) - } - BinaryOperator::AddOrConcat => { - if ltype.is_subtype_of(&self.types.number()) && rtype.is_subtype_of(&self.types.number()) { - (self.types.number(), phase) - } else if ltype.is_subtype_of(&self.types.string()) && rtype.is_subtype_of(&self.types.string()) { - (self.types.string(), phase) - } else { - // If any of the types are unresolved (error) then don't report this assuming the error has already been reported - if !ltype.is_unresolved() && !rtype.is_unresolved() { - self.spanned_error( - exp, - format!( - "Binary operator '+' cannot be applied to operands of type '{}' and '{}'; only ({}, {}) and ({}, {}) are supported", - ltype, - rtype, - self.types.number(), - self.types.number(), - self.types.string(), - self.types.string(), - ), - ); - } - self.resolved_error() - } - } - BinaryOperator::Sub - | BinaryOperator::Mul - | BinaryOperator::Div - | BinaryOperator::FloorDiv - | BinaryOperator::Mod - | BinaryOperator::Power => { - self.validate_type(ltype, self.types.number(), left); - self.validate_type(rtype, self.types.number(), right); - (self.types.number(), phase) - } - BinaryOperator::Equal | BinaryOperator::NotEqual => { - self.validate_type_binary_equality(rtype, ltype, exp); - (self.types.bool(), phase) - } - BinaryOperator::Less - | BinaryOperator::LessOrEqual - | BinaryOperator::Greater - | BinaryOperator::GreaterOrEqual => { - self.validate_type(ltype, self.types.number(), left); - self.validate_type(rtype, self.types.number(), right); - (self.types.bool(), phase) - } - BinaryOperator::UnwrapOr => { - // Left argument must be an optional type - if !ltype.is_option() { - self.spanned_error(left, format!("Expected optional type, found \"{}\"", ltype)); - (ltype, phase) - } else { - // Right argument must be a subtype of the inner type of the left argument - let inner_type = *ltype.maybe_unwrap_option(); - self.validate_type(rtype, inner_type, right); - (inner_type, phase) - } - } + }); + (self.types.string(), phase) + } + Literal::Number(_) => (self.types.number(), Phase::Independent), + Literal::Boolean(_) => (self.types.bool(), Phase::Independent), + } + } + + fn type_check_binary_op( + &mut self, + left: &Expr, + env: &mut SymbolEnv, + right: &Expr, + op: &BinaryOperator, + exp: &Expr, + ) -> (TypeRef, Phase) { + let (ltype, ltype_phase) = self.type_check_exp(left, env); + let (rtype, rtype_phase) = self.type_check_exp(right, env); + + // Resolve the phase + let phase = combine_phases(ltype_phase, rtype_phase); + + match op { + BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { + self.validate_type(ltype, self.types.bool(), left); + self.validate_type(rtype, self.types.bool(), right); + (self.types.bool(), phase) + } + BinaryOperator::AddOrConcat => { + if ltype.is_subtype_of(&self.types.number()) && rtype.is_subtype_of(&self.types.number()) { + (self.types.number(), phase) + } else if ltype.is_subtype_of(&self.types.string()) && rtype.is_subtype_of(&self.types.string()) { + (self.types.string(), phase) + } else { + // If any of the types are unresolved (error) then don't report this assuming the error has already been reported + if !ltype.is_unresolved() && !rtype.is_unresolved() { + self.spanned_error( + exp, + format!( + "Binary operator '+' cannot be applied to operands of type '{}' and '{}'; only ({}, {}) and ({}, {}) are supported", + ltype, + rtype, + self.types.number(), + self.types.number(), + self.types.string(), + self.types.string(), + ), + ); } + self.resolved_error() } - ExprKind::Unary { op, exp: unary_exp } => { - let (type_, phase) = self.type_check_exp(unary_exp, env); - - match op { - UnaryOperator::Not => (self.validate_type(type_, self.types.bool(), unary_exp), phase), - UnaryOperator::Minus => (self.validate_type(type_, self.types.number(), unary_exp), phase), - UnaryOperator::OptionalTest => { - if !type_.is_option() { - self.spanned_error(unary_exp, format!("Expected optional type, found \"{}\"", type_)); - } - (self.types.bool(), phase) - } - UnaryOperator::OptionalUnwrap => { - if !type_.is_option() { - self.spanned_error(unary_exp, format!("'!' expects an optional type, found \"{}\"", type_)); - (type_, phase) - } else { - let inner_type = *type_.maybe_unwrap_option(); - (inner_type, phase) - } - } - } + } + BinaryOperator::Sub + | BinaryOperator::Mul + | BinaryOperator::Div + | BinaryOperator::FloorDiv + | BinaryOperator::Mod + | BinaryOperator::Power => { + self.validate_type(ltype, self.types.number(), left); + self.validate_type(rtype, self.types.number(), right); + (self.types.number(), phase) + } + BinaryOperator::Equal | BinaryOperator::NotEqual => { + self.validate_type_binary_equality(rtype, ltype, exp); + (self.types.bool(), phase) + } + BinaryOperator::Less | BinaryOperator::LessOrEqual | BinaryOperator::Greater | BinaryOperator::GreaterOrEqual => { + self.validate_type(ltype, self.types.number(), left); + self.validate_type(rtype, self.types.number(), right); + (self.types.bool(), phase) + } + BinaryOperator::UnwrapOr => { + // Left argument must be an optional type + if !ltype.is_option() { + self.spanned_error(left, format!("Expected optional type, found \"{}\"", ltype)); + (ltype, phase) + } else { + // Right argument must be a subtype of the inner type of the left argument + let inner_type = *ltype.maybe_unwrap_option(); + self.validate_type(rtype, inner_type, right); + (inner_type, phase) } - ExprKind::Range { - start, - inclusive: _, - end, - } => { - let (stype, stype_phase) = self.type_check_exp(start, env); - let (etype, _) = self.type_check_exp(end, env); + } + } + } + + fn type_check_unary_op(&mut self, unary_exp: &Expr, env: &mut SymbolEnv, op: &UnaryOperator) -> (TypeRef, Phase) { + let (type_, phase) = self.type_check_exp(unary_exp, env); - self.validate_type(stype, self.types.number(), start); - self.validate_type(etype, self.types.number(), end); - (self.types.add_type(Type::Array(stype)), stype_phase) + match op { + UnaryOperator::Not => (self.validate_type(type_, self.types.bool(), unary_exp), phase), + UnaryOperator::Minus => (self.validate_type(type_, self.types.number(), unary_exp), phase), + UnaryOperator::OptionalTest => { + if !type_.is_option() { + self.spanned_error(unary_exp, format!("Expected optional type, found \"{}\"", type_)); } - ExprKind::Reference(_ref) => { - let (vi, phase) = self.resolve_reference(_ref, env); - let var_type = match vi { - ResolveReferenceResult::Variable(var) => var.type_, - ResolveReferenceResult::Location(_, type_) => type_, - }; - (var_type, phase) + (self.types.bool(), phase) + } + UnaryOperator::OptionalUnwrap => { + if !type_.is_option() { + self.spanned_error(unary_exp, format!("'!' expects an optional type, found \"{}\"", type_)); + (type_, phase) + } else { + let inner_type = *type_.maybe_unwrap_option(); + (inner_type, phase) } - ExprKind::New(new_expr) => { - let New { - class, - obj_id, - arg_list, - obj_scope, - } = new_expr; - // Type check everything - let class_type = self - .resolve_user_defined_type(class, env, self.ctx.current_stmt_idx()) - .unwrap_or_else(|e| self.type_error(e)); - let obj_scope_type = obj_scope.as_ref().map(|x| self.type_check_exp(x, env).0); - let obj_id_type = obj_id.as_ref().map(|x| self.type_check_exp(x, env).0); - let arg_list_types = self.type_check_arg_list(arg_list, env); - - // Lookup the class's type in the env - let (class_env, class_symbol) = match *class_type { - Type::Class(ref class) => { - if class.is_abstract { - self.spanned_error(exp, format!("Cannot instantiate abstract class \"{}\"", class.name)); - } + } + } + } - // error if we are trying to instantiate a preflight in a static method - // without an explicit scope (there is no "this" to use as the scope) - if class.phase == Phase::Preflight && obj_scope.is_none() { - // check if there is a "this" symbol in the current environment - let has_this = env.lookup(&"this".into(), Some(self.ctx.current_stmt_idx())).is_some(); - // if we have a "this", it means we can use it as a default scope, so we are fine - if !has_this { - // we don't have a "this", so we need to check if we are in a static method - // because the entrypoint scope doesn't have a "this" but it is not static - let is_static = self.ctx().current_function().map(|f| f.is_static); - if let Some(true) = is_static { - self.spanned_error( - exp, - format!( - "Cannot instantiate preflight class \"{}\" in a static method without an explicit scope", - class.name - ), - ); - } - } - } + fn type_check_json_map_lit( + &mut self, + fields: &IndexMap, + env: &mut SymbolEnv, + exp: &Expr, + ) -> (TypeRef, Phase) { + let mut known_types = IndexMap::new(); + fields.iter().for_each(|(name, v)| { + let (known_type, _) = self.type_check_exp(v, env); + known_types.insert( + name.clone(), + SpannedTypeInfo { + type_: known_type, + span: v.span(), + }, + ); + // Ensure we don't allow MutJson to Json or vice versa + match *known_type { + Type::Json(_) => { + if self.is_in_mut_json { + self.spanned_error( + v, + "\"MutJson\" fields cannot be \"Json\" (hint: try using Json.deepMutCopy())", + ) + } + } + Type::MutJson => { + if !self.is_in_mut_json { + self.spanned_error( + v, + "\"Json\" fields cannot be \"MutJson\" (hint: try using Json.deepCopy())", + ) + } + } + _ => {} + }; - if class.phase == Phase::Independent || env.phase == class.phase { - (&class.env, &class.name) - } else { - self.spanned_error( - exp, - format!( - "Cannot create {} class \"{}\" in {} phase", - class.phase, class.name, env.phase - ), - ); - return (self.types.error(), Phase::Independent); - } - } - // If type is anything we have to assume it's ok to initialize it - Type::Anything => return (self.types.anything(), Phase::Independent), - // If type is error, we assume the error was already reported and evauate the new expression to error as well - Type::Unresolved => return self.resolved_error(), - Type::Struct(_) => { - self.spanned_error( - class, - format!( - "Cannot instantiate type \"{}\" because it is a struct and not a class. Use struct instantiation instead.", - class_type - ), - ); - return self.resolved_error(); - } - _ => { - self.spanned_error( - class, - format!("Cannot instantiate type \"{}\" because it is not a class", class_type), - ); - return self.resolved_error(); - } - }; + if self.is_in_mut_json && !known_type.is_json_legal_value() { + self.spanned_error( + v, + format!( + "Expected a valid Json value (https://www.json.org/json-en.html), but got \"{}\"", + known_type + ), + ); + } + }); - // Type check args against constructor - let init_method_name = if env.phase == Phase::Preflight || class_env.phase == Phase::Independent { - CLASS_INIT_NAME - } else { - CLASS_INFLIGHT_INIT_NAME - }; + ( + self.types.add_type(Type::Json(Some(JsonData { + expression_id: exp.id, + kind: JsonDataKind::Fields(known_types), + }))), + env.phase, + ) + } - let lookup_res = class_env.lookup_ext(&init_method_name.into(), None); - let constructor_type = match lookup_res { - LookupResult::Found(k, _) => k.as_variable().expect("Expected constructor to be a variable").type_, - LookupResult::NotFound(_, _) => { - self.spanned_error( - exp, - format!("Constructor for class \"{}\" is private", class_symbol.name), - ); - return self.resolved_error(); - } - LookupResult::NotPublic(_, _) - | LookupResult::MultipleFound - | LookupResult::DefinedLater(_) - | LookupResult::ExpectedNamespace(_) => { - self.type_error(lookup_result_to_type_error( - lookup_res, - &Symbol { - name: CLASS_INIT_NAME.into(), - span: class_symbol.span.clone(), - }, - )); - return self.resolved_error(); - } - }; - let constructor_sig = constructor_type - .as_function_sig() - .expect("Expected constructor to be a function signature"); + fn type_check_json_lit( + &mut self, + is_mut: &bool, + element: &Expr, + env: &mut SymbolEnv, + exp: &Expr, + ) -> (TypeRef, Phase) { + if *is_mut { + self.is_in_mut_json = true; + } - // Verify return type (This should never fail since we define the constructors return type during AST building) - self.validate_type(constructor_sig.return_type, class_type, exp); + self.ctx.push_json(); + let (known_type, _) = self.type_check_exp(&element, env); + self.ctx.pop_json(); - self.type_check_arg_list_against_function_sig(&arg_list, &constructor_sig, exp, arg_list_types); + // When we are no longer in a Json literal, we reset the is_in_mut_json flag + if !self.ctx.in_json() { + self.is_in_mut_json = false; + } - let non_std_args = !class_type.as_class().unwrap().std_construct_args; + if *is_mut { + (self.types.mut_json(), env.phase) + } else { + if known_type.is_json() { + (known_type, env.phase) + } else { + ( + self.types.add_type(Type::Json(Some(JsonData { + expression_id: exp.id, + kind: JsonDataKind::Type(SpannedTypeInfo { + type_: known_type, + span: element.span(), + }), + }))), + env.phase, + ) + } + } + } - // If this is a preflight class make sure the object's scope and id are of correct type - if class_type.is_preflight_class() { - // Get reference to resource object's scope - let obj_scope_type = if obj_scope_type.is_none() { - // If this returns None, this means we're instantiating a preflight object in the global scope, which is valid - env - .lookup(&"this".into(), Some(self.ctx.current_stmt_idx())) - .map(|v| v.as_variable().expect("Expected \"this\" to be a variable").type_) - } else { - // If this is a non-standard preflight class, make sure the object's scope isn't explicitly set (using the `in` keywords) - if non_std_args { - self.spanned_error( - obj_scope.as_ref().unwrap(), - format!( - "Cannot set scope of non-standard preflight class \"{}\" using `in`", - class_type - ), - ); - } + fn type_check_struct_lit( + &mut self, + type_: &TypeAnnotation, + env: &mut SymbolEnv, + fields: &IndexMap, + exp: &Expr, + ) -> (TypeRef, Phase) { + // Find this struct's type in the environment + let struct_type = self.resolve_type_annotation(type_, env); + + // Type check each of the struct's fields + let field_types: IndexMap = fields + .iter() + .map(|(name, exp)| { + let (t, _) = self.type_check_exp(exp, env); + (name.clone(), t) + }) + .collect(); - obj_scope_type - }; + // If we don't have type information for the struct we don't need to validate the fields + if struct_type.is_anything() || struct_type.is_unresolved() { + return (struct_type, env.phase); + } - // Verify the object scope is a construct - if let Some(obj_scope_type) = obj_scope_type { - if !obj_scope_type.is_subtype_of(&self.types.construct_interface()) { - self.spanned_error( - exp, - format!( - "Expected scope to be a preflight object, instead found \"{}\"", - obj_scope_type - ), - ); - } - } + // Make sure it really is a struct type + let st = struct_type + .as_struct() + .expect(&format!("Expected \"{}\" to be a struct type", struct_type)); - // Verify the object id is a string - if let Some(obj_id_type) = obj_id_type { - self.validate_type(obj_id_type, self.types.string(), obj_id.as_ref().unwrap()); - // If this is a non-standard preflight class, make sure the object's id isn't explicitly set (using the `as` keywords) - if non_std_args { - self.spanned_error( - obj_id.as_ref().unwrap(), - format!( - "Cannot set id of non-standard preflight class \"{}\" using `as`", - class_type - ), - ); - } - } - } else { - // This is an inflight class, make sure the object scope and id are not set - if let Some(obj_scope) = obj_scope { - self.spanned_error(obj_scope, "Inflight classes cannot have a scope"); - } - if let Some(obj_id) = obj_id { - self.spanned_error(obj_id, "Inflight classes cannot have an id"); - } + // Verify that all expected fields are present and are the right type + for (name, field_type) in st.fields(true) { + match fields.get(name.as_str()) { + Some(field_exp) => { + let t = field_types.get(name.as_str()).unwrap(); + self.validate_type(*t, field_type, field_exp); + } + None => { + if !field_type.is_option() { + self.spanned_error(exp, format!("\"{}\" is not initialized", name)); } - - (class_type, env.phase) } - ExprKind::Call { callee, arg_list } => { - // Type check the call arguments - let arg_list_types = self.type_check_arg_list(arg_list, env); - - // Resolve the function's reference (either a method in the class's env or a function in the current env) - let (func_type, callee_phase) = match callee { - CalleeKind::Expr(expr) => self.type_check_exp(expr, env), - CalleeKind::SuperCall(method) => resolve_super_method(method, env, &self.types).unwrap_or_else(|e| { - self.type_error(e); - self.resolved_error() - }), - }; - let is_option = func_type.is_option(); - let func_type = func_type.maybe_unwrap_option(); + } + } - // If the callee's signature type is unknown, just evaluate the entire call expression as an error - if func_type.is_unresolved() { - return self.resolved_error(); - } + // Verify that no unexpected fields are present + for (name, _t) in field_types.iter() { + if st.env.lookup(name, Some(self.ctx.current_stmt_idx())).is_none() { + self.spanned_error(exp, format!("\"{}\" is not a field of \"{}\"", name.name, st.name.name)); + } + } - // If the caller's signature is `any`, then just evaluate the entire call expression as `any` - if func_type.is_anything() { - return (self.types.anything(), Phase::Independent); - } + (struct_type, env.phase) + } - // Make sure this is a function signature type - let func_sig = if let Some(func_sig) = func_type.as_function_sig() { - func_sig.clone() - } else if let Some(class) = func_type.as_preflight_class() { - // return the signature of the "handle" method - let lookup_res = class.get_method(&CLOSURE_CLASS_HANDLE_METHOD.into()); - let handle_type = if let Some(method) = lookup_res { - method.type_ - } else { - self.spanned_error(callee, "Expected a function or method"); - return self.resolved_error(); - }; - if let Some(sig_type) = handle_type.as_function_sig() { - sig_type.clone() - } else { - self.spanned_error(callee, "Expected a function or method"); - return self.resolved_error(); - } - } else { - self.spanned_error( - callee, - format!("Expected a function or method, found \"{}\"", func_type), - ); - return self.resolved_error(); - }; + fn type_check_set_lit( + &mut self, + type_: &Option, + env: &mut SymbolEnv, + items: &Vec, + exp: &Expr, + ) -> (TypeRef, Phase) { + // Infer type based on either the explicit type or the value in one of the items + let (mut container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Set(t) | Type::MutSet(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Set\" or \"MutSet\", found \"{container_type}\""), + ); + self.types.error() + } + }; + (container_type, element_type) + } else { + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Set(inner_type)), inner_type) + }; - if !env.phase.can_call_to(&func_sig.phase) { - self.spanned_error( - exp, - format!("Cannot call into {} phase while {}", func_sig.phase, env.phase), - ); - } + // Verify all types are the same as the inferred type + for item in items { + let (t, _) = self.type_check_exp(item, env); - // If the function is phase independent, then inherit from the callee - let func_phase = if func_sig.phase == Phase::Independent { - callee_phase - } else { - func_sig.phase - }; + if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { + // this is an set of JSON, change the element type to reflect that + let json_data = JsonData { + expression_id: exp.id, + kind: JsonDataKind::List(vec![]), + }; + element_type = self.types.add_type(Type::Json(Some(json_data))); + } - if let Some(value) = self.type_check_arg_list_against_function_sig(arg_list, &func_sig, exp, arg_list_types) { - return (value, func_phase); - } + // Augment the json list data with the new element type + if let Type::Json(Some(JsonData { ref mut kind, .. })) = &mut *element_type { + if let JsonDataKind::List(ref mut json_list) = kind { + json_list.push(SpannedTypeInfo { + type_: t, + span: item.span(), + }); + } + } - // If the function is "wingc_env", then print out the current environment - if let CalleeKind::Expr(call_expr) = callee { - if let ExprKind::Reference(Reference::Identifier(ident)) = &call_expr.kind { - if ident.name == "wingc_env" { - println!("[symbol environment at {}]", exp.span().file_id); - println!("{}", env.to_string()); - } - } - } + self.validate_type(t, element_type, item); + element_type = self.types.maybe_unwrap_inference(element_type); + } - if is_option { - // When calling a an optional function, the return type is always optional - // To allow this to be both safe and unsurprising, - // the callee must be a reference with an optional accessor - if let CalleeKind::Expr(call_expr) = callee { - if let ExprKind::Reference(Reference::InstanceMember { optional_accessor, .. }) = &call_expr.kind { - if *optional_accessor { - (self.types.make_option(func_sig.return_type), func_phase) - } else { - // No additional error is needed here, since the type checker will already have errored without optional chaining - (self.types.error(), func_phase) - } - } else { - // TODO do we want syntax for this? e.g. `foo?.()` - self.spanned_error(callee, "Cannot call an optional function"); - (self.types.error(), func_phase) - } - } else { - // TODO do we want syntax for this? e.g. `foo?.()` - self.spanned_error(callee, "Cannot call an optional function"); - (self.types.error(), func_phase) - } - } else { - (func_sig.return_type, func_phase) - } + if let Type::Set(ref mut inner) | Type::MutSet(ref mut inner) = &mut *container_type { + *inner = element_type; + } + + (container_type, env.phase) + } + + fn type_check_map_lit( + &mut self, + type_: &Option, + env: &mut SymbolEnv, + fields: &Vec<(Expr, Expr)>, + exp: &Expr, + ) -> (TypeRef, Phase) { + // Infer type based on either the explicit type or the value in one of the fields + let (mut container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Map(t) | Type::MutMap(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Map\" or \"MutMap\", found \"{container_type}\""), + ); + self.types.error() } - ExprKind::ArrayLiteral { type_, items } => { - // Infer type based on either the explicit type or the value in one of the items - let (mut container_type, mut element_type) = if let Some(type_) = type_ { - let container_type = self.resolve_type_annotation(type_, env); - let element_type = match *container_type { - Type::Array(t) | Type::MutArray(t) => t, - _ => { - self.spanned_error( - &type_.span, - format!("Expected \"Array\" or \"MutArray\", found \"{container_type}\""), - ); - self.types.error() - } - }; - (container_type, element_type) - } else if self.ctx.in_json() { - let json_data = JsonData { - expression_id: exp.id, - kind: JsonDataKind::List(vec![]), - }; - let inner_type = self.types.add_type(Type::Json(Some(json_data))); - (self.types.add_type(Type::Array(inner_type)), inner_type) - } else { - let inner_type = self.types.make_inference(); - (self.types.add_type(Type::Array(inner_type)), inner_type) - }; + }; + (container_type, element_type) + } else { + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Map(inner_type)), inner_type) + }; - // Verify all types are the same as the inferred type and find the aggregate phase of all the items - let mut phase = Phase::Independent; - for item in items { - let (t, item_phase) = self.type_check_exp(item, env); - phase = combine_phases(phase, item_phase); - - if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { - // This is an array of JSON, change the element type to reflect that - let json_data = JsonData { - expression_id: exp.id, - kind: JsonDataKind::List(vec![]), - }; - element_type = self.types.add_type(Type::Json(Some(json_data))); - } + // Verify all types are the same as the inferred type and that all keys are of string type + for (key, value) in fields { + let (t, _) = self.type_check_exp(value, env); + if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { + // This is an field of JSON, change the element type to reflect that + let json_data = JsonData { + expression_id: exp.id, + kind: JsonDataKind::Fields(IndexMap::new()), + }; + element_type = self.types.add_type(Type::Json(Some(json_data))); + } - // Augment the json list data with the new element type - if let Type::Json(Some(JsonData { ref mut kind, .. })) = &mut *element_type { - if let JsonDataKind::List(ref mut json_list) = kind { - json_list.push(SpannedTypeInfo { - type_: t, - span: item.span(), - }); - } - } + self.validate_type(t, element_type, value); + element_type = self.types.maybe_unwrap_inference(element_type); - if !self.ctx.in_json() { - // If we're not in a Json literal, validate the type of each element - self.validate_type(t, element_type, item); - element_type = self.types.maybe_unwrap_inference(element_type); - } else if self.is_in_mut_json && !t.is_json_legal_value() { - // if we're in a MutJson literal, we only need to check that each field is legal json - self.spanned_error( - item, - format!("Expected a valid Json value (https://www.json.org/json-en.html), but got \"{t}\""), - ); - } - } + // Verify that the key is a string + let (key_type, _) = self.type_check_exp(key, env); + self.validate_type(key_type, self.types.string(), key); + } - if let Type::Array(ref mut inner) | Type::MutArray(ref mut inner) = &mut *container_type { - *inner = element_type; - } + if let Type::Map(ref mut inner) | Type::MutMap(ref mut inner) = &mut *container_type { + *inner = element_type; + } - (container_type, phase) + (container_type, env.phase) + } + + fn type_check_array_lit( + &mut self, + type_: &Option, + env: &mut SymbolEnv, + exp: &Expr, + items: &Vec, + ) -> (TypeRef, Phase) { + // Infer type based on either the explicit type or the value in one of the items + let (mut container_type, mut element_type) = if let Some(type_) = type_ { + let container_type = self.resolve_type_annotation(type_, env); + let element_type = match *container_type { + Type::Array(t) | Type::MutArray(t) => t, + _ => { + self.spanned_error( + &type_.span, + format!("Expected \"Array\" or \"MutArray\", found \"{container_type}\""), + ); + self.types.error() } - ExprKind::MapLiteral { fields, type_ } => { - // Infer type based on either the explicit type or the value in one of the fields - let (mut container_type, mut element_type) = if let Some(type_) = type_ { - let container_type = self.resolve_type_annotation(type_, env); - let element_type = match *container_type { - Type::Map(t) | Type::MutMap(t) => t, - _ => { - self.spanned_error( - &type_.span, - format!("Expected \"Map\" or \"MutMap\", found \"{container_type}\""), - ); - self.types.error() - } - }; - (container_type, element_type) - } else { - let inner_type = self.types.make_inference(); - (self.types.add_type(Type::Map(inner_type)), inner_type) - }; + }; + (container_type, element_type) + } else if self.ctx.in_json() { + let json_data = JsonData { + expression_id: exp.id, + kind: JsonDataKind::List(vec![]), + }; + let inner_type = self.types.add_type(Type::Json(Some(json_data))); + (self.types.add_type(Type::Array(inner_type)), inner_type) + } else { + let inner_type = self.types.make_inference(); + (self.types.add_type(Type::Array(inner_type)), inner_type) + }; - // Verify all types are the same as the inferred type and that all keys are of string type - for (key, value) in fields { - let (t, _) = self.type_check_exp(value, env); - if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { - // This is an field of JSON, change the element type to reflect that - let json_data = JsonData { - expression_id: exp.id, - kind: JsonDataKind::Fields(IndexMap::new()), - }; - element_type = self.types.add_type(Type::Json(Some(json_data))); - } + // Verify all types are the same as the inferred type and find the aggregate phase of all the items + let mut phase = Phase::Independent; + for item in items { + let (t, item_phase) = self.type_check_exp(item, env); + phase = combine_phases(phase, item_phase); + + if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { + // This is an array of JSON, change the element type to reflect that + let json_data = JsonData { + expression_id: exp.id, + kind: JsonDataKind::List(vec![]), + }; + element_type = self.types.add_type(Type::Json(Some(json_data))); + } - self.validate_type(t, element_type, value); - element_type = self.types.maybe_unwrap_inference(element_type); + // Augment the json list data with the new element type + if let Type::Json(Some(JsonData { ref mut kind, .. })) = &mut *element_type { + if let JsonDataKind::List(ref mut json_list) = kind { + json_list.push(SpannedTypeInfo { + type_: t, + span: item.span(), + }); + } + } - // Verify that the key is a string - let (key_type, _) = self.type_check_exp(key, env); - self.validate_type(key_type, self.types.string(), key); - } + if !self.ctx.in_json() { + // If we're not in a Json literal, validate the type of each element + self.validate_type(t, element_type, item); + element_type = self.types.maybe_unwrap_inference(element_type); + } else if self.is_in_mut_json && !t.is_json_legal_value() { + // if we're in a MutJson literal, we only need to check that each field is legal json + self.spanned_error( + item, + format!("Expected a valid Json value (https://www.json.org/json-en.html), but got \"{t}\""), + ); + } + } - if let Type::Map(ref mut inner) | Type::MutMap(ref mut inner) = &mut *container_type { - *inner = element_type; - } + if let Type::Array(ref mut inner) | Type::MutArray(ref mut inner) = &mut *container_type { + *inner = element_type; + } - (container_type, env.phase) - } - ExprKind::SetLiteral { type_, items } => { - // Infer type based on either the explicit type or the value in one of the items - let (mut container_type, mut element_type) = if let Some(type_) = type_ { - let container_type = self.resolve_type_annotation(type_, env); - let element_type = match *container_type { - Type::Set(t) | Type::MutSet(t) => t, - _ => { - self.spanned_error( - &type_.span, - format!("Expected \"Set\" or \"MutSet\", found \"{container_type}\""), - ); - self.types.error() - } - }; - (container_type, element_type) - } else { - let inner_type = self.types.make_inference(); - (self.types.add_type(Type::Set(inner_type)), inner_type) - }; + (container_type, phase) + } - // Verify all types are the same as the inferred type - for item in items { - let (t, _) = self.type_check_exp(item, env); - - if t.is_json() && !matches!(*element_type, Type::Json(Some(..))) { - // this is an set of JSON, change the element type to reflect that - let json_data = JsonData { - expression_id: exp.id, - kind: JsonDataKind::List(vec![]), - }; - element_type = self.types.add_type(Type::Json(Some(json_data))); - } + fn type_check_call( + &mut self, + arg_list: &ArgList, + env: &mut SymbolEnv, + callee: &CalleeKind, + exp: &Expr, + ) -> (TypeRef, Phase) { + // Type check the call arguments + let arg_list_types = self.type_check_arg_list(arg_list, env); - // Augment the json list data with the new element type - if let Type::Json(Some(JsonData { ref mut kind, .. })) = &mut *element_type { - if let JsonDataKind::List(ref mut json_list) = kind { - json_list.push(SpannedTypeInfo { - type_: t, - span: item.span(), - }); - } - } + // Resolve the function's reference (either a method in the class's env or a function in the current env) + let (func_type, callee_phase) = match callee { + CalleeKind::Expr(expr) => self.type_check_exp(expr, env), + CalleeKind::SuperCall(method) => resolve_super_method(method, env, &self.types).unwrap_or_else(|e| { + self.type_error(e); + self.resolved_error() + }), + }; + let is_option = func_type.is_option(); + let func_type = func_type.maybe_unwrap_option(); - self.validate_type(t, element_type, item); - element_type = self.types.maybe_unwrap_inference(element_type); - } + // If the callee's signature type is unknown, just evaluate the entire call expression as an error + if func_type.is_unresolved() { + return self.resolved_error(); + } - if let Type::Set(ref mut inner) | Type::MutSet(ref mut inner) = &mut *container_type { - *inner = element_type; - } + // If the caller's signature is `any`, then just evaluate the entire call expression as `any` + if func_type.is_anything() { + return (self.types.anything(), Phase::Independent); + } - (container_type, env.phase) - } - ExprKind::StructLiteral { type_, fields } => { - // Find this struct's type in the environment - let struct_type = self.resolve_type_annotation(type_, env); - - // Type check each of the struct's fields - let field_types: IndexMap = fields - .iter() - .map(|(name, exp)| { - let (t, _) = self.type_check_exp(exp, env); - (name.clone(), t) - }) - .collect(); - - // If we don't have type information for the struct we don't need to validate the fields - if struct_type.is_anything() || struct_type.is_unresolved() { - return (struct_type, env.phase); - } + // Make sure this is a function signature type + let func_sig = if let Some(func_sig) = func_type.as_function_sig() { + func_sig.clone() + } else if let Some(class) = func_type.as_preflight_class() { + // return the signature of the "handle" method + let lookup_res = class.get_method(&CLOSURE_CLASS_HANDLE_METHOD.into()); + let handle_type = if let Some(method) = lookup_res { + method.type_ + } else { + self.spanned_error(callee, "Expected a function or method"); + return self.resolved_error(); + }; + if let Some(sig_type) = handle_type.as_function_sig() { + sig_type.clone() + } else { + self.spanned_error(callee, "Expected a function or method"); + return self.resolved_error(); + } + } else { + self.spanned_error( + callee, + format!("Expected a function or method, found \"{}\"", func_type), + ); + return self.resolved_error(); + }; - // Make sure it really is a struct type - let st = struct_type - .as_struct() - .expect(&format!("Expected \"{}\" to be a struct type", struct_type)); - - // Verify that all expected fields are present and are the right type - for (name, field_type) in st.fields(true) { - match fields.get(name.as_str()) { - Some(field_exp) => { - let t = field_types.get(name.as_str()).unwrap(); - self.validate_type(*t, field_type, field_exp); - } - None => { - if !field_type.is_option() { - self.spanned_error(exp, format!("\"{}\" is not initialized", name)); - } - } - } - } + if !env.phase.can_call_to(&func_sig.phase) { + self.spanned_error( + exp, + format!("Cannot call into {} phase while {}", func_sig.phase, env.phase), + ); + } - // Verify that no unexpected fields are present - for (name, _t) in field_types.iter() { - if st.env.lookup(name, Some(self.ctx.current_stmt_idx())).is_none() { - self.spanned_error(exp, format!("\"{}\" is not a field of \"{}\"", name.name, st.name.name)); - } - } + // If the function is phase independent, then inherit from the callee + let func_phase = if func_sig.phase == Phase::Independent { + callee_phase + } else { + func_sig.phase + }; + + if let Some(value) = self.type_check_arg_list_against_function_sig(arg_list, &func_sig, exp, arg_list_types) { + return (value, func_phase); + } - (struct_type, env.phase) + // If the function is "wingc_env", then print out the current environment + if let CalleeKind::Expr(call_expr) = callee { + if let ExprKind::Reference(Reference::Identifier(ident)) = &call_expr.kind { + if ident.name == "wingc_env" { + println!("[symbol environment at {}]", exp.span().file_id); + println!("{}", env.to_string()); } - ExprKind::JsonLiteral { is_mut, element } => { - if *is_mut { - self.is_in_mut_json = true; + } + } + + if is_option { + // When calling a an optional function, the return type is always optional + // To allow this to be both safe and unsurprising, + // the callee must be a reference with an optional accessor + if let CalleeKind::Expr(call_expr) = callee { + if let ExprKind::Reference(Reference::InstanceMember { optional_accessor, .. }) = &call_expr.kind { + if *optional_accessor { + (self.types.make_option(func_sig.return_type), func_phase) + } else { + // No additional error is needed here, since the type checker will already have errored without optional chaining + (self.types.error(), func_phase) } + } else { + // TODO do we want syntax for this? e.g. `foo?.()` + self.spanned_error(callee, "Cannot call an optional function"); + (self.types.error(), func_phase) + } + } else { + // TODO do we want syntax for this? e.g. `foo?.()` + self.spanned_error(callee, "Cannot call an optional function"); + (self.types.error(), func_phase) + } + } else { + (func_sig.return_type, func_phase) + } + } - self.ctx.push_json(); - let (known_type, _) = self.type_check_exp(&element, env); - self.ctx.pop_json(); + fn type_check_range(&mut self, start: &Expr, env: &mut SymbolEnv, end: &Expr) -> (TypeRef, Phase) { + let (stype, stype_phase) = self.type_check_exp(start, env); + let (etype, _) = self.type_check_exp(end, env); - // When we are no longer in a Json literal, we reset the is_in_mut_json flag - if !self.ctx.in_json() { - self.is_in_mut_json = false; - } + self.validate_type(stype, self.types.number(), start); + self.validate_type(etype, self.types.number(), end); + (self.types.add_type(Type::Array(stype)), stype_phase) + } - if *is_mut { - (self.types.mut_json(), env.phase) - } else { - if known_type.is_json() { - (known_type, env.phase) - } else { - ( - self.types.add_type(Type::Json(Some(JsonData { - expression_id: exp.id, - kind: JsonDataKind::Type(SpannedTypeInfo { - type_: known_type, - span: element.span(), - }), - }))), - env.phase, - ) - } - } + fn type_check_new(&mut self, new_expr: &New, env: &mut SymbolEnv, exp: &Expr) -> (TypeRef, Phase) { + let New { + class, + obj_id, + arg_list, + obj_scope, + } = new_expr; + // Type check everything + let class_type = self + .resolve_user_defined_type(class, env, self.ctx.current_stmt_idx()) + .unwrap_or_else(|e| self.type_error(e)); + let obj_scope_type = obj_scope.as_ref().map(|x| self.type_check_exp(x, env).0); + let obj_id_type = obj_id.as_ref().map(|x| self.type_check_exp(x, env).0); + let arg_list_types = self.type_check_arg_list(arg_list, env); + + // Lookup the class's type in the env + let (class_env, class_symbol) = match *class_type { + Type::Class(ref class) => { + if class.is_abstract { + self.spanned_error(exp, format!("Cannot instantiate abstract class \"{}\"", class.name)); } - ExprKind::JsonMapLiteral { fields } => { - let mut known_types = IndexMap::new(); - fields.iter().for_each(|(name, v)| { - let (known_type, _) = self.type_check_exp(v, env); - known_types.insert( - name.clone(), - SpannedTypeInfo { - type_: known_type, - span: v.span(), - }, - ); - // Ensure we don't allow MutJson to Json or vice versa - match *known_type { - Type::Json(_) => { - if self.is_in_mut_json { - self.spanned_error( - v, - "\"MutJson\" fields cannot be \"Json\" (hint: try using Json.deepMutCopy())", - ) - } - } - Type::MutJson => { - if !self.is_in_mut_json { - self.spanned_error( - v, - "\"Json\" fields cannot be \"MutJson\" (hint: try using Json.deepCopy())", - ) - } - } - _ => {} - }; - if self.is_in_mut_json && !known_type.is_json_legal_value() { + // error if we are trying to instantiate a preflight in a static method + // without an explicit scope (there is no "this" to use as the scope) + if class.phase == Phase::Preflight && obj_scope.is_none() { + // check if there is a "this" symbol in the current environment + let has_this = env.lookup(&"this".into(), Some(self.ctx.current_stmt_idx())).is_some(); + // if we have a "this", it means we can use it as a default scope, so we are fine + if !has_this { + // we don't have a "this", so we need to check if we are in a static method + // because the entrypoint scope doesn't have a "this" but it is not static + let is_static = self.ctx().current_function().map(|f| f.is_static); + if let Some(true) = is_static { self.spanned_error( - v, + exp, format!( - "Expected a valid Json value (https://www.json.org/json-en.html), but got \"{}\"", - known_type + "Cannot instantiate preflight class \"{}\" in a static method without an explicit scope", + class.name ), ); } - }); + } + } - ( - self.types.add_type(Type::Json(Some(JsonData { - expression_id: exp.id, - kind: JsonDataKind::Fields(known_types), - }))), - env.phase, - ) + if class.phase == Phase::Independent || env.phase == class.phase { + (&class.env, &class.name) + } else { + self.spanned_error( + exp, + format!( + "Cannot create {} class \"{}\" in {} phase", + class.phase, class.name, env.phase + ), + ); + return (self.types.error(), Phase::Independent); } - ExprKind::FunctionClosure(func_def) => self.type_check_closure(func_def, env), - ExprKind::CompilerDebugPanic => { - // Handle the debug panic expression (during type-checking) - dbg_panic!(); - ( - self.type_error(TypeError { - message: "Panic expression".to_string(), - span: exp.span.clone(), - annotations: vec![], - }), - env.phase, - ) + } + // If type is anything we have to assume it's ok to initialize it + Type::Anything => return (self.types.anything(), Phase::Independent), + // If type is error, we assume the error was already reported and evauate the new expression to error as well + Type::Unresolved => return self.resolved_error(), + Type::Struct(_) => { + self.spanned_error( + class, + format!( + "Cannot instantiate type \"{}\" because it is a struct and not a class. Use struct instantiation instead.", + class_type + ), + ); + return self.resolved_error(); + } + _ => { + self.spanned_error( + class, + format!("Cannot instantiate type \"{}\" because it is not a class", class_type), + ); + return self.resolved_error(); + } + }; + + // Type check args against constructor + let init_method_name = if env.phase == Phase::Preflight || class_env.phase == Phase::Independent { + CLASS_INIT_NAME + } else { + CLASS_INFLIGHT_INIT_NAME + }; + + let lookup_res = class_env.lookup_ext(&init_method_name.into(), None); + let constructor_type = match lookup_res { + LookupResult::Found(k, _) => k.as_variable().expect("Expected constructor to be a variable").type_, + LookupResult::NotFound(_, _) => { + self.spanned_error( + exp, + format!("Constructor for class \"{}\" is private", class_symbol.name), + ); + return self.resolved_error(); + } + LookupResult::NotPublic(_, _) + | LookupResult::MultipleFound + | LookupResult::DefinedLater(_) + | LookupResult::ExpectedNamespace(_) => { + self.type_error(lookup_result_to_type_error( + lookup_res, + &Symbol { + name: CLASS_INIT_NAME.into(), + span: class_symbol.span.clone(), + }, + )); + return self.resolved_error(); + } + }; + let constructor_sig = constructor_type + .as_function_sig() + .expect("Expected constructor to be a function signature"); + + // Verify return type (This should never fail since we define the constructors return type during AST building) + self.validate_type(constructor_sig.return_type, class_type, exp); + + self.type_check_arg_list_against_function_sig(&arg_list, &constructor_sig, exp, arg_list_types); + + let non_std_args = !class_type.as_class().unwrap().std_construct_args; + + // If this is a preflight class make sure the object's scope and id are of correct type + if class_type.is_preflight_class() { + // Get reference to resource object's scope + let obj_scope_type = if obj_scope_type.is_none() { + // If this returns None, this means we're instantiating a preflight object in the global scope, which is valid + env + .lookup(&"this".into(), Some(self.ctx.current_stmt_idx())) + .map(|v| v.as_variable().expect("Expected \"this\" to be a variable").type_) + } else { + // If this is a non-standard preflight class, make sure the object's scope isn't explicitly set (using the `in` keywords) + if non_std_args { + self.spanned_error( + obj_scope.as_ref().unwrap(), + format!( + "Cannot set scope of non-standard preflight class \"{}\" using `in`", + class_type + ), + ); + } + + obj_scope_type + }; + + // Verify the object scope is a construct + if let Some(obj_scope_type) = obj_scope_type { + if !obj_scope_type.is_subtype_of(&self.types.construct_interface()) { + self.spanned_error( + exp, + format!( + "Expected scope to be a preflight object, instead found \"{}\"", + obj_scope_type + ), + ); } } - }(exp, env); - // If we're inflight but the expression is a lifted (preflight) expression then make it immutable - if env.phase == Phase::Inflight && phase == Phase::Preflight { - t = self.make_immutable(t); + // Verify the object id is a string + if let Some(obj_id_type) = obj_id_type { + self.validate_type(obj_id_type, self.types.string(), obj_id.as_ref().unwrap()); + // If this is a non-standard preflight class, make sure the object's id isn't explicitly set (using the `as` keywords) + if non_std_args { + self.spanned_error( + obj_id.as_ref().unwrap(), + format!( + "Cannot set id of non-standard preflight class \"{}\" using `as`", + class_type + ), + ); + } + } + } else { + // This is an inflight class, make sure the object scope and id are not set + if let Some(obj_scope) = obj_scope { + self.spanned_error(obj_scope, "Inflight classes cannot have a scope"); + } + if let Some(obj_id) = obj_id { + self.spanned_error(obj_id, "Inflight classes cannot have an id"); + } } - self.types.assign_type_to_expr(exp, t, phase); - - // In case any type inferences were updated during this check, ensure all related inferences are updated - self.update_known_inferences(&mut t, &exp.span); + (class_type, env.phase) + } - (t, phase) + fn type_check_reference(&mut self, _ref: &Reference, env: &mut SymbolEnv) -> (TypeRef, Phase) { + let (vi, phase) = self.resolve_reference(_ref, env); + let var_type = match vi { + ResolveReferenceResult::Variable(var) => var.type_, + ResolveReferenceResult::Location(_, type_) => type_, + }; + (var_type, phase) } fn resolved_error(&mut self) -> (TypeRef, Phase) {