mago_analyzer/plugin/libraries/stdlib/json/
json_encode.rs1use mago_codex::ttype::atomic::TAtomic;
4use mago_codex::ttype::atomic::scalar::TScalar;
5use mago_codex::ttype::atomic::scalar::bool::TBool;
6use mago_codex::ttype::atomic::scalar::string::TString;
7use mago_codex::ttype::get_non_empty_string;
8use mago_codex::ttype::union::TUnion;
9use mago_syntax::cst::Binary;
10use mago_syntax::cst::BinaryOperator;
11use mago_syntax::cst::Expression;
12
13use crate::plugin::context::InvocationInfo;
14use crate::plugin::context::ProviderContext;
15use crate::plugin::provider::Provider;
16use crate::plugin::provider::ProviderMeta;
17use crate::plugin::provider::function::FunctionReturnTypeProvider;
18use crate::plugin::provider::function::FunctionTarget;
19
20const JSON_THROW_ON_ERROR: i64 = 0x0040_0000;
21
22static META: ProviderMeta = ProviderMeta::new(
23 "php::json::json_encode",
24 "json_encode",
25 "Returns non-empty-string when JSON_THROW_ON_ERROR is set, otherwise string|false",
26);
27
28#[derive(Default)]
33pub struct JsonEncodeProvider;
34
35impl Provider for JsonEncodeProvider {
36 fn meta() -> &'static ProviderMeta {
37 &META
38 }
39}
40
41impl FunctionReturnTypeProvider for JsonEncodeProvider {
42 fn targets() -> FunctionTarget {
43 FunctionTarget::Exact(b"json_encode")
44 }
45
46 fn get_return_type(
47 &self,
48 context: &ProviderContext<'_, '_, '_>,
49 invocation: &InvocationInfo<'_, '_, '_>,
50 ) -> Option<TUnion> {
51 let flags_argument = invocation.get_argument(1, &[b"flags"])?;
52 let flags_type = context.get_expression_type(flags_argument)?;
53
54 let throws = match flags_type.get_single_literal_int_value() {
55 Some(value) => value & JSON_THROW_ON_ERROR > 0,
56 None => flags_contain_throw_on_error(context, flags_argument),
57 };
58
59 Some(if throws {
60 get_non_empty_string()
61 } else {
62 TUnion::from_vec(vec![
63 TAtomic::Scalar(TScalar::String(TString::non_empty())),
64 TAtomic::Scalar(TScalar::Bool(TBool::r#false())),
65 ])
66 })
67 }
68}
69
70fn flags_contain_throw_on_error(context: &ProviderContext<'_, '_, '_>, expr: &Expression<'_>) -> bool {
71 match expr {
72 Expression::Binary(Binary { lhs, operator: BinaryOperator::BitwiseOr(_), rhs }) => {
73 flags_contain_throw_on_error(context, lhs) || flags_contain_throw_on_error(context, rhs)
74 }
75 Expression::Parenthesized(parenthesized) => flags_contain_throw_on_error(context, parenthesized.expression),
76 _ => context
77 .get_expression_type(expr)
78 .and_then(|ty| ty.get_single_literal_int_value())
79 .is_some_and(|value| value & JSON_THROW_ON_ERROR > 0),
80 }
81}