From 5b45d055cf42f2b9d47ac42ac085886df636e967 Mon Sep 17 00:00:00 2001 From: Filippo Casarin Date: Tue, 5 Dec 2023 02:00:47 +0100 Subject: [PATCH 1/3] Merge subtask_description and subtask_name --- src/tools/find_bad_case/dag.rs | 1 - task-maker-format/src/ioi/finish_ui.rs | 8 +-- .../src/ioi/format/italian_yaml/cases_gen.rs | 60 ++++++++++++------- .../src/ioi/format/italian_yaml/gen_gen.rs | 2 - .../src/ioi/format/italian_yaml/mod.rs | 16 ++--- .../ioi/format/italian_yaml/static_inputs.rs | 3 +- task-maker-format/src/ioi/mod.rs | 2 - task-maker-format/tests/utils.rs | 2 - 8 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/tools/find_bad_case/dag.rs b/src/tools/find_bad_case/dag.rs index 1f34c8e72..1a0526e1e 100644 --- a/src/tools/find_bad_case/dag.rs +++ b/src/tools/find_bad_case/dag.rs @@ -94,7 +94,6 @@ pub fn patch_task_for_batch( let subtask = SubtaskInfo { id: 0, name: Some(format!("batch-{}", batch_index)), - description: None, max_score: 100.0, testcases, span: None, diff --git a/task-maker-format/src/ioi/finish_ui.rs b/task-maker-format/src/ioi/finish_ui.rs index 4de20c4f9..8e0dc6552 100644 --- a/task-maker-format/src/ioi/finish_ui.rs +++ b/task-maker-format/src/ioi/finish_ui.rs @@ -120,8 +120,8 @@ impl FinishUI { cwriteln!(self, BLUE, "Generations"); for (st_num, subtask) in state.generations.iter().sorted_by_key(|(n, _)| *n) { cwrite!(self, BOLD, "Subtask {}", st_num); - if let Some(description) = &state.task.subtasks[st_num].description { - print!(" [{}]", description); + if let Some(name) = &state.task.subtasks[st_num].name { + print!(" [{}]", name); } println!(": {} points", state.task.subtasks[st_num].max_score); for (tc_num, testcase) in subtask.testcases.iter().sorted_by_key(|(n, _)| *n) { @@ -232,8 +232,8 @@ impl FinishUI { for (st_num, subtask) in eval.subtasks.iter().sorted_by_key(|(n, _)| *n) { cwrite!(self, BOLD, "Subtask #{}", st_num); - if let Some(description) = &state.task.subtasks[st_num].description { - print!(" [{}]", description); + if let Some(name) = &state.task.subtasks[st_num].name { + print!(" [{}]", name); } print!(": "); let max_score = state.task.subtasks[st_num].max_score; diff --git a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs index f00b391aa..9c6648688 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs @@ -124,8 +124,8 @@ where current_validator: Option, /// The identifier of the next subtask to process. subtask_id: SubtaskId, - /// The description of the last subtask added, if any. - subtask_description: Option, + /// The name of the last subtask added, if any. + subtask_name: Option, /// The identifier of the next testcase to process. testcase_id: TestcaseId, } @@ -166,7 +166,7 @@ where current_generator: None, current_validator: None, subtask_id: 0, - subtask_description: None, + subtask_name: None, testcase_id: 0, }; @@ -227,8 +227,8 @@ where for entry in &self.result { match entry { TaskInputEntry::Subtask(subtask) => { - if let Some(descr) = &subtask.description { - let _ = writeln!(gen, "\n# Subtask {}: {}", subtask.id, descr); + if let Some(name) = &subtask.name { + let _ = writeln!(gen, "\n# Subtask {}: {}", subtask.id, name); } else { let _ = writeln!(gen, "\n# Subtask {}", subtask.id); } @@ -521,17 +521,18 @@ where self.subtask_id, score ) })?; - let description = if line.len() >= 2 { - Some(line[1].as_str().to_string()) + let name = if line.len() >= 2 { + // Remove whitespaces for retrocompatibility with descriptions + let s = line[1].as_str(); + Some(s.chars().filter(|&c| c != ' ' && c != '\t').collect()) } else { None }; - self.subtask_description = description.clone(); + self.subtask_name = name.clone(); // FIXME: the cases.gen format does not yet support giving the subtasks a name. self.result.push(TaskInputEntry::Subtask(SubtaskInfo { id: self.subtask_id, - name: None, - description, + name, max_score: score, testcases: HashMap::new(), span: CodeSpan::from_str( @@ -637,8 +638,8 @@ where vars.insert("INPUT".to_string(), TM_VALIDATION_FILE_NAME.to_string()); vars.insert("ST_NUM".to_string(), (self.subtask_id - 1).to_string()); vars.insert("TC_NUM".to_string(), self.testcase_id.to_string()); - if let Some(descr) = &self.subtask_description { - vars.insert("ST_DESCRIPTION".to_string(), descr.clone()); + if let Some(name) = &self.subtask_name { + vars.insert("ST_NAME".to_string(), name.clone()); } vars } @@ -850,11 +851,11 @@ mod tests { assert_eq!(vars["INPUT"], TM_VALIDATION_FILE_NAME); assert_eq!(vars["ST_NUM"], "0"); assert_eq!(vars["TC_NUM"], "1"); - assert_eq!(vars["ST_DESCRIPTION"], "lol"); + assert_eq!(vars["ST_NAME"], "lol"); } #[test] - fn test_auto_variables_no_descr() { + fn test_auto_variables_no_name() { let gen = TestHelper::new() .add_file("gen/generator.py") .cases_gen(":GEN gen gen/generator.py\n:SUBTASK 42 lol\n12 34\n: SUBTASK 43") @@ -863,7 +864,7 @@ mod tests { assert_eq!(vars["INPUT"], TM_VALIDATION_FILE_NAME); assert_eq!(vars["ST_NUM"], "1"); assert_eq!(vars["TC_NUM"], "1"); - assert!(!vars.contains_key("ST_DESCRIPTION")); + assert!(!vars.contains_key("ST_NAME")); } #[test] @@ -1223,7 +1224,7 @@ mod tests { let subtask = &gen.result[0]; if let TaskInputEntry::Subtask(subtask) = subtask { assert_eq!(subtask.id, 0); - assert_eq!(subtask.description, None); + assert_eq!(subtask.name, None); assert_abs_diff_eq!(subtask.max_score, 42.0); } else { panic!("Expecting a subtask, got: {:?}", subtask); @@ -1231,17 +1232,32 @@ mod tests { } #[test] - fn test_add_subtask_description() { + fn test_add_subtask_name() { + let gen = TestHelper::new().cases_gen(":SUBTASK 42 the-name").unwrap(); + assert_eq!(gen.subtask_id, 1); + assert_eq!(gen.result.len(), 1); + let subtask = &gen.result[0]; + if let TaskInputEntry::Subtask(subtask) = subtask { + assert_eq!(subtask.id, 0); + assert_eq!(subtask.name, Some("the-name".into())); + assert_abs_diff_eq!(subtask.max_score, 42.0); + } else { + panic!("Expecting a subtask, got: {:?}", subtask); + } + } + + #[test] + fn test_add_subtask_space_in_name() { let gen = TestHelper::new() - .cases_gen(":SUBTASK 42 the description") + .cases_gen(":SUBTASK 42.42 the name") .unwrap(); assert_eq!(gen.subtask_id, 1); assert_eq!(gen.result.len(), 1); let subtask = &gen.result[0]; if let TaskInputEntry::Subtask(subtask) = subtask { assert_eq!(subtask.id, 0); - assert_eq!(subtask.description, Some("the description".into())); - assert_abs_diff_eq!(subtask.max_score, 42.0); + assert_eq!(subtask.name, Some("thename".into())); + assert_abs_diff_eq!(subtask.max_score, 42.42); } else { panic!("Expecting a subtask, got: {:?}", subtask); } @@ -1250,14 +1266,14 @@ mod tests { #[test] fn test_add_subtask_float_score() { let gen = TestHelper::new() - .cases_gen(":SUBTASK 42.42 the description") + .cases_gen(":SUBTASK 42.42 the-name") .unwrap(); assert_eq!(gen.subtask_id, 1); assert_eq!(gen.result.len(), 1); let subtask = &gen.result[0]; if let TaskInputEntry::Subtask(subtask) = subtask { assert_eq!(subtask.id, 0); - assert_eq!(subtask.description, Some("the description".into())); + assert_eq!(subtask.name, Some("the-name".into())); assert_abs_diff_eq!(subtask.max_score, 42.42); } else { panic!("Expecting a subtask, got: {:?}", subtask); diff --git a/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs index 6037a9c51..07c1d9097 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs @@ -52,7 +52,6 @@ where let mut default_subtask = Some(SubtaskInfo { id: 0, name: None, - description: None, max_score: 100.0, testcases: HashMap::new(), span: None, @@ -101,7 +100,6 @@ where entries.push(TaskInputEntry::Subtask(SubtaskInfo { id: subtask_id, name: None, - description: None, max_score: score.parse::().context("Invalid subtask score")?, testcases: HashMap::new(), span: CodeSpan::from_str( diff --git a/task-maker-format/src/ioi/format/italian_yaml/mod.rs b/task-maker-format/src/ioi/format/italian_yaml/mod.rs index b0886461e..94c24548e 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/mod.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/mod.rs @@ -161,15 +161,15 @@ //! Example: `:CONSTRAINT 0 <= $N < $M <= 1000000` will check that the variables `$N` and `$M` are //! between 0 and 1000000 and `$N` is smaller than `$M`. //! -//! ### `: SUBTASK score [description]` +//! ### `: SUBTASK score [name]` //! This command marks the start of a new subtask, just like how `#ST` in `gen/GEN` did. The score -//! can be a simple floating point number (either an integer or an integer.integer). The description +//! can be a simple floating point number (either an integer or an integer.integer). The name //! that follows is optional and will be included in the subtask metadata. //! //! When a new subtask is started the generator and validator will be reset to the default ones. //! -//! Example: `: SUBTASK 40 All the nodes are in a line` defines a new subtask worth 40 points, with -//! the provided description. +//! Example: `: SUBTASK 40 all-the-nodes-are-in-a-line` defines a new subtask worth 40 points, with +//! the provided name. //! //! ### `: COPY path` //! This command creates a new testcase coping the input file from the specified path, relative to @@ -200,7 +200,7 @@ //! obtained from the parsing of the generator's arguments will be available. Also some automatic //! variables will be available: //! - `$ST_NUM`: the 0-based index of the subtask -//! - `$ST_DESCRIPTION`: the description of the subtask +//! - `$ST_NAME`: the name of the subtask //! - `$TC_NUM`: the 0-based index of the testcase //! - `$INPUT` _(only for validators)_: the name of the file to validate //! @@ -216,11 +216,11 @@ //! : CONSTRAINT 1 <= $N <= 1000 //! : CONSTRAINT 1 <= $M <= 1000000 //! -//! : SUBTASK 0 Examples +//! : SUBTASK 0 examples //! : COPY gen/example1.in //! : COPY gen/example2.in //! -//! : SUBTASK 30 Nodes are in a line +//! : SUBTASK 30 nodes-are-in-a-line //! : GEN line //! : VAL line //! : CONSTRAINT $N <= 500 @@ -254,7 +254,7 @@ //! which will use the `hard` one. Note that since the `hard` generator does not have the argument //! specification, its parameters won't be checked. Also note that the constraint `$N <= 500` won't //! be checked because it was scoped only to the second subtask. -//! The subtask also does not have a description, the default one (`Subtask 2`) will be used. +//! The subtask also does not have a name, the default one (`subtask2`) will be used. use std::collections::HashMap; use std::fs; diff --git a/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs b/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs index 421b12143..c9b251166 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs @@ -38,8 +38,7 @@ where self.index = 1; return Some(TaskInputEntry::Subtask(SubtaskInfo { id: 0, - name: None, - description: Some("Static testcases".into()), + name: Some("static-testcases".into()), max_score: 100.0, testcases: HashMap::new(), span: None, diff --git a/task-maker-format/src/ioi/mod.rs b/task-maker-format/src/ioi/mod.rs index cfc9eef8c..817a4fb01 100644 --- a/task-maker-format/src/ioi/mod.rs +++ b/task-maker-format/src/ioi/mod.rs @@ -156,8 +156,6 @@ pub struct SubtaskInfo { /// /// This is what is used for running the solutions' checks. pub name: Option, - /// Textual description of the subtask. - pub description: Option, /// The maximum score of the subtask, must be >= 0. pub max_score: f64, /// The testcases inside this subtask. diff --git a/task-maker-format/tests/utils.rs b/task-maker-format/tests/utils.rs index c16376235..89f34d868 100644 --- a/task-maker-format/tests/utils.rs +++ b/task-maker-format/tests/utils.rs @@ -43,7 +43,6 @@ pub fn new_task_with_context(path: &Path) -> IOITask { let st0 = task.subtasks.entry(0).or_insert(SubtaskInfo { id: 0, name: None, - description: None, max_score: 10.0, testcases: HashMap::default(), span: None, @@ -60,7 +59,6 @@ pub fn new_task_with_context(path: &Path) -> IOITask { let st1 = task.subtasks.entry(1).or_insert(SubtaskInfo { id: 1, name: None, - description: None, max_score: 90.0, testcases: HashMap::default(), span: None, From cbd2f2415ae3ea0ec87512942c457d7093101dbe Mon Sep 17 00:00:00 2001 From: Filippo Casarin Date: Tue, 5 Dec 2023 11:54:33 +0100 Subject: [PATCH 2/3] Improve gen/GEN generation from cases.gen --- .../src/ioi/format/italian_yaml/cases_gen.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs index 9c6648688..4c229349d 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs @@ -227,12 +227,11 @@ where for entry in &self.result { match entry { TaskInputEntry::Subtask(subtask) => { + let _ = writeln!(gen, "\n# Subtask {}", subtask.id); + let _ = writeln!(gen, "#ST: {}", subtask.max_score); if let Some(name) = &subtask.name { - let _ = writeln!(gen, "\n# Subtask {}: {}", subtask.id, name); - } else { - let _ = writeln!(gen, "\n# Subtask {}", subtask.id); + let _ = writeln!(gen, "#STNAME: {}", name); } - let _ = writeln!(gen, "#ST: {}", subtask.max_score); if let Some(constraints) = self.subtask_constraints.get(subtask.id as usize) { for constr in constraints { let _ = writeln!(gen, "# {:?}", constr); @@ -529,7 +528,6 @@ where None }; self.subtask_name = name.clone(); - // FIXME: the cases.gen format does not yet support giving the subtasks a name. self.result.push(TaskInputEntry::Subtask(SubtaskInfo { id: self.subtask_id, name, @@ -834,11 +832,12 @@ mod tests { .split('\n') .filter(|s| !s.is_empty() && !s.starts_with("# ") && !s.starts_with("#COPY")) .collect(); - assert_eq!(res.len(), 4); + assert_eq!(res.len(), 5); assert_eq!(res[0], "#ST: 42"); - assert!(res[1].contains("12 34")); - assert_eq!(res[2], "#ST: 24"); - assert!(res[3].contains("21 21")); + assert_eq!(res[1], "#STNAME: lol"); + assert!(res[2].contains("12 34")); + assert_eq!(res[3], "#ST: 24"); + assert!(res[4].contains("21 21")); } #[test] From 49503f7d79791ff49a0a3bc246c431eeae9b717a Mon Sep 17 00:00:00 2001 From: Dario Petrillo Date: Thu, 7 Dec 2023 00:35:38 +0100 Subject: [PATCH 3/3] SanityCheck for deprecated spaces in subtask name --- src/tools/find_bad_case/dag.rs | 2 +- .../src/ioi/format/italian_yaml/cases_gen.rs | 42 +++++++++++-------- .../src/ioi/format/italian_yaml/gen_gen.rs | 8 +--- .../ioi/format/italian_yaml/static_inputs.rs | 4 +- task-maker-format/src/ioi/mod.rs | 5 ++- .../src/ioi/sanity_checks/subtasks.rs | 39 +++++++++++++++++ task-maker-format/tests/utils.rs | 8 +--- 7 files changed, 73 insertions(+), 35 deletions(-) diff --git a/src/tools/find_bad_case/dag.rs b/src/tools/find_bad_case/dag.rs index 1a0526e1e..a8f812eb3 100644 --- a/src/tools/find_bad_case/dag.rs +++ b/src/tools/find_bad_case/dag.rs @@ -96,8 +96,8 @@ pub fn patch_task_for_batch( name: Some(format!("batch-{}", batch_index)), max_score: 100.0, testcases, - span: None, is_default: false, + ..Default::default() }; task.subtasks.insert(0, subtask); } diff --git a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs index 4c229349d..a29073634 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs @@ -520,28 +520,34 @@ where self.subtask_id, score ) })?; - let name = if line.len() >= 2 { - // Remove whitespaces for retrocompatibility with descriptions - let s = line[1].as_str(); - Some(s.chars().filter(|&c| c != ' ' && c != '\t').collect()) + let description = if line.len() >= 2 { + Some(line[1].as_str().to_string()) } else { None }; + // Remove whitespaces for retrocompatibility with descriptions + let name = description + .as_deref() + .map(|s| s.chars().filter(|&c| c != ' ' && c != '\t').collect()); self.subtask_name = name.clone(); - self.result.push(TaskInputEntry::Subtask(SubtaskInfo { - id: self.subtask_id, - name, - max_score: score, - testcases: HashMap::new(), - span: CodeSpan::from_str( - &self.file_path, - &self.file_content, - span.start(), - span.end() - span.start(), - ) - .ok(), - is_default: false, - })); + self.result.push(TaskInputEntry::Subtask( + #[allow(deprecated)] + SubtaskInfo { + id: self.subtask_id, + name, + description, + max_score: score, + span: CodeSpan::from_str( + &self.file_path, + &self.file_content, + span.start(), + span.end() - span.start(), + ) + .ok(), + is_default: false, + ..Default::default() + }, + )); self.subtask_id += 1; Ok(()) } diff --git a/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs index 07c1d9097..834859e94 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/gen_gen.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::path::Path; use std::sync::Arc; @@ -51,11 +50,9 @@ where let mut default_subtask = Some(SubtaskInfo { id: 0, - name: None, max_score: 100.0, - testcases: HashMap::new(), - span: None, is_default: true, + ..Default::default() }); let mut generators = find_source_file( @@ -99,9 +96,7 @@ where let path = path.strip_prefix(task_dir).unwrap_or(path); entries.push(TaskInputEntry::Subtask(SubtaskInfo { id: subtask_id, - name: None, max_score: score.parse::().context("Invalid subtask score")?, - testcases: HashMap::new(), span: CodeSpan::from_str( path, &content, @@ -110,6 +105,7 @@ where ) .ok(), is_default: false, + ..Default::default() })); subtask_id += 1; } diff --git a/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs b/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs index c9b251166..e5af9a52b 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/static_inputs.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::path::PathBuf; use crate::ioi::format::italian_yaml::TaskInputEntry; @@ -40,9 +39,8 @@ where id: 0, name: Some("static-testcases".into()), max_score: 100.0, - testcases: HashMap::new(), - span: None, is_default: true, + ..Default::default() })); } let id = self.index - 1; // offset caused by the first iteration diff --git a/task-maker-format/src/ioi/mod.rs b/task-maker-format/src/ioi/mod.rs index 817a4fb01..4af489ee0 100644 --- a/task-maker-format/src/ioi/mod.rs +++ b/task-maker-format/src/ioi/mod.rs @@ -148,7 +148,7 @@ pub struct IOITask { } /// A subtask of a IOI task. -#[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] +#[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify, Default)] pub struct SubtaskInfo { /// The id of the subtask. pub id: SubtaskId, @@ -156,6 +156,9 @@ pub struct SubtaskInfo { /// /// This is what is used for running the solutions' checks. pub name: Option, + /// Textual description of the subtask. (deprecated) + #[deprecated(note = "Use the `name` field instead")] + pub description: Option, /// The maximum score of the subtask, must be >= 0. pub max_score: f64, /// The testcases inside this subtask. diff --git a/task-maker-format/src/ioi/sanity_checks/subtasks.rs b/task-maker-format/src/ioi/sanity_checks/subtasks.rs index 3d01d7df0..b9187cec6 100644 --- a/task-maker-format/src/ioi/sanity_checks/subtasks.rs +++ b/task-maker-format/src/ioi/sanity_checks/subtasks.rs @@ -231,3 +231,42 @@ impl SanityCheck for AllOutputsEqual { Ok(()) } } + +/// Check if any subtask uses the deprecated `description` field. +#[derive(Debug, Default)] +pub struct DeprecatedDescriptionInSubtask; +make_sanity_check!(DeprecatedDescriptionInSubtask); + +impl SanityCheck for DeprecatedDescriptionInSubtask { + type Task = IOITask; + + fn name(&self) -> &'static str { + "DeprecatedDescriptionInSubtask" + } + + fn category(&self) -> SanityCheckCategory { + SanityCheckCategory::Task + } + + fn pre_hook(&self, task: &IOITask, eval: &mut EvaluationData) -> Result<(), Error> { + for subtask in task.subtasks.values() { + #[allow(deprecated)] + let description = subtask.description.as_deref(); + match (subtask.name.as_deref(), description) { + (Some(a), Some(b)) if a != b => { + let mut diagnostic = Diagnostic::warning(format!( + "Subtask {} uses spaces and/or tabs in its name, which are now deprecated", + subtask.id + )); + if let Some(span) = subtask.span.clone() { + diagnostic = diagnostic.with_code_span(span); + } + eval.add_diagnostic(diagnostic)?; + } + _ => {} + } + } + + Ok(()) + } +} diff --git a/task-maker-format/tests/utils.rs b/task-maker-format/tests/utils.rs index 89f34d868..e2ea31828 100644 --- a/task-maker-format/tests/utils.rs +++ b/task-maker-format/tests/utils.rs @@ -42,11 +42,9 @@ pub fn new_task_with_context(path: &Path) -> IOITask { }; let st0 = task.subtasks.entry(0).or_insert(SubtaskInfo { id: 0, - name: None, max_score: 10.0, - testcases: HashMap::default(), - span: None, is_default: false, + ..Default::default() }); st0.testcases.entry(0).or_insert_with(|| { TestcaseInfo::new( @@ -58,11 +56,9 @@ pub fn new_task_with_context(path: &Path) -> IOITask { }); let st1 = task.subtasks.entry(1).or_insert(SubtaskInfo { id: 1, - name: None, max_score: 90.0, - testcases: HashMap::default(), - span: None, is_default: false, + ..Default::default() }); st1.testcases.entry(1).or_insert_with(|| { TestcaseInfo::new(