Skip to content

Commit 4d5df24

Browse files
authored
Fix ICE in relation to local declarations with erroneous initializers (#5631)
## Description Fixes #5616 and #5622. When generating the IR for constant and variable declarations the name being declared is not added to the local environment if the initializer cannot be compiled. This can happen when initializing a constant using a non-constant expression. Since the name isn't added to the environment, and since we attempt to continue compilation after an error, any attempt to use the name later in the code will cause the name to not be found, which in turn causes the IR converter to throw an ICE because it assumes that all used names are in scope. This PR fixes the issue by not throwing the initializer error until the name has been added to the environment. Note that 1. The initializer must be compiled before the name is added to the environment. The name should not be in scope during its own initialization. 2. #5602 is blocking a more elegant solution to this problem. The lack of a `Never` type means that the call to `convert_resolved_typeid` may fail if the initializer diverges, and since that call must be made before the new name can be added to the environment we check for termination first. The order of operations is therefore currently a) compile the initializer, then b) check that the initializer did not result in an error and did not diverge, then c) resolve the type of the name being declared, then d) add the name to the environment, and finally e) throw an error if the initializer resulted in an error. Once the `Never` type is introduced we can move the terminator check to the end of that sequence. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers.
1 parent 41a166c commit 4d5df24

File tree

20 files changed

+265
-230
lines changed

20 files changed

+265
-230
lines changed

sway-core/src/ir_generation/function.rs

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,10 +2164,11 @@ impl<'eng> FnCompiler<'eng> {
21642164

21652165
// We must compile the RHS before checking for shadowing, as it will still be in the
21662166
// previous scope.
2167-
let init_val = self.compile_expression_to_value(context, md_mgr, body)?;
2168-
if init_val.is_terminator {
2169-
return Ok(Some(init_val));
2170-
}
2167+
// Corner case: If compilation of the expression fails, then this call returns an error.
2168+
// However, the declared name must be added to the local environment before the error is
2169+
// thrown - otherwise we will get an internal compiler error later on when the name is
2170+
// accessed and isn't present in the environment.
2171+
let init_val = self.compile_expression_to_value(context, md_mgr, body);
21712172

21722173
let return_type = convert_resolved_typeid(
21732174
self.engines.te(),
@@ -2184,6 +2185,13 @@ impl<'eng> FnCompiler<'eng> {
21842185
.new_local_var(context, local_name.clone(), return_type, None, mutable)
21852186
.map_err(|ir_error| CompileError::InternalOwned(ir_error.to_string(), Span::dummy()))?;
21862187

2188+
// The name has now been added, so we can check if the initializer threw an error
2189+
let val = init_val?;
2190+
2191+
if val.is_terminator {
2192+
return Ok(Some(val));
2193+
};
2194+
21872195
// We can have empty aggregates, especially arrays, which shouldn't be initialized, but
21882196
// otherwise use a store.
21892197
let var_ty = local_var.get_type(context);
@@ -2195,7 +2203,7 @@ impl<'eng> FnCompiler<'eng> {
21952203
.add_metadatum(context, span_md_idx);
21962204
self.current_block
21972205
.append(context)
2198-
.store(local_ptr, init_val.value)
2206+
.store(local_ptr, val.value)
21992207
.add_metadatum(context, span_md_idx);
22002208
}
22012209
Ok(None)
@@ -2207,7 +2215,7 @@ impl<'eng> FnCompiler<'eng> {
22072215
md_mgr: &mut MetadataManager,
22082216
ast_const_decl: &ty::TyConstantDecl,
22092217
span_md_idx: Option<MetadataIndex>,
2210-
is_const_expression: bool,
2218+
is_expression: bool,
22112219
) -> Result<TerminatorValue, CompileError> {
22122220
// This is local to the function, so we add it to the locals, rather than the module
22132221
// globals like other const decls.
@@ -2219,6 +2227,11 @@ impl<'eng> FnCompiler<'eng> {
22192227
..
22202228
} = ast_const_decl;
22212229
if let Some(value) = value {
2230+
// Corner case: If compilation of the expression fails (e.g., because it is not
2231+
// constant), then this call returns an error.
2232+
// However, if is_expression = false then the declared name must be added to the local
2233+
// environment before the error is thrown - otherwise we will get an internal compiler
2234+
// error later on when the name is accessed and isn't present in the environment.
22222235
let const_expr_val = compile_constant_expression(
22232236
self.engines,
22242237
context,
@@ -2229,11 +2242,13 @@ impl<'eng> FnCompiler<'eng> {
22292242
call_path,
22302243
value,
22312244
*is_configurable,
2232-
)?;
2245+
);
22332246

2234-
if is_const_expression {
2235-
Ok(TerminatorValue::new(const_expr_val, context))
2247+
if is_expression {
2248+
// No declaration. Throw any error, and return on success.
2249+
Ok(TerminatorValue::new(const_expr_val?, context))
22362250
} else {
2251+
// Declaration. The name needs to be added to the local environment
22372252
let local_name = self
22382253
.lexical_map
22392254
.insert(call_path.suffix.as_str().to_owned());
@@ -2258,6 +2273,13 @@ impl<'eng> FnCompiler<'eng> {
22582273
CompileError::InternalOwned(ir_error.to_string(), Span::dummy())
22592274
})?;
22602275

2276+
// The name has now been added, so we can check if the initializer threw an error
2277+
let val = const_expr_val?;
2278+
2279+
if val.is_terminator(context) {
2280+
return Ok(TerminatorValue::new(val, context));
2281+
};
2282+
22612283
// We can have empty aggregates, especially arrays, which shouldn't be initialised, but
22622284
// otherwise use a store.
22632285
let var_ty = local_var.get_type(context);
@@ -2270,11 +2292,11 @@ impl<'eng> FnCompiler<'eng> {
22702292
let val = self
22712293
.current_block
22722294
.append(context)
2273-
.store(local_val, const_expr_val)
2295+
.store(local_val, val)
22742296
.add_metadatum(context, span_md_idx);
22752297
TerminatorValue::new(val, context)
22762298
} else {
2277-
TerminatorValue::new(const_expr_val, context)
2299+
TerminatorValue::new(val, context)
22782300
})
22792301
}
22802302
} else {
@@ -2411,18 +2433,6 @@ impl<'eng> FnCompiler<'eng> {
24112433
contents: &[ty::TyExpression],
24122434
span_md_idx: Option<MetadataIndex>,
24132435
) -> Result<TerminatorValue, CompileError> {
2414-
// If the first element diverges, then the element type has not been determined,
2415-
// so we can't use the normal compilation scheme. Instead just generate code for
2416-
// the first element and return.
2417-
let first_elem_value = if !contents.is_empty() {
2418-
let first_elem_value = return_on_termination_or_extract!(
2419-
self.compile_expression_to_value(context, md_mgr, &contents[0])?
2420-
);
2421-
Some(first_elem_value)
2422-
} else {
2423-
None
2424-
};
2425-
24262436
let elem_type = convert_resolved_typeid_no_span(
24272437
self.engines.te(),
24282438
self.engines.de(),
@@ -2444,14 +2454,6 @@ impl<'eng> FnCompiler<'eng> {
24442454
.get_local(array_var)
24452455
.add_metadatum(context, span_md_idx);
24462456

2447-
// Nothing more to do if the array is empty
2448-
if contents.is_empty() {
2449-
return Ok(TerminatorValue::new(array_value, context));
2450-
}
2451-
2452-
// The array is not empty, so it's safe to unwrap the first element
2453-
let first_elem_value = first_elem_value.unwrap();
2454-
24552457
// If all elements are the same constant, then we can initialize the array
24562458
// in a loop, reducing code size. But to check for that we've to compile
24572459
// the expressions first, to compare. If it turns out that they're not all
@@ -2468,15 +2470,10 @@ impl<'eng> FnCompiler<'eng> {
24682470
// We can compile all elements ahead of time without affecting register pressure.
24692471
let compiled_elems = contents
24702472
.iter()
2471-
.enumerate()
2472-
.map(|(idx, e)| {
2473-
if idx == 0 {
2474-
// The first element has already been compiled
2475-
Ok::<Value, CompileError>(first_elem_value)
2476-
} else {
2477-
let val = self.compile_expression_to_value(context, md_mgr, e)?;
2478-
Ok(val.value)
2479-
}
2473+
.map(|e| {
2474+
Ok::<_, CompileError>(
2475+
self.compile_expression_to_value(context, md_mgr, e)?.value,
2476+
)
24802477
})
24812478
.collect::<Result<Vec<_>, _>>()?;
24822479
let mut compiled_elems_iter = compiled_elems.iter();
@@ -2565,13 +2562,9 @@ impl<'eng> FnCompiler<'eng> {
25652562

25662563
// Compile each element and insert it immediately.
25672564
for (idx, elem_expr) in contents.iter().enumerate() {
2568-
let elem_value = if idx == 0 {
2569-
first_elem_value
2570-
} else {
2571-
return_on_termination_or_extract!(
2572-
self.compile_expression_to_value(context, md_mgr, elem_expr)?
2573-
)
2574-
};
2565+
let elem_value = return_on_termination_or_extract!(
2566+
self.compile_expression_to_value(context, md_mgr, elem_expr)?
2567+
);
25752568
let gep_val = self.current_block.append(context).get_elem_ptr_with_idx(
25762569
array_value,
25772570
elem_type,

sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,15 @@ impl ty::TyExpression {
364364
ExpressionKind::Break => {
365365
let expr = ty::TyExpression {
366366
expression: ty::TyExpressionVariant::Break,
367-
return_type: type_engine.insert(engines, TypeInfo::Unknown, None),
367+
return_type: type_engine.insert(engines, TypeInfo::Never, None),
368368
span,
369369
};
370370
Ok(expr)
371371
}
372372
ExpressionKind::Continue => {
373373
let expr = ty::TyExpression {
374374
expression: ty::TyExpressionVariant::Continue,
375-
return_type: type_engine.insert(engines, TypeInfo::Unknown, None),
375+
return_type: type_engine.insert(engines, TypeInfo::Never, None),
376376
span,
377377
};
378378
Ok(expr)
@@ -408,8 +408,7 @@ impl ty::TyExpression {
408408
.unwrap_or_else(|err| ty::TyExpression::error(err, expr_span, engines));
409409
let typed_expr = ty::TyExpression {
410410
expression: ty::TyExpressionVariant::Return(Box::new(expr)),
411-
return_type: type_engine.insert(engines, TypeInfo::Unknown, None),
412-
// FIXME: This should be Yes?
411+
return_type: type_engine.insert(engines, TypeInfo::Never, None),
413412
span,
414413
};
415414
Ok(typed_expr)
@@ -1758,20 +1757,20 @@ impl ty::TyExpression {
17581757
let engines = ctx.engines();
17591758

17601759
if contents.is_empty() {
1761-
let unknown_type = type_engine.insert(engines, TypeInfo::Unknown, None);
1760+
let never_type = type_engine.insert(engines, TypeInfo::Never, None);
17621761
return Ok(ty::TyExpression {
17631762
expression: ty::TyExpressionVariant::Array {
1764-
elem_type: unknown_type,
1763+
elem_type: never_type,
17651764
contents: Vec::new(),
17661765
},
17671766
return_type: type_engine.insert(
17681767
engines,
17691768
TypeInfo::Array(
17701769
TypeArgument {
1771-
type_id: unknown_type,
1770+
type_id: never_type,
17721771
span: Span::dummy(),
17731772
call_path_tree: None,
1774-
initial_type_id: unknown_type,
1773+
initial_type_id: never_type,
17751774
},
17761775
Length::new(0, Span::dummy()),
17771776
),
@@ -1781,7 +1780,8 @@ impl ty::TyExpression {
17811780
});
17821781
};
17831782

1784-
// start each element with the known array element type
1783+
// start each element with the known array element type, or Unknown if it is to be inferred
1784+
// from the elements
17851785
let initial_type = match &*ctx.engines().te().get(ctx.type_annotation()) {
17861786
TypeInfo::Array(element_type, _) => {
17871787
(*ctx.engines().te().get(element_type.type_id)).clone()

test/src/e2e_vm_tests/test_programs/should_fail/break_continue_in_strange_positions/Forc.lock

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/src/e2e_vm_tests/test_programs/should_fail/break_continue_in_strange_positions/Forc.toml

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/src/e2e_vm_tests/test_programs/should_fail/break_continue_in_strange_positions/json_abi_oracle.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/src/e2e_vm_tests/test_programs/should_fail/break_continue_in_strange_positions/src/main.sw

Lines changed: 0 additions & 38 deletions
This file was deleted.

test/src/e2e_vm_tests/test_programs/should_fail/break_continue_in_strange_positions/test.toml

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[package]]
2+
name = "const-instead-of-let"
3+
source = "member"
4+
dependencies = ["std"]
5+
6+
[[package]]
7+
name = "core"
8+
source = "path+from-root-8953E158C7D5F73E"
9+
10+
[[package]]
11+
name = "std"
12+
source = "path+from-root-8953E158C7D5F73E"
13+
dependencies = ["core"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
authors = ["Fuel Labs <[email protected]>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "const-instead-of-let"
6+
7+
[dependencies]
8+
std = { path = "../../../../../../sway-lib-std" }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"configurables": [],
3+
"functions": [
4+
{
5+
"attributes": null,
6+
"inputs": [],
7+
"name": "main",
8+
"output": {
9+
"name": "",
10+
"type": 0,
11+
"typeArguments": null
12+
}
13+
}
14+
],
15+
"loggedTypes": [],
16+
"messagesTypes": [],
17+
"types": [
18+
{
19+
"components": null,
20+
"type": "u64",
21+
"typeId": 0,
22+
"typeParameters": null
23+
}
24+
]
25+
}

0 commit comments

Comments
 (0)