Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: confusing error message when returning value from void functions #3098

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/tests/invalid/return_types.w
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 1 addition & 1 deletion libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub fn type_check(
jsii_imports: &mut Vec<JsiiImportSpec>,
) {
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
Expand Down
42 changes: 30 additions & 12 deletions libs/wingc/src/type_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Type>(&*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,
Expand Down Expand Up @@ -1825,6 +1825,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
sig.return_type,
false,
true,
func_def.signature.phase,
self.statement_idx,
);
Expand Down Expand Up @@ -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),
Expand All @@ -2187,6 +2188,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand Down Expand Up @@ -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(
Expand All @@ -2237,6 +2239,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand All @@ -2256,6 +2259,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand All @@ -2269,6 +2273,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand All @@ -2280,6 +2285,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand Down Expand Up @@ -2359,6 +2365,7 @@ impl<'a> TypeChecker<'a> {
Some(env.get_ref()),
env.return_type,
false,
false,
env.phase,
stmt.idx,
));
Expand All @@ -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");
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 8 additions & 5 deletions libs/wingc/src/type_check/jsii_importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -311,6 +311,7 @@ impl<'a> JsiiImporter<'a> {
None,
self.wing_types.void(),
false,
false,
Phase::Preflight,
self.jsii_spec.import_statement_idx,
);
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
28 changes: 24 additions & 4 deletions libs/wingc/src/type_check/symbol_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct SymbolEnv {
pub return_type: TypeRef,

pub is_init: bool,
pub is_function: bool,
eladcon marked this conversation as resolved.
Show resolved Hide resolved
pub phase: Phase,
statement_idx: usize,
}
Expand Down Expand Up @@ -155,6 +156,7 @@ impl SymbolEnv {
parent: Option<SymbolEnvRef>,
return_type: TypeRef,
is_init: bool,
is_function: bool,
phase: Phase,
statement_idx: usize,
) -> Self {
Expand All @@ -166,6 +168,7 @@ impl SymbolEnv {
parent,
return_type,
is_init,
is_function,
phase,
statement_idx,
}
Expand Down Expand Up @@ -350,6 +353,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> {
Expand Down Expand Up @@ -419,12 +430,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,
);
Expand Down Expand Up @@ -538,24 +550,32 @@ 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,
);

// 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,
});

Expand Down
Loading
Loading