File: Validation.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (254 lines) | stat: -rw-r--r-- 9,476 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
private struct ValidationRequest: EvaluationRequest {
    let unit: PackageUnit
    let packageResolver: PackageResolver

    func evaluate(evaluator: Evaluator) throws -> [String: [Diagnostic]] {
        var diagnostics: [String: [Diagnostic]] = [:]
        for sourceFile in unit.sourceFiles {
            var validator = PackageValidator(
                packageUnit: unit,
                packageResolver: packageResolver,
                evaluator: evaluator,
                sourceFile: sourceFile
            )
            try validator.walk(sourceFile.syntax)
            diagnostics[sourceFile.fileName] = validator.diagnostics
        }
        return diagnostics
    }
}

private struct PackageValidator: ASTVisitor {
    let packageUnit: PackageUnit
    let packageResolver: PackageResolver
    let evaluator: Evaluator
    let sourceFile: SyntaxNode<SourceFileSyntax>
    var diagnostics: [Diagnostic] = []
    var contextStack: [DeclContext] = []
    var declContext: DeclContext? { contextStack.last }

    init(
        packageUnit: PackageUnit,
        packageResolver: PackageResolver,
        evaluator: Evaluator,
        sourceFile: SyntaxNode<SourceFileSyntax>
    ) {
        self.packageUnit = packageUnit
        self.packageResolver = packageResolver
        self.evaluator = evaluator
        self.sourceFile = sourceFile
    }

    mutating func addDiagnostic(_ diagnostic: Diagnostic) {
        self.diagnostics.append(diagnostic)
    }

    mutating func pushContext(_ context: DeclContext) {
        self.contextStack.append(context)
    }
    mutating func popContext() {
        _ = self.contextStack.popLast()
    }

    // No-op for unhandled nodes
    func visit<T>(_: T) throws {}
    func visitPost<T>(_: T) throws {}

    mutating func visit(_ topLevelUse: SyntaxNode<TopLevelUseSyntax>) throws {
        _ = try validate(usePath: topLevelUse.item)
    }

    mutating func visit(_ world: SyntaxNode<WorldSyntax>) throws {
        // Enter world context
        pushContext(.init(kind: .world(world, sourceFile: sourceFile), packageUnit: packageUnit, packageResolver: packageResolver))
    }
    mutating func visitPost(_ world: SyntaxNode<WorldSyntax>) throws {
        popContext()  // Leave world context
    }

    mutating func visit(_ interface: SyntaxNode<InterfaceSyntax>) throws {
        // Enter interface context
        let context: InterfaceDefinitionContext
        switch declContext?.kind {
        case .interface, .inlineInterface:
            fatalError("Interface cannot be defined under other interface")
        case .world(let world, _): context = .world(world.name)
        case nil: context = .package(packageUnit.packageName)
        }
        pushContext(
            .init(
                kind: .interface(interface, sourceFile: sourceFile, context: context),
                packageUnit: packageUnit, packageResolver: packageResolver
            ))
    }
    mutating func visitPost(_ interface: SyntaxNode<InterfaceSyntax>) throws {
        popContext()  // Leave interface context
    }

    mutating func visit(_ importItem: ImportSyntax) throws {
        try visitExternKind(importItem.kind)
    }
    mutating func visitPost(_ importItem: ImportSyntax) throws {
        try visitPostExternKind(importItem.kind)
    }
    mutating func visit(_ export: ExportSyntax) throws {
        try visitExternKind(export.kind)
    }
    mutating func visitPost(_ export: ExportSyntax) throws {
        try visitPostExternKind(export.kind)
    }

    private mutating func visitExternKind(_ externKind: ExternKindSyntax) throws {
        guard case .world(let world, _) = declContext?.kind else {
            fatalError("WorldItem should not be appeared in non-world context")
        }
        switch externKind {
        case .interface(let name, let items):
            // Just set context. validation for inner items are handled by each visit methods
            pushContext(
                .init(
                    kind: .inlineInterface(
                        name: name,
                        items: items,
                        sourceFile: sourceFile,
                        parentWorld: world.name
                    ),
                    packageUnit: packageUnit,
                    packageResolver: packageResolver
                ))
        case .path(let path):
            _ = try validate(usePath: path)
        case .function: break  // Handled by visit(_: FunctionSyntax)
        }
    }
    private mutating func visitPostExternKind(_ externKind: ExternKindSyntax) throws {
        switch externKind {
        case .interface: self.popContext()  // Leave inline interface context
        default: break
        }
    }

    // MARK: Validate types

    mutating func visit(_ alias: TypeAliasSyntax) throws {
        _ = try validate(typeRepr: alias.typeRepr, textRange: nil)
    }

    mutating func visitPost(_ function: FunctionSyntax) throws {
        for param in function.parameters {
            _ = try validate(typeRepr: param.type, textRange: param.textRange)
        }
        switch function.results {
        case .named(let parameterList):
            for result in parameterList {
                _ = try validate(typeRepr: result.type, textRange: function.textRange)
            }
        case .anon(let typeRepr):
            _ = try validate(typeRepr: typeRepr, textRange: function.textRange)
        }
    }

    mutating func visit(_ record: RecordSyntax) throws {
        var fieldNames: Set<String> = []
        for field in record.fields {
            let name = field.name.text
            guard fieldNames.insert(name).inserted else {
                addDiagnostic(.invalidRedeclaration(of: name, textRange: field.name.textRange))
                continue
            }
            _ = try validate(typeRepr: field.type, textRange: field.textRange)
        }
    }

    mutating func visit(_ variant: VariantSyntax) throws {
        var caseNames: Set<String> = []
        for variantCase in variant.cases {
            let name = variantCase.name
            guard caseNames.insert(name.text).inserted else {
                addDiagnostic(.invalidRedeclaration(of: name.text, textRange: name.textRange))
                continue
            }
            guard let payloadType = variantCase.type else { continue }
            _ = try validate(typeRepr: payloadType, textRange: variantCase.textRange)
        }
    }

    mutating func visit(_ union: UnionSyntax) throws {
        for unionCase in union.cases {
            _ = try validate(typeRepr: unionCase.type, textRange: unionCase.textRange)
        }
    }

    mutating func visit(_ use: SyntaxNode<UseSyntax>) throws {
        guard let (interface, sourceFile, packageUnit) = try validate(usePath: use.from) else {
            return  // Skip rest of checks if interface reference is invalid
        }
        // Lookup within the found interface again.
        for useName in use.names {
            let request = TypeNameLookupRequest(
                context: .init(
                    kind: .interface(interface, sourceFile: sourceFile, context: .package(packageUnit.packageName)),
                    packageUnit: packageUnit,
                    packageResolver: packageResolver
                ),
                name: useName.name.text
            )
            try catchingDiagnostic { [evaluator] in
                _ = try evaluator.evaluate(request: request)
            }
        }
    }

    mutating func catchingDiagnostic<R>(textRange: TextRange? = nil, _ body: () throws -> R) throws -> R? {
        do {
            return try body()
        } catch let error as DiagnosticError {
            var diagnostic = error.diagnostic
            if diagnostic.textRange == nil {
                diagnostic.textRange = textRange
            }
            addDiagnostic(diagnostic)
            return nil
        }
    }

    mutating func validate(typeRepr: TypeReprSyntax, textRange: TextRange?) throws -> WITType? {
        guard let declContext else {
            fatalError("TypeRepr outside of declaration context!?")
        }
        let request = TypeResolutionRequest(context: declContext, typeRepr: typeRepr)
        return try self.catchingDiagnostic(textRange: textRange) { [evaluator] in
            try evaluator.evaluate(request: request)
        }
    }

    mutating func validate(usePath: UsePathSyntax) throws -> (
        interface: SyntaxNode<InterfaceSyntax>,
        sourceFile: SyntaxNode<SourceFileSyntax>,
        packageUnit: PackageUnit
    )? {
        // Check top-level use refers a valid interface
        let request = LookupInterfaceForUsePathRequest(
            use: usePath,
            packageResolver: packageResolver,
            packageUnit: packageUnit,
            sourceFile: declContext?.parentSourceFile
        )
        return try self.catchingDiagnostic { [evaluator] in
            try evaluator.evaluate(request: request)
        }
    }
}

extension PackageUnit {
    func validate(evaluator: Evaluator, packageResolver: PackageResolver) throws -> [String: [Diagnostic]] {
        try evaluator.evaluate(request: ValidationRequest(unit: self, packageResolver: packageResolver))
    }
}

extension SemanticsContext {
    /// Semantically validate this package.
    public func validate(package: PackageUnit) throws -> [String: [Diagnostic]] {
        try package.validate(evaluator: evaluator, packageResolver: packageResolver)
    }
}