Skip to main content

mago_analyzer/plugin/libraries/stdlib/json/
json_encode.rs

1//! `json_encode()` return type provider.
2
3use 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/// Provider for the `json_encode()` function.
29///
30/// When `JSON_THROW_ON_ERROR` flag is set, returns `non-empty-string`.
31/// Otherwise returns `non-empty-string|false`.
32#[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}