Skip to content

Commit 76df11f

Browse files
Francesco Zappa Nardellimeta-codesync[bot]
authored andcommitted
a class cannot be sealed with a trait
Summary: The semantics of sealing a class with a trait, eg: ``` <<__Sealed(T::class)>> class C { ... } trait T { ... } ``` is unclear at best. This diff bans sealing a class with a trait. Reviewed By: geralt-encore Differential Revision: D92393842 fbshipit-source-id: f8ba025f685702b4c4ff5497c52b1cb5d8d19c50
1 parent a85e3ce commit 76df11f

10 files changed

Lines changed: 93 additions & 4 deletions

File tree

hphp/hack/src/diagnostics/error_codes.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ module NastCheck = struct
284284
(* | PackageExprInInvariantDEPRECATED [@value 3105] *)
285285
| NamedInTransformedPseudofunction [@value 3106]
286286
| RequirePackageStrictInclusion [@value 3107]
287+
| ClassSealedWithTrait [@value 3108]
287288
(* Add new NastCheck codes here! Comment out when deprecating. *)
288289
[@@deriving enum, show { with_path = false }]
289290

hphp/hack/src/hackc/compile/compile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ fn emit_fatal_nast_check_error(err: &NastCheckError) -> Result<Unit, Error> {
563563
NastCheckError::CloneReturnType(_) => todo!(),
564564
NastCheckError::AttributeImplementedByRestriction { .. } => todo!(),
565565
NastCheckError::RequirePackageStrictInclusion { .. } => todo!(),
566+
NastCheckError::ClassSealedWithTrait { .. } => todo!(),
566567
}
567568
}
568569

hphp/hack/src/oxidized/gen/error_codes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<1979bd32cde8ff3069353e3a5093c26e>>
6+
// @generated SignedSource<<e8f3018ac482cee215fbdf70aca4ce8e>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -225,6 +225,7 @@ pub enum NastCheck {
225225
CloneReturnType = 3104,
226226
NamedInTransformedPseudofunction = 3106,
227227
RequirePackageStrictInclusion = 3107,
228+
ClassSealedWithTrait = 3108,
228229
}
229230
impl TrivialDrop for NastCheck {}
230231
arena_deserializer::impl_deserialize_in_arena!(NastCheck);

hphp/hack/src/oxidized/gen/nast_check_error.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<c2c92aa563f43993231b16002ec3023c>>
6+
// @generated SignedSource<<88a1d0face0230950853fb28a36ecce6>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -313,6 +313,12 @@ pub enum NastCheckError {
313313
MissingAssign(pos::Pos),
314314
#[rust_to_ocaml(name = "Clone_return_type")]
315315
CloneReturnType(pos::Pos),
316+
#[rust_to_ocaml(name = "Class_sealed_with_trait")]
317+
ClassSealedWithTrait {
318+
pos: pos::Pos,
319+
class_name: String,
320+
trait_name: String,
321+
},
316322
#[rust_to_ocaml(name = "Require_package_strict_inclusion")]
317323
RequirePackageStrictInclusion {
318324
required_pos: pos::Pos,

hphp/hack/src/typing/nast_check/attribute_nast_checks.ml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,42 @@ let check_dynamically_referenced attrs =
206206
to_user_diagnostic
207207
@@ Attribute_too_many_arguments { pos; name; expected = 1 }))
208208

209+
(* Check that a sealed attribute on a class does not refer to a trait. *)
210+
let check_no_class_sealed_with_trait env c =
211+
(* Only check classes, not interfaces or traits *)
212+
match c.c_kind with
213+
| Ast_defs.Cclass _ -> begin
214+
match find_attribute SN.UserAttributes.uaSealed c.c_user_attributes with
215+
| None -> ()
216+
| Some sealed_attr ->
217+
let class_name = snd c.c_name in
218+
let ctx = env.Nast_check_env.ctx in
219+
List.iter
220+
(fun (_, pos, expr_) ->
221+
match expr_ with
222+
| Class_const ((_, _, CI (_, name)), _) -> begin
223+
(* Look up the referenced class to check if it's a trait *)
224+
match Naming_provider.get_type_path ctx name with
225+
| None -> ()
226+
| Some path -> begin
227+
match
228+
Ast_provider.find_class_in_file ctx path name ~full:false
229+
with
230+
| None -> ()
231+
| Some cls ->
232+
if Ast_defs.is_c_trait cls.Aast.c_kind then
233+
Diagnostics.add_diagnostic
234+
Nast_check_error.(
235+
to_user_diagnostic
236+
@@ Class_sealed_with_trait
237+
{ pos; class_name; trait_name = name })
238+
end
239+
end
240+
| _ -> ())
241+
sealed_attr.ua_params
242+
end
243+
| _ -> ()
244+
209245
let check_no_sealed_on_constructors m =
210246
let (_, method_name) = m.m_name in
211247
if String.equal method_name SN.Members.__construct then begin
@@ -413,6 +449,7 @@ let handler =
413449
check_autocomplete_valid_text c.c_user_attributes;
414450
check_soft_internal_without_internal c.c_internal c.c_user_attributes;
415451
check_dynamically_referenced c.c_user_attributes;
452+
check_no_class_sealed_with_trait env c;
416453
check_attribute_arity
417454
c.c_user_attributes
418455
SN.UserAttributes.uaDocs

hphp/hack/src/typing/nast_check/nast_check_error.ml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ type t =
194194
| Lateinit_with_default of Pos.t
195195
| Missing_assign of Pos.t
196196
| Clone_return_type of Pos.t
197+
| Class_sealed_with_trait of {
198+
pos: Pos.t;
199+
class_name: string;
200+
trait_name: string;
201+
}
197202
| Require_package_strict_inclusion of {
198203
required_pos: Pos.t;
199204
required: string;
@@ -856,6 +861,18 @@ let clone_return_type pos =
856861
)
857862
[]
858863

864+
let class_sealed_with_trait pos class_name trait_name =
865+
let _ = class_name in
866+
User_diagnostic.make_err
867+
Error_code.(to_enum ClassSealedWithTrait)
868+
(pos, "Traits cannot appear in sealed allowlists")
869+
[
870+
( Pos_or_decl.of_raw_pos pos,
871+
Printf.sprintf
872+
"%s is a trait"
873+
(Markdown_lite.md_codify @@ Render.strip_ns trait_name) );
874+
]
875+
859876
let require_package_strict_inclusion
860877
required_pos
861878
required
@@ -995,6 +1012,8 @@ let to_user_diagnostic t =
9951012
| Lateinit_with_default pos -> lateinit_with_default pos
9961013
| Missing_assign pos -> missing_assign pos
9971014
| Clone_return_type pos -> clone_return_type pos
1015+
| Class_sealed_with_trait { pos; class_name; trait_name } ->
1016+
class_sealed_with_trait pos class_name trait_name
9981017
| Require_package_strict_inclusion
9991018
{
10001019
required_pos;

hphp/hack/src/typing/nast_check/nast_check_error.mli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ type t =
193193
| Lateinit_with_default of Pos.t
194194
| Missing_assign of Pos.t
195195
| Clone_return_type of Pos.t
196+
| Class_sealed_with_trait of {
197+
pos: Pos.t;
198+
class_name: string;
199+
trait_name: string;
200+
}
196201
| Require_package_strict_inclusion of {
197202
required_pos: Pos.t;
198203
required: string;

hphp/hack/src/typing/typing_class.ml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,10 +479,19 @@ let sealed_subtype (c : Nast.class_) ~is_enum ~env =
479479
else
480480
Cls.has_ancestor decl parent_name
481481
in
482-
if not includes_ancestor then
482+
(* Skip the warning if this is a class with a trait in the sealed allowlist.
483+
This case is now handled as a proper error in nast_check (NastCheck[3108])
484+
since traits cannot extend classes. *)
485+
let class_kind = Cls.kind decl in
486+
let is_class_with_trait =
487+
Ast_defs.(
488+
match (parent_kind, class_kind) with
489+
| (Cclass _, Ctrait) -> true
490+
| _ -> false)
491+
in
492+
if (not includes_ancestor) && not is_class_with_trait then
483493
let parent_pos = pos in
484494
let child_name = Cls.name decl in
485-
let class_kind = Cls.kind decl in
486495
let (child_kind, verb) =
487496
Ast_defs.(
488497
match (parent_kind, class_kind) with
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?hh
2+
3+
trait MyTrait {}
4+
5+
<<__Sealed(MyTrait::class)>>
6+
class MyClass {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ERROR: File "sealed_class_with_trait.php", line 5, characters 12-25:
2+
Traits cannot appear in sealed allowlists (NastCheck[3108])
3+
File "sealed_class_with_trait.php", line 5, characters 12-25:
4+
`MyTrait` is a trait

0 commit comments

Comments
 (0)