Skip to content

Commit

Permalink
Enforce decorating prod classes with final, abstract, or __Sealed
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed May 21, 2024
1 parent 3c222a8 commit 5d63ad7
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 3 deletions.
47 changes: 47 additions & 0 deletions src/analyzer/classlike_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,53 @@ impl<'a> ClassLikeAnalyzer<'a> {
analysis_data.issue_filter = Some(issue_filter.clone());
}

if stmt.kind.is_cclass()
&& classlike_storage
.direct_parent_class
.map(|parent_class_name| {
codebase
.classlike_infos
.get(&parent_class_name)
.map(|parent_classlike_storage| parent_classlike_storage.is_final)
.unwrap_or(false)
})
.unwrap_or(false)
{
analysis_data.maybe_add_issue(
Issue::new(
IssueKind::ExtendFinalClass,
"Cannot extend final class".to_string(),
classlike_storage.name_location,
&Some(FunctionLikeIdentifier::Function(name)),
),
statements_analyzer.get_config(),
statements_analyzer.get_file_path_actual(),
);
}

if stmt.kind.is_cclass()
&& !stmt.is_xhp
&& !classlike_storage.is_abstract
&& !classlike_storage.is_final
&& classlike_storage.child_classlikes.is_none()
&& function_context.is_production(codebase)
&& !classlike_storage
.all_parent_classes
.iter()
.any(|c| c == &StrId::EXCEPTION)
{
analysis_data.maybe_add_issue(
Issue::new(
IssueKind::MissingFinalOrAbstract,
"Class should always be declared abstract, final, or <<__Sealed>>".to_string(),
classlike_storage.name_location,
&Some(FunctionLikeIdentifier::Function(name)),
),
statements_analyzer.get_config(),
statements_analyzer.get_file_path_actual(),
);
}

let mut existing_enum_str_values = FxHashMap::default();
let mut existing_enum_int_values = FxHashMap::default();

Expand Down
16 changes: 14 additions & 2 deletions src/analyzer/expr/call/new_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,20 @@ fn analyze_named_constructor(
// todo check for unsafe instantiation
}

if storage.is_abstract && !can_extend {
// todo complain about abstract instantiation
if storage.is_abstract && !can_extend && !from_classname {
analysis_data.maybe_add_issue(
Issue::new(
IssueKind::AbstractInstantiation,
format!(
"Cannot call new on abstract class {}",
statements_analyzer.get_interner().lookup(&classlike_name)
),
statements_analyzer.get_hpos(pos),
&context.function_context.calling_functionlike_id,
),
statements_analyzer.get_config(),
statements_analyzer.get_file_path_actual(),
);
}

if storage.is_deprecated
Expand Down
12 changes: 11 additions & 1 deletion src/code_info/function_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,17 @@ impl FunctionContext {
.unwrap()
.is_production_code
}
_ => false,
_ => {
if let Some(calling_class) = self.calling_class {
codebase
.classlike_infos
.get(&calling_class)
.unwrap()
.is_production_code
} else {
false
}
}
}
}
}
5 changes: 5 additions & 0 deletions src/code_info/issue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ use crate::{

#[derive(Clone, PartialEq, Eq, Hash, Display, Debug, Serialize, Deserialize, EnumString)]
pub enum IssueKind {
AbstractInstantiation,
ExtendFinalClass,
CannotInferGenericParam,
CustomIssue(Box<String>),
DuplicateEnumValue,
EmptyBlock,
FalsableReturnStatement,
FalseArgument,
MissingFinalOrAbstract,
ForLoopInvalidation,
ImmutablePropertyWrite,
ImpossibleArrayAssignment,
Expand Down Expand Up @@ -311,6 +314,8 @@ pub fn get_issue_from_comment(
return Some(Ok(IssueKind::UnusedAssignment));
} else if trimmed_text.starts_with("HHAST_FIXME[NoJoinInAsyncFunction]") {
return Some(Ok(IssueKind::NoJoinInAsyncFunction));
} else if trimmed_text.starts_with("HHAST_FIXME[FinalOrAbstractClass]") {
return Some(Ok(IssueKind::MissingFinalOrAbstract));
}

None
Expand Down
15 changes: 15 additions & 0 deletions tests/inference/Class/finalOrAbstractClass/input.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
final class A {}

abstract class B {}

interface C {}

<<__Sealed()>>
class D {}

class E extends \Exception {}

class F {}

/* HHAST_FIXME[FinalOrAbstractClass] */
class G {}
1 change: 1 addition & 0 deletions tests/inference/Class/finalOrAbstractClass/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ERROR: FinalOrAbstractClass - input.hack:12:7

0 comments on commit 5d63ad7

Please sign in to comment.