Skip to main content

neo_devpack_solidity/solidity/validate/contract/
entry.rs

1pub fn validate_contract(metadata: &ContractMetadata) -> Vec<Diagnostic> {
2    let mut diagnostics = Vec::new();
3
4    let method_name_counts = build_method_name_counts(metadata);
5    let constructor_count = validate_methods(metadata, &mut diagnostics);
6
7    if constructor_count > 1 {
8        diagnostics.push(Diagnostic::error(format!("multiple constructors defined ({constructor_count} total)")));
9    }
10
11    validate_state_variables(metadata, &method_name_counts, &mut diagnostics);
12    validate_events(metadata, &mut diagnostics);
13    validate_return_types(metadata, &mut diagnostics);
14    validate_erc_nep_patterns(metadata, &mut diagnostics);
15    validate_using_directives(metadata, &mut diagnostics);
16    validate_type_definitions(metadata, &mut diagnostics);
17    validate_library(metadata, &mut diagnostics);
18    validate_abstract_contract(metadata, &mut diagnostics);
19    validate_flatten_warnings(metadata, &mut diagnostics);
20
21    diagnostics
22}
23
24fn validate_using_directives(_metadata: &ContractMetadata, _diagnostics: &mut [Diagnostic]) {
25    // `using` directives are validated during IR lowering where receiver type,
26    // member name, and lowered function overload information are all available.
27    // Keep this hook so future frontend-only `using` diagnostics can be added.
28}
29
30fn validate_type_definitions(_metadata: &ContractMetadata, _diagnostics: &mut [Diagnostic]) {
31    // User-defined value types (`type X is Y`) are now supported.
32    // They are treated as transparent type aliases; `wrap`/`unwrap` compile to no-ops.
33}
34
35fn validate_abstract_contract(metadata: &ContractMetadata, diagnostics: &mut Vec<Diagnostic>) {
36    let unimplemented: Vec<&str> = metadata
37        .methods
38        .iter()
39        .filter(|m| {
40            matches!(m.kind, FunctionKind::Regular)
41                && m.body.is_none()
42        })
43        .map(|m| m.name.as_str())
44        .collect();
45
46    if unimplemented.is_empty() {
47        return;
48    }
49
50    if metadata.is_interface {
51        return;
52    }
53
54    if metadata.is_abstract {
55        // Abstract contracts: informational warning listing unimplemented methods.
56        // This helps developers track what still needs implementation in derived contracts.
57        diagnostics.push(
58            Diagnostic::warning(format!(
59                "abstract contract '{}' has {} unimplemented function(s): [{}]",
60                metadata.name,
61                unimplemented.len(),
62                unimplemented.join(", "),
63            ))
64            .with_suggestion(
65                "derived contracts must implement these functions or also be declared abstract"
66            ),
67        );
68    } else {
69        // Non-abstract contracts must implement all declared functions.
70        diagnostics.push(
71            Diagnostic::error(format!(
72                "contract '{}' has {} unimplemented function(s) [{}] but is not declared abstract; \
73                 either provide implementations or declare the contract as 'abstract contract {}'",
74                metadata.name,
75                unimplemented.len(),
76                unimplemented.join(", "),
77                metadata.name,
78            ))
79            .with_suggestion(
80                "add 'abstract' before 'contract', or provide function bodies for all declared functions"
81            ),
82        );
83    }
84}
85
86fn validate_flatten_warnings(metadata: &ContractMetadata, diagnostics: &mut Vec<Diagnostic>) {
87    for warning in &metadata.flatten_warnings {
88        diagnostics.push(
89            Diagnostic::warning(warning.clone())
90                .with_code("W200")
91                .with_suggestion(
92                    "mark the base function 'virtual' and the overriding function 'override'",
93                ),
94        );
95    }
96}