<commit_before>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::associated_data_impl;
use crate::build::{
    build_constant_value, build_type_annotation, build_variable_definitions, ValidationLevel,
};
use crate::constants::ARGUMENT_DEFINITION;
use crate::errors::{ValidationMessage, ValidationMessageWithData};
use crate::ir::{ConstantValue, VariableDefinition};
use crate::GraphQLSuggestions;
use common::{Diagnostic, DiagnosticsResult, FeatureFlag, Location, WithLocation};
use errors::{par_try_map, try2};
use intern::string_key::{Intern, StringKey, StringKeyMap};
use lazy_static::lazy_static;
use schema::{SDLSchema, Schema, Type, TypeReference};
use std::collections::HashMap;

lazy_static! {
    static ref TYPE: StringKey = "type".intern();
    static ref DEFAULT_VALUE: StringKey = "defaultValue".intern();
    static ref PROVIDER: StringKey = "provider".intern();
    pub static ref UNUSED_LOCAL_VARIABLE_DEPRECATED: StringKey =
        "unusedLocalVariable_DEPRECATED".intern();
}

pub type FragmentSignatures = StringKeyMap<FragmentSignature>;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ProvidedVariableMetadata {
    pub module_name: StringKey,
}

associated_data_impl!(ProvidedVariableMetadata);

/// Describes the public API of a fragment, excluding its selections.
/// When translating ASTs to IR, fragment spread arguments must be
/// checked against the corresponding fragment definition. However,
/// this process can't use the IR fragment definition, since that
/// would depend on having checked its body! Since recursive fragments
/// are allowed, we break the cycle by first computing signatures
/// and using these to type check fragment spreads in selections.
#[derive(Debug, Eq, PartialEq)]
pub struct FragmentSignature {
    pub name: WithLocation<StringKey>,
    pub variable_definitions: Vec<VariableDefinition>,
    pub type_condition: Type,
}

pub fn build_signatures(
    schema: &SDLSchema,
    definitions: &[graphql_syntax::ExecutableDefinition],
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<FragmentSignatures> {
    let suggestions = GraphQLSuggestions::new(schema);
    let mut seen_signatures: StringKeyMap<FragmentSignature> =
        HashMap::with_capacity_and_hasher(definitions.len(), Default::default());
    let signatures = par_try_map(definitions, |definition| match definition {
        graphql_syntax::ExecutableDefinition::Fragment(fragment) => Ok(Some(
            build_fragment_signature(schema, fragment, &suggestions, enable_provided_variables)?,
        )),
        graphql_syntax::ExecutableDefinition::Operation(_) => Ok(None),
    })?;
    let mut errors = Vec::new();
    for signature in signatures.into_iter().flatten() {
        let previous_signature = seen_signatures.get(&signature.name.item);
        if let Some(previous_signature) = previous_signature {
            errors.push(
                Diagnostic::error(
                    ValidationMessage::DuplicateDefinition(signature.name.item),
                    previous_signature.name.location,
                )
                .annotate("also defined here", signature.name.location),
            );
            continue;
        }
        seen_signatures.insert(signature.name.item, signature);
    }
    if errors.is_empty() {
        Ok(seen_signatures)
    } else {
        Err(errors)
    }
}

fn build_fragment_signature(
    schema: &SDLSchema,
    fragment: &graphql_syntax::FragmentDefinition,
    suggestions: &GraphQLSuggestions<'_>,
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<FragmentSignature> {
    let type_name = fragment.type_condition.type_.value;
    let type_condition = match schema.get_type(type_name) {
        Some(type_condition) => match type_condition {
            Type::Interface(..) | Type::Object(..) | Type::Union(..) => Ok(type_condition),
            _ => Err(Diagnostic::error(
                ValidationMessage::ExpectedCompositeType(type_condition),
                fragment
                    .location
                    .with_span(fragment.type_condition.type_.span),
            )
            .into()),
        },
        None => Err(Diagnostic::error_with_data(
            ValidationMessageWithData::UnknownType {
                type_name,
                suggestions: suggestions.composite_type_suggestions(type_name),
            },
            fragment
                .location
                .with_span(fragment.type_condition.type_.span),
        )
        .into()),
    };
    let argument_definition_directives = fragment
        .directives
        .iter()
        .filter(|x| x.name.value == *ARGUMENT_DEFINITION)
        .collect::<Vec<_>>();
    if fragment.variable_definitions.is_some() && !argument_definition_directives.is_empty() {
        return Err(Diagnostic::error(
            ValidationMessage::VariableDefinitionsAndArgumentDirective(),
            fragment
                .location
                .with_span(argument_definition_directives[0].span),
        )
        .annotate(
            "variables are previously defined here",
            fragment.location.with_span(
                fragment
                    .variable_definitions
                    .as_ref()
                    .map(|list| list.span)
                    .unwrap(),
            ),
        )
        .into());
    } else if argument_definition_directives.len() > 1 {
        return Err(Diagnostic::error(
            ValidationMessage::ExpectedOneArgumentDefinitionsDirective(),
            fragment
                .location
                .with_span(argument_definition_directives[1].span),
        )
        .into());
    }
    let variable_definitions = fragment
        .variable_definitions
        .as_ref()
        .map(|variable_definitions| {
            build_variable_definitions(schema, &variable_definitions.items, fragment.location)
        })
        .or_else(|| {
            argument_definition_directives.get(0).map(|x| {
                build_fragment_variable_definitions(schema, fragment, x, enable_provided_variables)
            })
        })
        .unwrap_or_else(|| Ok(Default::default()));

    let (type_condition, variable_definitions) = try2(type_condition, variable_definitions)?;
    Ok(FragmentSignature {
        name: fragment
            .name
            .name_with_location(fragment.location.source_location()),
        type_condition,
        variable_definitions,
    })
}

fn build_fragment_variable_definitions(
    schema: &SDLSchema,
    fragment: &graphql_syntax::FragmentDefinition,
    directive: &graphql_syntax::Directive,
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<Vec<VariableDefinition>> {
    if let Some(arguments) = &directive.arguments {
        Ok(arguments
            .items
            .iter()
            .map(|variable_arg| {
                if let graphql_syntax::Value::Constant(graphql_syntax::ConstantValue::Object(
                    object,
                )) = &variable_arg.value
                {
                    let variable_name = &variable_arg.name;
                    let mut extra_items = Vec::new();
                    let mut type_arg = None;
                    let mut default_arg = None;
                    let mut unused_local_variable_arg = None;
                    let mut provider_arg = None;
                    for item in &object.items {
                        let name = item.name.value;
                        if name == *TYPE {
                            type_arg = Some(item);
                        } else if name == *DEFAULT_VALUE {
                            default_arg = Some(item);
                        } else if name == *UNUSED_LOCAL_VARIABLE_DEPRECATED {
                            unused_local_variable_arg = Some(item);
                        } else if name == *PROVIDER {
                            if !enable_provided_variables.is_enabled_for(fragment.name.value) {
                                return Err(vec![Diagnostic::error(
                                    format!("Invalid usage of provided variable: this feature is gated and currently set to {}",
                                                    enable_provided_variables),
                                     fragment.location.with_span(item.span),
                                )]);
                            }
                            provider_arg = Some(item);
                        } else {
                            extra_items.push(item);
                        }
                    }
                    // Check that no extraneous keys were supplied
                    if !extra_items.is_empty() {
                        return Err(extra_items
                            .iter()
                            .map(|item| {
                                Diagnostic::error(
                                    ValidationMessage::InvalidArgumentDefinitionsKey(
                                        item.name.value,
                                    ),
                                    fragment.location.with_span(item.span),
                                )
                            })
                            .collect());
                    }

                    // Convert variable type, validate that it's an input type
                    let type_ = get_argument_type(schema, fragment.location, type_arg, object)?;
                    if !type_.inner().is_input_type() {
                        return Err(Diagnostic::error(
                            ValidationMessage::ExpectedFragmentArgumentToHaveInputType(
                                schema.get_type_name(type_.inner()),
                            ),
                            fragment.location.with_span(variable_arg.value.span()),
                        )
                        .into());
                    }

                    let mut directives = Vec::new();
                        if let Some(unused_local_variable_arg) = unused_local_variable_arg {
                            if !matches!(
                                unused_local_variable_arg,
                                graphql_syntax::ConstantArgument {
                                    value: graphql_syntax::ConstantValue::Boolean(
                                        graphql_syntax::BooleanNode { value: true, .. }
                                    ),
                                    ..
                                }
                            ) {
                                return Err(vec![Diagnostic::error(
                                    ValidationMessage::InvalidUnusedFragmentVariableSuppressionArg,
                                    fragment
                                        .location
                                        .with_span(unused_local_variable_arg.value.span()),
                                )]);
                            }
                            directives.push(crate::Directive {
                                name: WithLocation::new(
                                    fragment.location.with_span(unused_local_variable_arg.span),
                                    *UNUSED_LOCAL_VARIABLE_DEPRECATED,
                                ),
                                arguments: Vec::new(),
                                data: None,
                            });
                        }
                        if let Some(provider_arg) = provider_arg {
                            if let graphql_syntax::ConstantValue::String(provider_module_name) = &provider_arg.value {
                                if let Some(default_arg_) = default_arg {
                                    return Err(vec![Diagnostic::error(
                                        ValidationMessage::ProvidedVariableIncompatibleWithDefaultValue{argument_name: variable_name.value},
                                        fragment
                                            .location
                                            .with_span(provider_arg.span),
                                    ).annotate("Default value declared here",
                                    fragment
                                    .location
                                    .with_span(default_arg_.span))]);
                                }
                                directives.push(crate::Directive {
                                    name: WithLocation::new(
                                        fragment.location.with_span(provider_arg.span),
                                        ProvidedVariableMetadata::directive_name(),
                                    ),
                                    arguments: Vec::new(),
                                    data: Some(Box::new(ProvidedVariableMetadata{module_name: provider_module_name.value})),
                                });

                            } else{
                                return Err(vec![Diagnostic::error(
                                    ValidationMessage::LiteralStringArgumentExpectedForDirective{arg_name: *PROVIDER, directive_name: *ARGUMENT_DEFINITION },
                                    fragment
                                        .location
                                        .with_span(provider_arg.value.span()),
                                )]);
                            }

                        }

                    let default_value =
                        get_default_value(schema, fragment.location, default_arg, &type_)?;

                    Ok(VariableDefinition {
                        name: variable_name
                            .name_with_location(fragment.location.source_location()),
                        type_,
                        directives,
                        default_value,
                    })
                } else {
                    Err(Diagnostic::error(
                        ValidationMessage::ExpectedArgumentDefinitionToBeObject(),
                        fragment.location.with_span(variable_arg.value.span()),
                    )
                    .into())
                }
            })
            .collect::<DiagnosticsResult<Vec<VariableDefinition>>>()?)
    } else {
        Ok(Default::default())
    }
}

fn get_argument_type(
    schema: &SDLSchema,
    location: Location,
    type_arg: Option<&graphql_syntax::ConstantArgument>,
    object: &graphql_syntax::List<graphql_syntax::ConstantArgument>,
) -> DiagnosticsResult<TypeReference> {
    let type_name_and_span = match type_arg {
        Some(graphql_syntax::ConstantArgument {
            value: graphql_syntax::ConstantValue::String(type_name_node),
            span,
            ..
        }) => Some((type_name_node.value, span)),
        Some(graphql_syntax::ConstantArgument {
            value: graphql_syntax::ConstantValue::Enum(type_name_node),
            span,
            ..
        }) => Some((type_name_node.value, span)),
        _ => None,
    };
    if let Some((type_name, &span)) = type_name_and_span {
        let type_ast = graphql_syntax::parse_type(type_name.lookup(), location.source_location())
            .map_err(|diagnostics| {
            diagnostics
                .into_iter()
                .map(|diagnostic| {
                    let message = diagnostic.message().to_string();
                    Diagnostic::error(
                        message,
                        // TODO: ideally, `parse_type()` would take in the offset
                        // location and report the error at the right location.
                        location.with_span(span),
                    )
                })
                .collect::<Vec<_>>()
        })?;
        let type_ = build_type_annotation(schema, &type_ast, location)?;
        Ok(type_)
    } else {
        Err(Diagnostic::error(
            ValidationMessage::ExpectedArgumentDefinitionLiteralType(),
            location.with_span(type_arg.map_or(object.span, |x| x.span)),
        )
        .into())
    }
}

fn get_default_value(
    schema: &SDLSchema,
    location: Location,
    default_arg: Option<&graphql_syntax::ConstantArgument>,
    type_: &TypeReference,
) -> DiagnosticsResult<Option<WithLocation<ConstantValue>>> {
    default_arg
        .map(|x| {
            let constant_value_span = x.value.span();
            build_constant_value(schema, &x.value, type_, location, ValidationLevel::Strict).map(
                |constant_value| {
                    WithLocation::from_span(
                        location.source_location(),
                        constant_value_span,
                        constant_value,
                    )
                },
            )
        })
        .transpose()
}
<commit_msg>use get_string_literal() in provider arg parsing<commit_after>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::associated_data_impl;
use crate::build::{
    build_constant_value, build_type_annotation, build_variable_definitions, ValidationLevel,
};
use crate::constants::ARGUMENT_DEFINITION;
use crate::errors::{ValidationMessage, ValidationMessageWithData};
use crate::ir::{ConstantValue, VariableDefinition};
use crate::GraphQLSuggestions;
use common::{Diagnostic, DiagnosticsResult, FeatureFlag, Location, WithLocation};
use errors::{par_try_map, try2};
use intern::string_key::{Intern, StringKey, StringKeyMap};
use lazy_static::lazy_static;
use schema::{SDLSchema, Schema, Type, TypeReference};
use std::collections::HashMap;

lazy_static! {
    static ref TYPE: StringKey = "type".intern();
    static ref DEFAULT_VALUE: StringKey = "defaultValue".intern();
    static ref PROVIDER: StringKey = "provider".intern();
    pub static ref UNUSED_LOCAL_VARIABLE_DEPRECATED: StringKey =
        "unusedLocalVariable_DEPRECATED".intern();
}

pub type FragmentSignatures = StringKeyMap<FragmentSignature>;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ProvidedVariableMetadata {
    pub module_name: StringKey,
}

associated_data_impl!(ProvidedVariableMetadata);

/// Describes the public API of a fragment, excluding its selections.
/// When translating ASTs to IR, fragment spread arguments must be
/// checked against the corresponding fragment definition. However,
/// this process can't use the IR fragment definition, since that
/// would depend on having checked its body! Since recursive fragments
/// are allowed, we break the cycle by first computing signatures
/// and using these to type check fragment spreads in selections.
#[derive(Debug, Eq, PartialEq)]
pub struct FragmentSignature {
    pub name: WithLocation<StringKey>,
    pub variable_definitions: Vec<VariableDefinition>,
    pub type_condition: Type,
}

pub fn build_signatures(
    schema: &SDLSchema,
    definitions: &[graphql_syntax::ExecutableDefinition],
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<FragmentSignatures> {
    let suggestions = GraphQLSuggestions::new(schema);
    let mut seen_signatures: StringKeyMap<FragmentSignature> =
        HashMap::with_capacity_and_hasher(definitions.len(), Default::default());
    let signatures = par_try_map(definitions, |definition| match definition {
        graphql_syntax::ExecutableDefinition::Fragment(fragment) => Ok(Some(
            build_fragment_signature(schema, fragment, &suggestions, enable_provided_variables)?,
        )),
        graphql_syntax::ExecutableDefinition::Operation(_) => Ok(None),
    })?;
    let mut errors = Vec::new();
    for signature in signatures.into_iter().flatten() {
        let previous_signature = seen_signatures.get(&signature.name.item);
        if let Some(previous_signature) = previous_signature {
            errors.push(
                Diagnostic::error(
                    ValidationMessage::DuplicateDefinition(signature.name.item),
                    previous_signature.name.location,
                )
                .annotate("also defined here", signature.name.location),
            );
            continue;
        }
        seen_signatures.insert(signature.name.item, signature);
    }
    if errors.is_empty() {
        Ok(seen_signatures)
    } else {
        Err(errors)
    }
}

fn build_fragment_signature(
    schema: &SDLSchema,
    fragment: &graphql_syntax::FragmentDefinition,
    suggestions: &GraphQLSuggestions<'_>,
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<FragmentSignature> {
    let type_name = fragment.type_condition.type_.value;
    let type_condition = match schema.get_type(type_name) {
        Some(type_condition) => match type_condition {
            Type::Interface(..) | Type::Object(..) | Type::Union(..) => Ok(type_condition),
            _ => Err(Diagnostic::error(
                ValidationMessage::ExpectedCompositeType(type_condition),
                fragment
                    .location
                    .with_span(fragment.type_condition.type_.span),
            )
            .into()),
        },
        None => Err(Diagnostic::error_with_data(
            ValidationMessageWithData::UnknownType {
                type_name,
                suggestions: suggestions.composite_type_suggestions(type_name),
            },
            fragment
                .location
                .with_span(fragment.type_condition.type_.span),
        )
        .into()),
    };
    let argument_definition_directives = fragment
        .directives
        .iter()
        .filter(|x| x.name.value == *ARGUMENT_DEFINITION)
        .collect::<Vec<_>>();
    if fragment.variable_definitions.is_some() && !argument_definition_directives.is_empty() {
        return Err(Diagnostic::error(
            ValidationMessage::VariableDefinitionsAndArgumentDirective(),
            fragment
                .location
                .with_span(argument_definition_directives[0].span),
        )
        .annotate(
            "variables are previously defined here",
            fragment.location.with_span(
                fragment
                    .variable_definitions
                    .as_ref()
                    .map(|list| list.span)
                    .unwrap(),
            ),
        )
        .into());
    } else if argument_definition_directives.len() > 1 {
        return Err(Diagnostic::error(
            ValidationMessage::ExpectedOneArgumentDefinitionsDirective(),
            fragment
                .location
                .with_span(argument_definition_directives[1].span),
        )
        .into());
    }
    let variable_definitions = fragment
        .variable_definitions
        .as_ref()
        .map(|variable_definitions| {
            build_variable_definitions(schema, &variable_definitions.items, fragment.location)
        })
        .or_else(|| {
            argument_definition_directives.get(0).map(|x| {
                build_fragment_variable_definitions(schema, fragment, x, enable_provided_variables)
            })
        })
        .unwrap_or_else(|| Ok(Default::default()));

    let (type_condition, variable_definitions) = try2(type_condition, variable_definitions)?;
    Ok(FragmentSignature {
        name: fragment
            .name
            .name_with_location(fragment.location.source_location()),
        type_condition,
        variable_definitions,
    })
}

fn build_fragment_variable_definitions(
    schema: &SDLSchema,
    fragment: &graphql_syntax::FragmentDefinition,
    directive: &graphql_syntax::Directive,
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<Vec<VariableDefinition>> {
    if let Some(arguments) = &directive.arguments {
        Ok(arguments
            .items
            .iter()
            .map(|variable_arg| {
                if let graphql_syntax::Value::Constant(graphql_syntax::ConstantValue::Object(
                    object,
                )) = &variable_arg.value
                {
                    let variable_name = &variable_arg.name;
                    let mut extra_items = Vec::new();
                    let mut type_arg = None;
                    let mut default_arg = None;
                    let mut unused_local_variable_arg = None;
                    let mut provider_arg = None;
                    for item in &object.items {
                        let name = item.name.value;
                        if name == *TYPE {
                            type_arg = Some(item);
                        } else if name == *DEFAULT_VALUE {
                            default_arg = Some(item);
                        } else if name == *UNUSED_LOCAL_VARIABLE_DEPRECATED {
                            unused_local_variable_arg = Some(item);
                        } else if name == *PROVIDER {
                            if !enable_provided_variables.is_enabled_for(fragment.name.value) {
                                return Err(vec![Diagnostic::error(
                                    format!("Invalid usage of provided variable: this feature is gated and currently set to {}",
                                    enable_provided_variables),
                                    fragment.location.with_span(item.span),
                                )]);
                            }
                            provider_arg = Some(item);
                        } else {
                            extra_items.push(item);
                        }
                    }
                    // Check that no extraneous keys were supplied
                    if !extra_items.is_empty() {
                        return Err(extra_items
                            .iter()
                            .map(|item| {
                                Diagnostic::error(
                                    ValidationMessage::InvalidArgumentDefinitionsKey(
                                        item.name.value,
                                    ),
                                    fragment.location.with_span(item.span),
                                )
                            })
                            .collect());
                    }

                    // Convert variable type, validate that it's an input type
                    let type_ = get_argument_type(schema, fragment.location, type_arg, object)?;
                    if !type_.inner().is_input_type() {
                        return Err(Diagnostic::error(
                            ValidationMessage::ExpectedFragmentArgumentToHaveInputType(
                                schema.get_type_name(type_.inner()),
                            ),
                            fragment.location.with_span(variable_arg.value.span()),
                        )
                        .into());
                    }

                    let mut directives = Vec::new();
                    if let Some(unused_local_variable_arg) = unused_local_variable_arg {
                        if !matches!(
                            unused_local_variable_arg,
                            graphql_syntax::ConstantArgument {
                                value: graphql_syntax::ConstantValue::Boolean(
                                    graphql_syntax::BooleanNode { value: true, .. }
                                ),
                                ..
                            }
                        ) {
                            return Err(vec![Diagnostic::error(
                                ValidationMessage::InvalidUnusedFragmentVariableSuppressionArg,
                                fragment
                                    .location
                                    .with_span(unused_local_variable_arg.value.span()),
                            )]);
                        }
                        directives.push(crate::Directive {
                            name: WithLocation::new(
                                fragment.location.with_span(unused_local_variable_arg.span),
                                *UNUSED_LOCAL_VARIABLE_DEPRECATED,
                            ),
                            arguments: Vec::new(),
                            data: None,
                        });
                    }
                    if let Some(provider_arg) = provider_arg {
                        let provider_module_name = provider_arg.value.get_string_literal().ok_or_else(|| {
                            vec![Diagnostic::error(
                                ValidationMessage::LiteralStringArgumentExpectedForDirective{arg_name: *PROVIDER, directive_name: *ARGUMENT_DEFINITION },
                                fragment
                                    .location
                                    .with_span(provider_arg.value.span()),
                            )]
                        })?;
                        if let Some(default_arg_) = default_arg {
                            return Err(vec![Diagnostic::error(
                                ValidationMessage::ProvidedVariableIncompatibleWithDefaultValue{argument_name: variable_name.value},
                                fragment
                                    .location
                                    .with_span(provider_arg.span),
                            ).annotate("Default value declared here",
                            fragment
                            .location
                            .with_span(default_arg_.span))]);
                        }
                        directives.push(crate::Directive {
                            name: WithLocation::new(
                                fragment.location.with_span(provider_arg.span),
                                ProvidedVariableMetadata::directive_name(),
                            ),
                            arguments: Vec::new(),
                            data: Some(Box::new(ProvidedVariableMetadata{module_name: provider_module_name})),
                        });
                    }

                    let default_value =
                        get_default_value(schema, fragment.location, default_arg, &type_)?;

                    Ok(VariableDefinition {
                        name: variable_name
                            .name_with_location(fragment.location.source_location()),
                        type_,
                        directives,
                        default_value,
                    })
                } else {
                    Err(Diagnostic::error(
                        ValidationMessage::ExpectedArgumentDefinitionToBeObject(),
                        fragment.location.with_span(variable_arg.value.span()),
                    )
                    .into())
                }
            })
            .collect::<DiagnosticsResult<Vec<VariableDefinition>>>()?)
    } else {
        Ok(Default::default())
    }
}

fn get_argument_type(
    schema: &SDLSchema,
    location: Location,
    type_arg: Option<&graphql_syntax::ConstantArgument>,
    object: &graphql_syntax::List<graphql_syntax::ConstantArgument>,
) -> DiagnosticsResult<TypeReference> {
    let type_name_and_span = match type_arg {
        Some(graphql_syntax::ConstantArgument {
            value: graphql_syntax::ConstantValue::String(type_name_node),
            span,
            ..
        }) => Some((type_name_node.value, span)),
        Some(graphql_syntax::ConstantArgument {
            value: graphql_syntax::ConstantValue::Enum(type_name_node),
            span,
            ..
        }) => Some((type_name_node.value, span)),
        _ => None,
    };
    if let Some((type_name, &span)) = type_name_and_span {
        let type_ast = graphql_syntax::parse_type(type_name.lookup(), location.source_location())
            .map_err(|diagnostics| {
            diagnostics
                .into_iter()
                .map(|diagnostic| {
                    let message = diagnostic.message().to_string();
                    Diagnostic::error(
                        message,
                        // TODO: ideally, `parse_type()` would take in the offset
                        // location and report the error at the right location.
                        location.with_span(span),
                    )
                })
                .collect::<Vec<_>>()
        })?;
        let type_ = build_type_annotation(schema, &type_ast, location)?;
        Ok(type_)
    } else {
        Err(Diagnostic::error(
            ValidationMessage::ExpectedArgumentDefinitionLiteralType(),
            location.with_span(type_arg.map_or(object.span, |x| x.span)),
        )
        .into())
    }
}

fn get_default_value(
    schema: &SDLSchema,
    location: Location,
    default_arg: Option<&graphql_syntax::ConstantArgument>,
    type_: &TypeReference,
) -> DiagnosticsResult<Option<WithLocation<ConstantValue>>> {
    default_arg
        .map(|x| {
            let constant_value_span = x.value.span();
            build_constant_value(schema, &x.value, type_, location, ValidationLevel::Strict).map(
                |constant_value| {
                    WithLocation::from_span(
                        location.source_location(),
                        constant_value_span,
                        constant_value,
                    )
                },
            )
        })
        .transpose()
}
<|endoftext|>


<commit_before>//! Code common to structs, unions, and enum variants.

use crate::render::RenderContext;
use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay};
use ide_db::SnippetCap;
use itertools::Itertools;

/// A rendered struct, union, or enum variant, split into fields for actual
/// auto-completion (`literal`, using `field: ()`) and display in the
/// completions menu (`detail`, using `field: type`).
pub(crate) struct RenderedCompound {
    pub literal: String,
    pub detail: String,
}

/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_record(
    db: &dyn HirDatabase,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    name: Option<&str>,
) -> RenderedCompound {
    let fields = fields.iter();

    let (completions, types): (Vec<_>, Vec<_>) = fields
        .enumerate()
        .map(|(idx, field)| {
            (
                if snippet_cap.is_some() {
                    format!("{}: ${{{}:()}}", field.name(db), idx + 1)
                } else {
                    format!("{}: ()", field.name(db))
                },
                format!("{}: {}", field.name(db), field.ty(db).display(db)),
            )
        })
        .unzip();
    RenderedCompound {
        literal: format!("{} {{ {} }}", name.unwrap_or(""), completions.iter().format(", ")),
        detail: format!("{} {{ {} }}", name.unwrap_or(""), types.iter().format(", ")),
    }
}

/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_tuple(
    db: &dyn HirDatabase,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    name: Option<&str>,
) -> RenderedCompound {
    let fields = fields.iter();

    let (completions, types): (Vec<_>, Vec<_>) = fields
        .enumerate()
        .map(|(idx, field)| {
            (
                if snippet_cap.is_some() {
                    format!("${{{}:()}}", (idx + 1).to_string())
                } else {
                    "()".to_string()
                },
                field.ty(db).display(db).to_string(),
            )
        })
        .unzip();
    RenderedCompound {
        literal: format!("{}({})", name.unwrap_or(""), completions.iter().format(", ")),
        detail: format!("{}({})", name.unwrap_or(""), types.iter().format(", ")),
    }
}

/// Find all the visible fields in a `HasAttrs`. Returns the list of visible
/// fields, plus a boolean for whether the list is comprehensive (contains no
/// private fields and is not marked `#[non_exhaustive]`).
pub(crate) fn visible_fields(
    ctx: &RenderContext<'_>,
    fields: &[hir::Field],
    item: impl HasAttrs,
) -> Option<(Vec<hir::Field>, bool)> {
    let module = ctx.completion.module?;
    let n_fields = fields.len();
    let fields = fields
        .iter()
        .filter(|field| field.is_visible_from(ctx.db(), module))
        .copied()
        .collect::<Vec<_>>();

    let fields_omitted =
        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
    Some((fields, fields_omitted))
}
<commit_msg>visibility tweak for CI<commit_after>//! Code common to structs, unions, and enum variants.

use crate::render::RenderContext;
use hir::{db::HirDatabase, HasAttrs, HasVisibility, HirDisplay};
use ide_db::SnippetCap;
use itertools::Itertools;

/// A rendered struct, union, or enum variant, split into fields for actual
/// auto-completion (`literal`, using `field: ()`) and display in the
/// completions menu (`detail`, using `field: type`).
pub(crate) struct RenderedCompound {
    pub(crate) literal: String,
    pub(crate) detail: String,
}

/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_record(
    db: &dyn HirDatabase,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    name: Option<&str>,
) -> RenderedCompound {
    let fields = fields.iter();

    let (completions, types): (Vec<_>, Vec<_>) = fields
        .enumerate()
        .map(|(idx, field)| {
            (
                if snippet_cap.is_some() {
                    format!("{}: ${{{}:()}}", field.name(db), idx + 1)
                } else {
                    format!("{}: ()", field.name(db))
                },
                format!("{}: {}", field.name(db), field.ty(db).display(db)),
            )
        })
        .unzip();
    RenderedCompound {
        literal: format!("{} {{ {} }}", name.unwrap_or(""), completions.iter().format(", ")),
        detail: format!("{} {{ {} }}", name.unwrap_or(""), types.iter().format(", ")),
    }
}

/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
/// the `name` argument for an anonymous type.
pub(crate) fn render_tuple(
    db: &dyn HirDatabase,
    snippet_cap: Option<SnippetCap>,
    fields: &[hir::Field],
    name: Option<&str>,
) -> RenderedCompound {
    let fields = fields.iter();

    let (completions, types): (Vec<_>, Vec<_>) = fields
        .enumerate()
        .map(|(idx, field)| {
            (
                if snippet_cap.is_some() {
                    format!("${{{}:()}}", (idx + 1).to_string())
                } else {
                    "()".to_string()
                },
                field.ty(db).display(db).to_string(),
            )
        })
        .unzip();
    RenderedCompound {
        literal: format!("{}({})", name.unwrap_or(""), completions.iter().format(", ")),
        detail: format!("{}({})", name.unwrap_or(""), types.iter().format(", ")),
    }
}

/// Find all the visible fields in a `HasAttrs`. Returns the list of visible
/// fields, plus a boolean for whether the list is comprehensive (contains no
/// private fields and is not marked `#[non_exhaustive]`).
pub(crate) fn visible_fields(
    ctx: &RenderContext<'_>,
    fields: &[hir::Field],
    item: impl HasAttrs,
) -> Option<(Vec<hir::Field>, bool)> {
    let module = ctx.completion.module?;
    let n_fields = fields.len();
    let fields = fields
        .iter()
        .filter(|field| field.is_visible_from(ctx.db(), module))
        .copied()
        .collect::<Vec<_>>();

    let fields_omitted =
        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
    Some((fields, fields_omitted))
}
<|endoftext|>


<commit_before>use crate::ast::Field;
use crate::attr::Display;
use proc_macro2::TokenTree;
use quote::{format_ident, quote_spanned};
use std::collections::HashSet as Set;
use syn::ext::IdentExt;
use syn::parse::{ParseStream, Parser};
use syn::{parse_str, Ident, Index, LitStr, Member, Result, Token};

impl Display<'_> {
    // Transform `"error {var}"` to `"error {}", var`.
    pub fn expand_shorthand(&mut self, fields: &[Field]) {
        let raw_args = self.args.clone();
        let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
        let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();

        let span = self.fmt.span();
        let fmt = self.fmt.value();
        let mut read = fmt.as_str();
        let mut out = String::new();
        let mut args = self.args.clone();
        let mut has_bonus_display = false;

        let mut has_trailing_comma = false;
        if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
            if punct.as_char() == ',' {
                has_trailing_comma = true;
            }
        }

        while let Some(brace) = read.find('{') {
            out += &read[..brace + 1];
            read = &read[brace + 1..];
            if read.starts_with('{') {
                out.push('{');
                read = &read[1..];
                continue;
            }
            let next = match read.chars().next() {
                Some(next) => next,
                None => return,
            };
            let member = match next {
                '0'..='9' => {
                    let int = take_int(&mut read);
                    let member = match int.parse::<u32>() {
                        Ok(index) => Member::Unnamed(Index { index, span }),
                        Err(_) => return,
                    };
                    if !fields.contains(&member) {
                        out += ∫
                        continue;
                    }
                    member
                }
                'a'..='z' | 'A'..='Z' | '_' => {
                    let ident = take_ident(&mut read);
                    let mut ident = parse_str::<Ident>(&ident).unwrap();
                    ident.set_span(span);
                    Member::Named(ident)
                }
                _ => continue,
            };
            let local = match &member {
                Member::Unnamed(index) => format_ident!("_{}", index),
                Member::Named(ident) => ident.clone(),
            };
            let mut formatvar = local.clone();
            if formatvar.to_string().starts_with("r#") {
                // Replace the "r#" prefix.
                formatvar = format_ident!("raw_field_{}", formatvar);
            }
            if formatvar.to_string().starts_with('_') {
                // Work around leading underscore being rejected by 1.40 and
                // older compilers. https://github.com/rust-lang/rust/pull/66847
                formatvar = format_ident!("field_{}", formatvar);
            }
            out += &formatvar.to_string();
            if !named_args.insert(formatvar.clone()) {
                // Already specified in the format argument list.
                continue;
            }
            if !has_trailing_comma {
                args.extend(quote_spanned!(span=> ,));
            }
            args.extend(quote_spanned!(span=> #formatvar = #local));
            if read.starts_with('}') && fields.contains(&member) {
                has_bonus_display = true;
                args.extend(quote_spanned!(span=> .as_display()));
            }
            has_trailing_comma = false;
        }

        out += read;
        self.fmt = LitStr::new(&out, self.fmt.span());
        self.args = args;
        self.has_bonus_display = has_bonus_display;
    }
}

fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
    let mut named_args = Set::new();

    while !input.is_empty() {
        if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
            input.parse::<Token![,]>()?;
            let ident: Ident = input.parse()?;
            input.parse::<Token![=]>()?;
            named_args.insert(ident);
        } else {
            input.parse::<TokenTree>()?;
        }
    }

    Ok(named_args)
}

fn take_int(read: &mut &str) -> String {
    let mut int = String::new();
    for (i, ch) in read.char_indices() {
        match ch {
            '0'..='9' => int.push(ch),
            _ => {
                *read = &read[i..];
                break;
            }
        }
    }
    int
}

fn take_ident(read: &mut &str) -> String {
    let mut ident = String::new();
    if let Some(rest) = read.strip_prefix("r#") {
        ident.push_str("r#");
        *read = rest;
    }
    for (i, ch) in read.char_indices() {
        match ch {
            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
            _ => {
                *read = &read[i..];
                break;
            }
        }
    }
    ident
}
<commit_msg>Replace use of str::strip_prefix<commit_after>use crate::ast::Field;
use crate::attr::Display;
use proc_macro2::TokenTree;
use quote::{format_ident, quote_spanned};
use std::collections::HashSet as Set;
use syn::ext::IdentExt;
use syn::parse::{ParseStream, Parser};
use syn::{parse_str, Ident, Index, LitStr, Member, Result, Token};

impl Display<'_> {
    // Transform `"error {var}"` to `"error {}", var`.
    pub fn expand_shorthand(&mut self, fields: &[Field]) {
        let raw_args = self.args.clone();
        let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
        let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();

        let span = self.fmt.span();
        let fmt = self.fmt.value();
        let mut read = fmt.as_str();
        let mut out = String::new();
        let mut args = self.args.clone();
        let mut has_bonus_display = false;

        let mut has_trailing_comma = false;
        if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
            if punct.as_char() == ',' {
                has_trailing_comma = true;
            }
        }

        while let Some(brace) = read.find('{') {
            out += &read[..brace + 1];
            read = &read[brace + 1..];
            if read.starts_with('{') {
                out.push('{');
                read = &read[1..];
                continue;
            }
            let next = match read.chars().next() {
                Some(next) => next,
                None => return,
            };
            let member = match next {
                '0'..='9' => {
                    let int = take_int(&mut read);
                    let member = match int.parse::<u32>() {
                        Ok(index) => Member::Unnamed(Index { index, span }),
                        Err(_) => return,
                    };
                    if !fields.contains(&member) {
                        out += ∫
                        continue;
                    }
                    member
                }
                'a'..='z' | 'A'..='Z' | '_' => {
                    let ident = take_ident(&mut read);
                    let mut ident = parse_str::<Ident>(&ident).unwrap();
                    ident.set_span(span);
                    Member::Named(ident)
                }
                _ => continue,
            };
            let local = match &member {
                Member::Unnamed(index) => format_ident!("_{}", index),
                Member::Named(ident) => ident.clone(),
            };
            let mut formatvar = local.clone();
            if formatvar.to_string().starts_with("r#") {
                // Replace the "r#" prefix.
                formatvar = format_ident!("raw_field_{}", formatvar);
            }
            if formatvar.to_string().starts_with('_') {
                // Work around leading underscore being rejected by 1.40 and
                // older compilers. https://github.com/rust-lang/rust/pull/66847
                formatvar = format_ident!("field_{}", formatvar);
            }
            out += &formatvar.to_string();
            if !named_args.insert(formatvar.clone()) {
                // Already specified in the format argument list.
                continue;
            }
            if !has_trailing_comma {
                args.extend(quote_spanned!(span=> ,));
            }
            args.extend(quote_spanned!(span=> #formatvar = #local));
            if read.starts_with('}') && fields.contains(&member) {
                has_bonus_display = true;
                args.extend(quote_spanned!(span=> .as_display()));
            }
            has_trailing_comma = false;
        }

        out += read;
        self.fmt = LitStr::new(&out, self.fmt.span());
        self.args = args;
        self.has_bonus_display = has_bonus_display;
    }
}

fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
    let mut named_args = Set::new();

    while !input.is_empty() {
        if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
            input.parse::<Token![,]>()?;
            let ident: Ident = input.parse()?;
            input.parse::<Token![=]>()?;
            named_args.insert(ident);
        } else {
            input.parse::<TokenTree>()?;
        }
    }

    Ok(named_args)
}

fn take_int(read: &mut &str) -> String {
    let mut int = String::new();
    for (i, ch) in read.char_indices() {
        match ch {
            '0'..='9' => int.push(ch),
            _ => {
                *read = &read[i..];
                break;
            }
        }
    }
    int
}

fn take_ident(read: &mut &str) -> String {
    let mut ident = String::new();
    if read.starts_with("r#") {
        ident.push_str("r#");
        *read = &read[2..];
    }
    for (i, ch) in read.char_indices() {
        match ch {
            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
            _ => {
                *read = &read[i..];
                break;
            }
        }
    }
    ident
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The restyle damage is a hint that tells layout which kind of operations may
//! be needed in presence of incremental style changes.

#![deny(missing_docs)]

use computed_values::display;
use matching::{StyleChange, StyleDifference};
use properties::ComputedValues;
use std::fmt;

bitflags! {
    /// Individual layout actions that may be necessary after restyling.
    pub struct ServoRestyleDamage: u8 {
        /// Repaint the node itself.
        ///
        /// Currently unused; need to decide how this propagates.
        const REPAINT = 0x01;

        /// The stacking-context-relative position of this node or its
        /// descendants has changed.
        ///
        /// Propagates both up and down the flow tree.
        const REPOSITION = 0x02;

        /// Recompute the overflow regions (bounding box of object and all descendants).
        ///
        /// Propagates down the flow tree because the computation is bottom-up.
        const STORE_OVERFLOW = 0x04;

        /// Recompute intrinsic inline_sizes (minimum and preferred).
        ///
        /// Propagates down the flow tree because the computation is.
        /// bottom-up.
        const BUBBLE_ISIZES = 0x08;

        /// Recompute actual inline-sizes and block-sizes, only taking
        /// out-of-flow children into account.
        ///
        /// Propagates up the flow tree because the computation is top-down.
        const REFLOW_OUT_OF_FLOW = 0x10;

        /// Recompute actual inline_sizes and block_sizes.
        ///
        /// Propagates up the flow tree because the computation is top-down.
        const REFLOW = 0x20;

        /// Re-resolve generated content.
        ///
        /// Propagates up the flow tree because the computation is inorder.
        const RESOLVE_GENERATED_CONTENT = 0x40;

        /// The entire flow needs to be reconstructed.
        const RECONSTRUCT_FLOW = 0x80;
    }
}

malloc_size_of_is_0!(ServoRestyleDamage);

impl ServoRestyleDamage {
    /// Compute the `StyleDifference` (including the appropriate restyle damage)
    /// for a given style change between `old` and `new`.
    pub fn compute_style_difference(
        old: &ComputedValues,
        new: &ComputedValues,
    ) -> StyleDifference {
        let damage = compute_damage(old, new);
        let change = if damage.is_empty() {
            StyleChange::Unchanged
        } else {
            // FIXME(emilio): Differentiate between reset and inherited
            // properties here, and set `reset_only` appropriately so the
            // optimization to skip the cascade in those cases applies.
            StyleChange::Changed { reset_only: false }
        };
        StyleDifference::new(damage, change)
    }

    /// Returns a bitmask that represents a flow that needs to be rebuilt and
    /// reflowed.
    ///
    /// FIXME(bholley): Do we ever actually need this? Shouldn't
    /// RECONSTRUCT_FLOW imply everything else?
    pub fn rebuild_and_reflow() -> ServoRestyleDamage {
        ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
        ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::BUBBLE_ISIZES |
        ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW |
        ServoRestyleDamage::RECONSTRUCT_FLOW
    }

    /// Returns a bitmask indicating that the frame needs to be reconstructed.
    pub fn reconstruct() -> ServoRestyleDamage {
        ServoRestyleDamage::RECONSTRUCT_FLOW
    }

    /// Supposing a flow has the given `position` property and this damage,
    /// returns the damage that we should add to the *parent* of this flow.
    pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage {
        if child_is_absolutely_positioned {
            self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
                    ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
                    ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
        } else {
            self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
                    ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW |
                    ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
        }
    }

    /// Supposing the *parent* of a flow with the given `position` property has
    /// this damage, returns the damage that we should add to this flow.
    pub fn damage_for_child(self,
                            parent_is_absolutely_positioned: bool,
                            child_is_absolutely_positioned: bool)
                            -> ServoRestyleDamage {
        match (parent_is_absolutely_positioned, child_is_absolutely_positioned) {
            (false, true) => {
                // Absolute children are out-of-flow and therefore insulated from changes.
                //
                // FIXME(pcwalton): Au contraire, if the containing block dimensions change!
                self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION)
            }
            (true, false) => {
                // Changing the position of an absolutely-positioned block requires us to reflow
                // its kids.
                if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) {
                    self | ServoRestyleDamage::REFLOW
                } else {
                    self
                }
            }
            _ => {
                // TODO(pcwalton): Take floatedness into account.
                self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::REFLOW)
            }
        }
    }
}

impl Default for ServoRestyleDamage {
    fn default() -> Self {
        Self::empty()
    }
}

impl fmt::Display for ServoRestyleDamage {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let mut first_elem = true;

        let to_iter =
            [ (ServoRestyleDamage::REPAINT, "Repaint")
            , (ServoRestyleDamage::REPOSITION, "Reposition")
            , (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow")
            , (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes")
            , (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow")
            , (ServoRestyleDamage::REFLOW, "Reflow")
            , (ServoRestyleDamage::RESOLVE_GENERATED_CONTENT, "ResolveGeneratedContent")
            , (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow")
            ];

        for &(damage, damage_str) in &to_iter {
            if self.contains(damage) {
                if !first_elem { write!(f, " | ")?; }
                write!(f, "{}", damage_str)?;
                first_elem = false;
            }
        }

        if first_elem {
            write!(f, "NoDamage")?;
        }

        Ok(())
    }
}

// NB: We need the braces inside the RHS due to Rust #8012.  This particular
// version of this macro might be safe anyway, but we want to avoid silent
// breakage on modifications.
macro_rules! add_if_not_equal(
    ($old:ident, $new:ident, $damage:ident,
     [ $($effect:path),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
        if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
            $damage.insert($($effect)|*);
            true
        } else {
            false
        }
    })
);

fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage {
    let mut damage = ServoRestyleDamage::empty();

    // This should check every CSS property, as enumerated in the fields of
    // http://doc.servo.org/style/properties/struct.ComputedValues.html

    // FIXME: Test somehow that every property is included.

    add_if_not_equal!(old, new, damage,
                      [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                      ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                      ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW,
                      ServoRestyleDamage::RECONSTRUCT_FLOW], [
        get_box.clear, get_box.float, get_box.display, get_box.position, get_counters.content,
        get_counters.counter_reset, get_counters.counter_increment,
        get_list.quotes, get_list.list_style_type,

        // If these text or font properties change, we need to reconstruct the flow so that
        // text shaping is re-run.
        get_inheritedtext.letter_spacing, get_inheritedtext.text_rendering,
        get_inheritedtext.text_transform, get_inheritedtext.word_spacing,
        get_inheritedtext.overflow_wrap, get_inheritedtext.text_justify,
        get_inheritedtext.white_space, get_inheritedtext.word_break, get_text.text_overflow,
        get_font.font_family, get_font.font_style, get_font.font_variant_caps, get_font.font_weight,
        get_font.font_size, get_font.font_stretch,
        get_inheritedbox.direction, get_inheritedbox.writing_mode,
        get_text.text_decoration_line, get_text.unicode_bidi,
        get_inheritedtable.empty_cells, get_inheritedtable.caption_side,
        get_column.column_width, get_column.column_count
    ]) || (new.get_box().display == display::T::inline &&
           add_if_not_equal!(old, new, damage,
                             [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                             ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                             ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW,
                             ServoRestyleDamage::RECONSTRUCT_FLOW], [
        // For inline boxes only, border/padding styles are used in flow construction (to decide
        // whether to create fragments for empty flows).
        get_border.border_top_width, get_border.border_right_width,
        get_border.border_bottom_width, get_border.border_left_width,
        get_padding.padding_top, get_padding.padding_right,
        get_padding.padding_bottom, get_padding.padding_left
    ])) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                            ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                            ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW],
        [get_border.border_top_width, get_border.border_right_width,
        get_border.border_bottom_width, get_border.border_left_width,
        get_margin.margin_top, get_margin.margin_right,
        get_margin.margin_bottom, get_margin.margin_left,
        get_padding.padding_top, get_padding.padding_right,
        get_padding.padding_bottom, get_padding.padding_left,
        get_position.width, get_position.height,
        get_inheritedtext.line_height,
        get_inheritedtext.text_align, get_inheritedtext.text_indent,
        get_table.table_layout,
        get_inheritedtable.border_collapse,
        get_inheritedtable.border_spacing,
        get_column.column_gap,
        get_position.flex_direction,
        get_position.flex_wrap,
        get_position.justify_content,
        get_position.align_items,
        get_position.align_content,
        get_position.order,
        get_position.flex_basis,
        get_position.flex_grow,
        get_position.flex_shrink,
        get_position.align_self
    ]) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                            ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::REFLOW_OUT_OF_FLOW],
        [get_position.top, get_position.left,
        get_position.right, get_position.bottom,
        get_effects.opacity,
        get_box.transform, get_box.transform_style, get_box.transform_origin,
        get_box.perspective, get_box.perspective_origin
    ]) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT], [
        get_color.color, get_background.background_color,
        get_background.background_image, get_background.background_position_x,
        get_background.background_position_y, get_background.background_repeat,
        get_background.background_attachment, get_background.background_clip,
        get_background.background_origin, get_background.background_size,
        get_border.border_top_color, get_border.border_right_color,
        get_border.border_bottom_color, get_border.border_left_color,
        get_border.border_top_style, get_border.border_right_style,
        get_border.border_bottom_style, get_border.border_left_style,
        get_border.border_top_left_radius, get_border.border_top_right_radius,
        get_border.border_bottom_left_radius, get_border.border_bottom_right_radius,
        get_position.z_index, get_box._servo_overflow_clip_box,
        get_inheritedtext._servo_text_decorations_in_effect,
        get_pointing.cursor, get_pointing.pointer_events,
        get_effects.box_shadow, get_effects.clip, get_inheritedtext.text_shadow, get_effects.filter,
        get_effects.mix_blend_mode, get_inheritedbox.image_rendering,

        // Note: May require REFLOW et al. if `visibility: collapse` is implemented.
        get_inheritedbox.visibility
    ]);

    // Paint worklets may depend on custom properties,
    // so if they have changed we should repaint.
    if old.custom_properties() != new.custom_properties() {
        damage.insert(ServoRestyleDamage::REPAINT);
    }

    // If the layer requirements of this flow have changed due to the value
    // of the transform, then reflow is required to rebuild the layers.
    if old.transform_requires_layer() != new.transform_requires_layer() {
        damage.insert(ServoRestyleDamage::rebuild_and_reflow());
    }

    damage
}
<commit_msg>style: Remove redundant deny().<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! The restyle damage is a hint that tells layout which kind of operations may
//! be needed in presence of incremental style changes.

use computed_values::display;
use matching::{StyleChange, StyleDifference};
use properties::ComputedValues;
use std::fmt;

bitflags! {
    /// Individual layout actions that may be necessary after restyling.
    pub struct ServoRestyleDamage: u8 {
        /// Repaint the node itself.
        ///
        /// Currently unused; need to decide how this propagates.
        const REPAINT = 0x01;

        /// The stacking-context-relative position of this node or its
        /// descendants has changed.
        ///
        /// Propagates both up and down the flow tree.
        const REPOSITION = 0x02;

        /// Recompute the overflow regions (bounding box of object and all descendants).
        ///
        /// Propagates down the flow tree because the computation is bottom-up.
        const STORE_OVERFLOW = 0x04;

        /// Recompute intrinsic inline_sizes (minimum and preferred).
        ///
        /// Propagates down the flow tree because the computation is.
        /// bottom-up.
        const BUBBLE_ISIZES = 0x08;

        /// Recompute actual inline-sizes and block-sizes, only taking
        /// out-of-flow children into account.
        ///
        /// Propagates up the flow tree because the computation is top-down.
        const REFLOW_OUT_OF_FLOW = 0x10;

        /// Recompute actual inline_sizes and block_sizes.
        ///
        /// Propagates up the flow tree because the computation is top-down.
        const REFLOW = 0x20;

        /// Re-resolve generated content.
        ///
        /// Propagates up the flow tree because the computation is inorder.
        const RESOLVE_GENERATED_CONTENT = 0x40;

        /// The entire flow needs to be reconstructed.
        const RECONSTRUCT_FLOW = 0x80;
    }
}

malloc_size_of_is_0!(ServoRestyleDamage);

impl ServoRestyleDamage {
    /// Compute the `StyleDifference` (including the appropriate restyle damage)
    /// for a given style change between `old` and `new`.
    pub fn compute_style_difference(
        old: &ComputedValues,
        new: &ComputedValues,
    ) -> StyleDifference {
        let damage = compute_damage(old, new);
        let change = if damage.is_empty() {
            StyleChange::Unchanged
        } else {
            // FIXME(emilio): Differentiate between reset and inherited
            // properties here, and set `reset_only` appropriately so the
            // optimization to skip the cascade in those cases applies.
            StyleChange::Changed { reset_only: false }
        };
        StyleDifference::new(damage, change)
    }

    /// Returns a bitmask that represents a flow that needs to be rebuilt and
    /// reflowed.
    ///
    /// FIXME(bholley): Do we ever actually need this? Shouldn't
    /// RECONSTRUCT_FLOW imply everything else?
    pub fn rebuild_and_reflow() -> ServoRestyleDamage {
        ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
        ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::BUBBLE_ISIZES |
        ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW |
        ServoRestyleDamage::RECONSTRUCT_FLOW
    }

    /// Returns a bitmask indicating that the frame needs to be reconstructed.
    pub fn reconstruct() -> ServoRestyleDamage {
        ServoRestyleDamage::RECONSTRUCT_FLOW
    }

    /// Supposing a flow has the given `position` property and this damage,
    /// returns the damage that we should add to the *parent* of this flow.
    pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage {
        if child_is_absolutely_positioned {
            self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
                    ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
                    ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
        } else {
            self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION |
                    ServoRestyleDamage::STORE_OVERFLOW | ServoRestyleDamage::REFLOW |
                    ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::RESOLVE_GENERATED_CONTENT)
        }
    }

    /// Supposing the *parent* of a flow with the given `position` property has
    /// this damage, returns the damage that we should add to this flow.
    pub fn damage_for_child(self,
                            parent_is_absolutely_positioned: bool,
                            child_is_absolutely_positioned: bool)
                            -> ServoRestyleDamage {
        match (parent_is_absolutely_positioned, child_is_absolutely_positioned) {
            (false, true) => {
                // Absolute children are out-of-flow and therefore insulated from changes.
                //
                // FIXME(pcwalton): Au contraire, if the containing block dimensions change!
                self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION)
            }
            (true, false) => {
                // Changing the position of an absolutely-positioned block requires us to reflow
                // its kids.
                if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) {
                    self | ServoRestyleDamage::REFLOW
                } else {
                    self
                }
            }
            _ => {
                // TODO(pcwalton): Take floatedness into account.
                self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION | ServoRestyleDamage::REFLOW)
            }
        }
    }
}

impl Default for ServoRestyleDamage {
    fn default() -> Self {
        Self::empty()
    }
}

impl fmt::Display for ServoRestyleDamage {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let mut first_elem = true;

        let to_iter =
            [ (ServoRestyleDamage::REPAINT, "Repaint")
            , (ServoRestyleDamage::REPOSITION, "Reposition")
            , (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow")
            , (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes")
            , (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow")
            , (ServoRestyleDamage::REFLOW, "Reflow")
            , (ServoRestyleDamage::RESOLVE_GENERATED_CONTENT, "ResolveGeneratedContent")
            , (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow")
            ];

        for &(damage, damage_str) in &to_iter {
            if self.contains(damage) {
                if !first_elem { write!(f, " | ")?; }
                write!(f, "{}", damage_str)?;
                first_elem = false;
            }
        }

        if first_elem {
            write!(f, "NoDamage")?;
        }

        Ok(())
    }
}

// NB: We need the braces inside the RHS due to Rust #8012.  This particular
// version of this macro might be safe anyway, but we want to avoid silent
// breakage on modifications.
macro_rules! add_if_not_equal(
    ($old:ident, $new:ident, $damage:ident,
     [ $($effect:path),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
        if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
            $damage.insert($($effect)|*);
            true
        } else {
            false
        }
    })
);

fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage {
    let mut damage = ServoRestyleDamage::empty();

    // This should check every CSS property, as enumerated in the fields of
    // http://doc.servo.org/style/properties/struct.ComputedValues.html

    // FIXME: Test somehow that every property is included.

    add_if_not_equal!(old, new, damage,
                      [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                      ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                      ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW,
                      ServoRestyleDamage::RECONSTRUCT_FLOW], [
        get_box.clear, get_box.float, get_box.display, get_box.position, get_counters.content,
        get_counters.counter_reset, get_counters.counter_increment,
        get_list.quotes, get_list.list_style_type,

        // If these text or font properties change, we need to reconstruct the flow so that
        // text shaping is re-run.
        get_inheritedtext.letter_spacing, get_inheritedtext.text_rendering,
        get_inheritedtext.text_transform, get_inheritedtext.word_spacing,
        get_inheritedtext.overflow_wrap, get_inheritedtext.text_justify,
        get_inheritedtext.white_space, get_inheritedtext.word_break, get_text.text_overflow,
        get_font.font_family, get_font.font_style, get_font.font_variant_caps, get_font.font_weight,
        get_font.font_size, get_font.font_stretch,
        get_inheritedbox.direction, get_inheritedbox.writing_mode,
        get_text.text_decoration_line, get_text.unicode_bidi,
        get_inheritedtable.empty_cells, get_inheritedtable.caption_side,
        get_column.column_width, get_column.column_count
    ]) || (new.get_box().display == display::T::inline &&
           add_if_not_equal!(old, new, damage,
                             [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                             ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                             ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW,
                             ServoRestyleDamage::RECONSTRUCT_FLOW], [
        // For inline boxes only, border/padding styles are used in flow construction (to decide
        // whether to create fragments for empty flows).
        get_border.border_top_width, get_border.border_right_width,
        get_border.border_bottom_width, get_border.border_left_width,
        get_padding.padding_top, get_padding.padding_right,
        get_padding.padding_bottom, get_padding.padding_left
    ])) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                            ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::BUBBLE_ISIZES,
                            ServoRestyleDamage::REFLOW_OUT_OF_FLOW, ServoRestyleDamage::REFLOW],
        [get_border.border_top_width, get_border.border_right_width,
        get_border.border_bottom_width, get_border.border_left_width,
        get_margin.margin_top, get_margin.margin_right,
        get_margin.margin_bottom, get_margin.margin_left,
        get_padding.padding_top, get_padding.padding_right,
        get_padding.padding_bottom, get_padding.padding_left,
        get_position.width, get_position.height,
        get_inheritedtext.line_height,
        get_inheritedtext.text_align, get_inheritedtext.text_indent,
        get_table.table_layout,
        get_inheritedtable.border_collapse,
        get_inheritedtable.border_spacing,
        get_column.column_gap,
        get_position.flex_direction,
        get_position.flex_wrap,
        get_position.justify_content,
        get_position.align_items,
        get_position.align_content,
        get_position.order,
        get_position.flex_basis,
        get_position.flex_grow,
        get_position.flex_shrink,
        get_position.align_self
    ]) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT, ServoRestyleDamage::REPOSITION,
                            ServoRestyleDamage::STORE_OVERFLOW, ServoRestyleDamage::REFLOW_OUT_OF_FLOW],
        [get_position.top, get_position.left,
        get_position.right, get_position.bottom,
        get_effects.opacity,
        get_box.transform, get_box.transform_style, get_box.transform_origin,
        get_box.perspective, get_box.perspective_origin
    ]) || add_if_not_equal!(old, new, damage,
                            [ServoRestyleDamage::REPAINT], [
        get_color.color, get_background.background_color,
        get_background.background_image, get_background.background_position_x,
        get_background.background_position_y, get_background.background_repeat,
        get_background.background_attachment, get_background.background_clip,
        get_background.background_origin, get_background.background_size,
        get_border.border_top_color, get_border.border_right_color,
        get_border.border_bottom_color, get_border.border_left_color,
        get_border.border_top_style, get_border.border_right_style,
        get_border.border_bottom_style, get_border.border_left_style,
        get_border.border_top_left_radius, get_border.border_top_right_radius,
        get_border.border_bottom_left_radius, get_border.border_bottom_right_radius,
        get_position.z_index, get_box._servo_overflow_clip_box,
        get_inheritedtext._servo_text_decorations_in_effect,
        get_pointing.cursor, get_pointing.pointer_events,
        get_effects.box_shadow, get_effects.clip, get_inheritedtext.text_shadow, get_effects.filter,
        get_effects.mix_blend_mode, get_inheritedbox.image_rendering,

        // Note: May require REFLOW et al. if `visibility: collapse` is implemented.
        get_inheritedbox.visibility
    ]);

    // Paint worklets may depend on custom properties,
    // so if they have changed we should repaint.
    if old.custom_properties() != new.custom_properties() {
        damage.insert(ServoRestyleDamage::REPAINT);
    }

    // If the layer requirements of this flow have changed due to the value
    // of the transform, then reflow is required to rebuild the layers.
    if old.transform_requires_layer() != new.transform_requires_layer() {
        damage.insert(ServoRestyleDamage::rebuild_and_reflow());
    }

    damage
}
<|endoftext|>