diff --git a/examples/tests/invalid/return_types.w b/examples/tests/invalid/return_types.w new file mode 100644 index 00000000000..18238901fb2 --- /dev/null +++ b/examples/tests/invalid/return_types.w @@ -0,0 +1,22 @@ + return 9; +// ^^^^^^^^^ Return statement outside of function cannot return a value +if true { + return 9; +// ^^^^^^^^^ Return statement outside of function cannot return a value +} + +inflight (): void => { + return 9; +//^^^^^^^^^ Unexpected return value from void function + if true { + return 9; +// ^^^^^^^^^ Unexpected return value from void function + } +}; + +class C { + func() { + return 9; +// ^^^^^^^^^ Unexpected return value from void function + } +} diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 99941be2f1c..336875cc0cf 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -192,7 +192,7 @@ pub fn type_check( jsii_imports: &mut Vec, ) { assert!(scope.env.borrow().is_none(), "Scope should not have an env yet"); - let env = SymbolEnv::new(None, types.void(), false, Phase::Preflight, 0); + let env = SymbolEnv::new(None, types.void(), false, false, Phase::Preflight, 0); scope.set_env(env); // note: Globals are emitted here and wrapped in "{ ... }" blocks. Wrapping makes these emissions, actual diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index ad4d1e12d9a..f1a51028c26 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -1027,7 +1027,7 @@ impl Types { // TODO: this is hack to create the top-level mapping from lib names to symbols // We construct a void ref by hand since we can't call self.void() while constructing the Types struct let void_ref = UnsafeRef::(&*types[void_idx] as *const Type); - let libraries = SymbolEnv::new(None, void_ref, false, Phase::Preflight, 0); + let libraries = SymbolEnv::new(None, void_ref, false, false, Phase::Preflight, 0); Self { types, @@ -1825,6 +1825,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), sig.return_type, false, + true, func_def.signature.phase, self.statement_idx, ); @@ -2164,7 +2165,7 @@ impl<'a> TypeChecker<'a> { _t => self.types.error(), }; - let mut scope_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, env.phase, stmt.idx); + let mut scope_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, false, env.phase, stmt.idx); match scope_env.define( &iterator, SymbolKind::make_free_variable(iterator.clone(), iterator_type, false, env.phase), @@ -2187,6 +2188,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2215,7 +2217,7 @@ impl<'a> TypeChecker<'a> { // and complete the type checking process for additional errors. let var_type = cond_type.maybe_unwrap_option(); - let mut stmt_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, env.phase, stmt.idx); + let mut stmt_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, false, env.phase, stmt.idx); // Add the variable to if block scope match stmt_env.define( @@ -2237,6 +2239,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2256,6 +2259,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2269,6 +2273,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2280,6 +2285,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2359,6 +2365,7 @@ impl<'a> TypeChecker<'a> { Some(env.get_ref()), env.return_type, false, + false, env.phase, stmt.idx, )); @@ -2369,6 +2376,8 @@ impl<'a> TypeChecker<'a> { let return_type = self.type_check_exp(return_expression, env); if !env.return_type.is_void() { self.validate_type(return_type, env.return_type, return_expression); + } else if env.is_in_function() { + self.spanned_error(stmt, "Unexpected return value from void function"); } else { self.spanned_error(stmt, "Return statement outside of function cannot return a value"); } @@ -2400,7 +2409,7 @@ impl<'a> TypeChecker<'a> { let (parent_class, parent_class_env) = self.extract_parent_class(parent.as_ref(), *phase, name, env, stmt); // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. - let dummy_env = SymbolEnv::new(None, self.types.void(), false, env.phase, stmt.idx); + let dummy_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); let impl_interfaces = implements .iter() @@ -2439,7 +2448,7 @@ impl<'a> TypeChecker<'a> { }; // Create a the real class environment to be filled with the class AST types - let mut class_env = SymbolEnv::new(parent_class_env, self.types.void(), false, env.phase, stmt.idx); + let mut class_env = SymbolEnv::new(parent_class_env, self.types.void(), false, false, env.phase, stmt.idx); // Add fields to the class env for field in fields.iter() { @@ -2590,7 +2599,7 @@ impl<'a> TypeChecker<'a> { } StmtKind::Interface(AstInterface { name, methods, extends }) => { // Create environment representing this interface, for now it'll be empty just so we can support referencing ourselves from the interface definition. - let dummy_env = SymbolEnv::new(None, self.types.void(), false, env.phase, stmt.idx); + let dummy_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); let extend_interfaces = extends .iter() @@ -2626,7 +2635,7 @@ impl<'a> TypeChecker<'a> { }; // Create the real interface environment to be filled with the interface AST types - let mut interface_env = SymbolEnv::new(None, self.types.void(), false, env.phase, stmt.idx); + let mut interface_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); // Add methods to the interface env for (method_name, sig) in methods.iter() { @@ -2664,7 +2673,7 @@ impl<'a> TypeChecker<'a> { // fail type checking. // Create an environment for the struct - let mut struct_env = SymbolEnv::new(None, self.types.void(), false, env.phase, stmt.idx); + let mut struct_env = SymbolEnv::new(None, self.types.void(), false, false, env.phase, stmt.idx); // Add fields to the struct env for field in fields.iter() { @@ -2738,13 +2747,13 @@ impl<'a> TypeChecker<'a> { finally_statements, } => { // Create a new environment for the try block - let try_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, env.phase, stmt.idx); + let try_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, false, env.phase, stmt.idx); try_statements.set_env(try_env); self.inner_scopes.push(try_statements); // Create a new environment for the catch block if let Some(catch_block) = catch_block { - let mut catch_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, env.phase, stmt.idx); + let mut catch_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, false, env.phase, stmt.idx); // Add the exception variable to the catch block if let Some(exception_var) = &catch_block.exception_var { @@ -2765,7 +2774,7 @@ impl<'a> TypeChecker<'a> { // Create a new environment for the finally block if let Some(finally_statements) = finally_statements { - let finally_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, env.phase, stmt.idx); + let finally_env = SymbolEnv::new(Some(env.get_ref()), env.return_type, false, false, env.phase, stmt.idx); finally_statements.set_env(finally_env); self.inner_scopes.push(finally_statements); } @@ -2812,6 +2821,7 @@ impl<'a> TypeChecker<'a> { Some(class_env.get_ref()), self.types.void(), true, + true, class_env.phase, scope.statements[0].idx, ); @@ -2924,6 +2934,7 @@ impl<'a> TypeChecker<'a> { Some(parent_env.get_ref()), method_sig.return_type, is_init, + true, method_sig.phase, statement_idx, ); @@ -3145,7 +3156,14 @@ impl<'a> TypeChecker<'a> { types_map.insert(format!("{o}"), (*o, *n)); } - let new_env = SymbolEnv::new(None, original_type_class.env.return_type, false, Phase::Independent, 0); + let new_env = SymbolEnv::new( + None, + original_type_class.env.return_type, + false, + false, + Phase::Independent, + 0, + ); let tt = Type::Class(Class { name: original_type_class.name.clone(), env: new_env, diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index faad8f34f10..a33fd9fe08d 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -200,7 +200,7 @@ impl<'a> JsiiImporter<'a> { } else { let ns = self.wing_types.add_namespace(Namespace { name: type_name.assembly().to_string(), - env: SymbolEnv::new(None, self.wing_types.void(), false, Phase::Preflight, 0), + env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), loaded: false, }); self @@ -242,7 +242,7 @@ impl<'a> JsiiImporter<'a> { } else { let ns = self.wing_types.add_namespace(Namespace { name: namespace_name.to_string(), - env: SymbolEnv::new(None, self.wing_types.void(), false, Phase::Preflight, 0), + env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), loaded: false, }); parent_ns @@ -311,6 +311,7 @@ impl<'a> JsiiImporter<'a> { None, self.wing_types.void(), false, + false, Phase::Preflight, self.jsii_spec.import_statement_idx, ); @@ -324,6 +325,7 @@ impl<'a> JsiiImporter<'a> { None, self.wing_types.void(), false, + false, iface_env.phase, self.jsii_spec.import_statement_idx, ), // Dummy env, will be replaced below @@ -336,6 +338,7 @@ impl<'a> JsiiImporter<'a> { None, self.wing_types.void(), false, + false, iface_env.phase, self.jsii_spec.import_statement_idx, ), // Dummy env, will be replaced below @@ -609,7 +612,7 @@ impl<'a> JsiiImporter<'a> { }; // Create environment representing this class, for now it'll be empty just so we can support referencing ourselves from the class definition. - let dummy_env = SymbolEnv::new(None, self.wing_types.void(), false, class_phase, 0); + let dummy_env = SymbolEnv::new(None, self.wing_types.void(), false, false, class_phase, 0); let new_type_symbol = Self::jsii_name_to_symbol(type_name, &jsii_class.location_in_module); // Create the new resource/class type and add it to the current environment. // When adding the class methods below we'll be able to reference this type. @@ -661,7 +664,7 @@ impl<'a> JsiiImporter<'a> { self.register_jsii_type(&jsii_class_fqn, &new_type_symbol, new_type); // Create class's actual environment before we add properties and methods to it - let mut class_env = SymbolEnv::new(base_class_env, self.wing_types.void(), false, class_phase, 0); + let mut class_env = SymbolEnv::new(base_class_env, self.wing_types.void(), false, false, class_phase, 0); // Add constructor to the class environment let jsii_initializer = jsii_class.initializer.as_ref(); @@ -894,7 +897,7 @@ impl<'a> JsiiImporter<'a> { { let ns = self.wing_types.add_namespace(Namespace { name: assembly.name.clone(), - env: SymbolEnv::new(None, self.wing_types.void(), false, Phase::Preflight, 0), + env: SymbolEnv::new(None, self.wing_types.void(), false, false, Phase::Preflight, 0), loaded: false, }); self diff --git a/libs/wingc/src/type_check/symbol_env.rs b/libs/wingc/src/type_check/symbol_env.rs index 5916b38f584..6ddf6c57791 100644 --- a/libs/wingc/src/type_check/symbol_env.rs +++ b/libs/wingc/src/type_check/symbol_env.rs @@ -27,6 +27,8 @@ pub struct SymbolEnv { pub return_type: TypeRef, pub is_init: bool, + // Whether this scope is inside of a function + pub is_function: bool, pub phase: Phase, statement_idx: usize, } @@ -155,6 +157,7 @@ impl SymbolEnv { parent: Option, return_type: TypeRef, is_init: bool, + is_function: bool, phase: Phase, statement_idx: usize, ) -> Self { @@ -166,6 +169,7 @@ impl SymbolEnv { parent, return_type, is_init, + is_function, phase, statement_idx, } @@ -350,6 +354,14 @@ impl SymbolEnv { pub fn iter(&self, with_ancestry: bool) -> SymbolEnvIter { SymbolEnvIter::new(self, with_ancestry) } + + pub fn is_in_function(&self) -> bool { + let mut curr_env = self.get_ref(); + while !curr_env.is_function && !curr_env.is_root() { + curr_env = curr_env.parent.unwrap(); + } + curr_env.is_function + } } pub struct SymbolEnvIter<'a> { @@ -419,12 +431,13 @@ mod tests { #[test] fn test_statement_idx_lookups() { let types = setup_types(); - let mut parent_env = SymbolEnv::new(None, types.void(), false, Phase::Independent, 0); + let mut parent_env = SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0); let child_scope_idx = 10; let mut child_env = SymbolEnv::new( Some(parent_env.get_ref()), types.void(), false, + false, crate::ast::Phase::Independent, child_scope_idx, ); @@ -538,11 +551,12 @@ mod tests { #[test] fn test_nested_lookups() { let mut types = setup_types(); - let mut parent_env = SymbolEnv::new(None, types.void(), false, Phase::Independent, 0); + let mut parent_env = SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0); let child_env = SymbolEnv::new( Some(parent_env.get_ref()), types.void(), false, + false, crate::ast::Phase::Independent, 0, ); @@ -550,12 +564,19 @@ mod tests { // Create namespaces let ns1 = types.add_namespace(Namespace { name: "ns1".to_string(), - env: SymbolEnv::new(None, types.void(), false, Phase::Independent, 0), + env: SymbolEnv::new(None, types.void(), false, false, Phase::Independent, 0), loaded: false, }); let ns2 = types.add_namespace(Namespace { name: "ns2".to_string(), - env: SymbolEnv::new(Some(ns1.env.get_ref()), types.void(), false, Phase::Independent, 0), + env: SymbolEnv::new( + Some(ns1.env.get_ref()), + types.void(), + false, + false, + Phase::Independent, + 0, + ), loaded: false, }); diff --git a/tools/hangar/__snapshots__/invalid.ts.snap b/tools/hangar/__snapshots__/invalid.ts.snap index 0a5d6c66038..4d2cec0b364 100644 --- a/tools/hangar/__snapshots__/invalid.ts.snap +++ b/tools/hangar/__snapshots__/invalid.ts.snap @@ -1445,6 +1445,49 @@ error: Inflight initializers cannot have parameters +Tests 1 failed (1) +Test Files 1 failed (1) +Duration " +`; + +exports[`return_types.w 1`] = ` +"error: Return statement outside of function cannot return a value + --> ../../../examples/tests/invalid/return_types.w:1:5 + | +1 | return 9; + | ^^^^^^^^^ Return statement outside of function cannot return a value + + +error: Return statement outside of function cannot return a value + --> ../../../examples/tests/invalid/return_types.w:4:5 + | +4 | return 9; + | ^^^^^^^^^ Return statement outside of function cannot return a value + + +error: Unexpected return value from void function + --> ../../../examples/tests/invalid/return_types.w:9:3 + | +9 | return 9; + | ^^^^^^^^^ Unexpected return value from void function + + +error: Unexpected return value from void function + --> ../../../examples/tests/invalid/return_types.w:12:5 + | +12 | return 9; + | ^^^^^^^^^ Unexpected return value from void function + + +error: Unexpected return value from void function + --> ../../../examples/tests/invalid/return_types.w:19:5 + | +19 | return 9; + | ^^^^^^^^^ Unexpected return value from void function + + + + Tests 1 failed (1) Test Files 1 failed (1) Duration "