Coverage Report

Created: 2026-08-13 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/validator/component_validator.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "common/errinfo.h"
5
#include "common/spdlog.h"
6
#include "validator/component_name.h"
7
#include "validator/validator.h"
8
9
#include <algorithm>
10
#include <unordered_set>
11
#include <variant>
12
13
namespace WasmEdge {
14
namespace Validator {
15
16
using namespace std::literals;
17
18
namespace {
19
2.12k
std::string toLowerStr(std::string_view SV) {
20
2.12k
  std::string Result(SV);
21
2.12k
  std::transform(
22
2.12k
      Result.begin(), Result.end(), Result.begin(),
23
4.63k
      [](unsigned char C) { return static_cast<char>(std::tolower(C)); });
24
2.12k
  return Result;
25
2.12k
}
26
27
// Maps a component-side ExternDesc::DescType to its Sort::SortType.
28
// Returns nullopt for `CoreType` (= `(core module (type i))`), which has no
29
// representation in Sort::SortType; callers must handle that sort separately
30
// via Sort::CoreSortType::Module.
31
std::optional<AST::Component::Sort::SortType>
32
250
descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept {
33
250
  switch (DT) {
34
0
  case AST::Component::ExternDesc::DescType::CoreType:
35
0
    return std::nullopt;
36
11
  case AST::Component::ExternDesc::DescType::FuncType:
37
11
    return AST::Component::Sort::SortType::Func;
38
230
  case AST::Component::ExternDesc::DescType::ValueBound:
39
230
    return AST::Component::Sort::SortType::Value;
40
8
  case AST::Component::ExternDesc::DescType::TypeBound:
41
8
    return AST::Component::Sort::SortType::Type;
42
0
  case AST::Component::ExternDesc::DescType::ComponentType:
43
0
    return AST::Component::Sort::SortType::Component;
44
1
  case AST::Component::ExternDesc::DescType::InstanceType:
45
1
    return AST::Component::Sort::SortType::Instance;
46
0
  default:
47
0
    assumingUnreachable();
48
250
  }
49
250
}
50
51
// Shallow sort-kind match between a Sort and an ExternDesc. The spec's
52
// instantiation / export-ascription rules require the supplied sortidx to be
53
// a subtype of the externdesc. Here we only enforce that the kind agrees;
54
// deep structural subtyping (record fields, func signatures, etc.) is not
55
// yet implemented and is the main remaining correctness gap at these sites.
56
bool sortMatchesDescType(const AST::Component::Sort &S,
57
20
                         AST::Component::ExternDesc::DescType DT) noexcept {
58
20
  auto Mapped = descTypeToSortType(DT);
59
20
  if (S.isCore()) {
60
1
    return !Mapped.has_value() &&
61
0
           S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module;
62
1
  }
63
19
  return Mapped.has_value() && S.getSortType() == *Mapped;
64
20
}
65
66
// Fallback type-index lookup against an InstanceType's own local
67
// type-decl space (used when the outer ComponentContext scope doesn't
68
// own the InstanceType).
69
const AST::Component::InstanceType *
70
resolveNestedInstanceType(const AST::Component::InstanceType &Parent,
71
0
                          uint32_t TypeIdx) noexcept {
72
0
  uint32_t LocalIdx = 0;
73
0
  for (const auto &LocalDecl : Parent.getDecl()) {
74
0
    if (!LocalDecl.isType()) {
75
0
      continue;
76
0
    }
77
0
    if (LocalIdx == TypeIdx) {
78
0
      const auto *LocalDT = LocalDecl.getType();
79
0
      if (LocalDT != nullptr && LocalDT->isInstanceType()) {
80
0
        return &LocalDT->getInstanceType();
81
0
      }
82
0
      return nullptr;
83
0
    }
84
0
    LocalIdx++;
85
0
  }
86
0
  return nullptr;
87
0
}
88
89
// Resolve a type index in `Comp`'s own type index space to an InstanceType.
90
// Returns nullptr when the index does not refer to an inline InstanceType
91
// definition — callers treat nullptr as "no required shape" and fall back
92
// to inferred exports. TypeBound imports and outer-alias type imports
93
// currently fall through to nullptr; a more complete resolver would walk
94
// the alias chain to recover the underlying InstanceType.
95
const AST::Component::InstanceType *
96
resolveChildInstanceType(const AST::Component::Component &Comp,
97
0
                         uint32_t TypeIdx) {
98
0
  uint32_t CurrentIdx = 0;
99
0
  for (const auto &Sec : Comp.getSections()) {
100
0
    if (std::holds_alternative<AST::Component::TypeSection>(Sec)) {
101
0
      const auto &TSec = std::get<AST::Component::TypeSection>(Sec);
102
0
      for (const auto &DT : TSec.getContent()) {
103
0
        if (CurrentIdx == TypeIdx) {
104
0
          if (DT.isInstanceType()) {
105
0
            return &DT.getInstanceType();
106
0
          }
107
0
          return nullptr;
108
0
        }
109
0
        CurrentIdx++;
110
0
      }
111
0
    } else if (std::holds_alternative<AST::Component::ImportSection>(Sec)) {
112
0
      const auto &ISec = std::get<AST::Component::ImportSection>(Sec);
113
0
      for (const auto &Import : ISec.getContent()) {
114
0
        if (Import.getDesc().getDescType() ==
115
0
            AST::Component::ExternDesc::DescType::TypeBound) {
116
0
          if (CurrentIdx == TypeIdx) {
117
0
            return nullptr;
118
0
          }
119
0
          CurrentIdx++;
120
0
        }
121
0
      }
122
0
    } else if (std::holds_alternative<AST::Component::AliasSection>(Sec)) {
123
0
      const auto &ASec = std::get<AST::Component::AliasSection>(Sec);
124
0
      for (const auto &Alias : ASec.getContent()) {
125
0
        if (!Alias.getSort().isCore() &&
126
0
            Alias.getSort().getSortType() ==
127
0
                AST::Component::Sort::SortType::Type) {
128
0
          if (CurrentIdx == TypeIdx) {
129
0
            return nullptr;
130
0
          }
131
0
          CurrentIdx++;
132
0
        }
133
0
      }
134
0
    }
135
0
  }
136
0
  return nullptr;
137
0
}
138
139
// Validate that a name may appear at an export position: reject the
140
// `relative-url=` prefix (not part of the extern-name grammar) and any
141
// plainname/interfacename kind that isn't allowed on an export.
142
945
Expect<ComponentName> validateExportName(std::string_view Name) noexcept {
143
945
  if (Name.rfind("relative-url="sv, 0) == 0) {
144
0
    spdlog::error(ErrCode::Value::InvalidExternName);
145
0
    spdlog::error("    Export name '{}' is not a valid extern name"sv, Name);
146
0
    return Unexpect(ErrCode::Value::InvalidExternName);
147
0
  }
148
1.82k
  EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name));
149
1.82k
  switch (CName.getKind()) {
150
353
  case ComponentNameKind::Label:
151
608
  case ComponentNameKind::Constructor:
152
608
  case ComponentNameKind::Method:
153
649
  case ComponentNameKind::Static:
154
878
  case ComponentNameKind::InterfaceType:
155
878
    return CName;
156
4
  default:
157
4
    spdlog::error(ErrCode::Value::InvalidExportName);
158
4
    spdlog::error("    Export name '{}' kind is not valid for exports"sv, Name);
159
4
    return Unexpect(ErrCode::Value::InvalidExportName);
160
1.82k
  }
161
1.82k
}
162
163
} // namespace
164
165
void Validator::populateInstanceFromType(
166
82
    uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept {
167
82
  for (const auto &Decl : IT.getDecl()) {
168
7
    if (!Decl.isExportDecl()) {
169
5
      continue;
170
5
    }
171
2
    const auto &Exp = Decl.getExport();
172
2
    const auto &ED = Exp.getExternDesc();
173
2
    auto ST = descTypeToSortType(ED.getDescType());
174
2
    if (!ST.has_value()) {
175
      // InstanceExport::ST has no `(core module)` variant — skip rather
176
      // than crash. TODO: extend InstanceExport with a core-sort alternative.
177
0
      spdlog::debug(
178
0
          "    populateInstanceFromType: skipping `(core module)` export "
179
0
          "'{}'"sv,
180
0
          Exp.getName());
181
0
      continue;
182
0
    }
183
2
    const AST::Component::InstanceType *NestedIT = nullptr;
184
2
    if (ED.getDescType() ==
185
2
        AST::Component::ExternDesc::DescType::InstanceType) {
186
0
      NestedIT = CompCtx.getInstanceType(ED.getTypeIndex());
187
0
      if (NestedIT == nullptr) {
188
0
        NestedIT = resolveNestedInstanceType(IT, ED.getTypeIndex());
189
0
      }
190
0
    }
191
    // Resource-typed exports carry a canonical id so an alias-export of
192
    // the type slot can preserve identity. `(sub resource)` introduces a
193
    // fresh id; `(eq i)` should inherit, but cross-scope resource lookup
194
    // inside an InstanceType body is not yet implemented (TODO).
195
2
    std::optional<uint64_t> ResourceId;
196
2
    if (ED.getDescType() == AST::Component::ExternDesc::DescType::TypeBound &&
197
0
        !ED.isEqType()) {
198
0
      ResourceId = CompCtx.allocateFreshResourceId();
199
0
    }
200
2
    CompCtx.addInstanceExport(InstIdx, Exp.getName(), *ST, NestedIT,
201
2
                              /*NestedInstIdx=*/std::nullopt, ResourceId);
202
2
  }
203
82
}
204
205
bool Validator::exportSatisfies(
206
    const AST::Component::InstanceType &RequiredCtx,
207
    const ComponentContext::InstanceExport &Provided,
208
0
    const AST::Component::ExternDesc &Required) const noexcept {
209
0
  auto RequiredST = descTypeToSortType(Required.getDescType());
210
0
  if (!RequiredST.has_value()) {
211
    // `(core module)` has no InstanceExport::ST variant — no constraint.
212
0
    return true;
213
0
  }
214
0
  if (Provided.ST != *RequiredST) {
215
0
    return false;
216
0
  }
217
  // Instance-on-instance: recurse if both sides resolve to an
218
  // InstanceType. Otherwise sort-kind match (pre-Phase-3 behaviour).
219
0
  if (Required.getDescType() ==
220
0
          AST::Component::ExternDesc::DescType::InstanceType &&
221
0
      Provided.IT != nullptr) {
222
0
    const auto *RequiredIT = CompCtx.getInstanceType(Required.getTypeIndex());
223
0
    if (RequiredIT == nullptr) {
224
      // Required's idx lives in RequiredCtx's local type-decls.
225
0
      RequiredIT =
226
0
          resolveNestedInstanceType(RequiredCtx, Required.getTypeIndex());
227
0
    }
228
0
    if (RequiredIT != nullptr) {
229
0
      return isInstanceSubtype(*Provided.IT, *RequiredIT);
230
0
    }
231
0
  }
232
0
  return true;
233
0
}
234
235
std::optional<std::string> Validator::findMissingRequiredExport(
236
    uint32_t ProvidedInstIdx,
237
0
    const AST::Component::InstanceType &RequiredIT) const noexcept {
238
0
  const auto &Exports = CompCtx.getInstance(ProvidedInstIdx).Exports;
239
0
  for (const auto &Decl : RequiredIT.getDecl()) {
240
0
    if (!Decl.isExportDecl()) {
241
0
      continue;
242
0
    }
243
0
    const auto &Exp = Decl.getExport();
244
0
    auto It = Exports.find(std::string(Exp.getName()));
245
0
    if (It == Exports.end()) {
246
0
      return std::string(Exp.getName());
247
0
    }
248
0
    if (!exportSatisfies(RequiredIT, It->second, Exp.getExternDesc())) {
249
0
      return std::string(Exp.getName());
250
0
    }
251
0
  }
252
0
  return std::nullopt;
253
0
}
254
255
bool Validator::isInstanceSubtype(
256
    const AST::Component::InstanceType &S,
257
0
    const AST::Component::InstanceType &T) const noexcept {
258
  // S subtype T iff every export declared by T is present in S with a
259
  // satisfying type. Build a quick (name → externdesc) lookup of S's
260
  // exports for the lookup.
261
0
  std::unordered_map<std::string, const AST::Component::ExternDesc *> SExports;
262
0
  for (const auto &Decl : S.getDecl()) {
263
0
    if (Decl.isExportDecl()) {
264
0
      const auto &E = Decl.getExport();
265
0
      SExports.emplace(std::string(E.getName()), &E.getExternDesc());
266
0
    }
267
0
  }
268
0
  for (const auto &Decl : T.getDecl()) {
269
0
    if (!Decl.isExportDecl()) {
270
0
      continue;
271
0
    }
272
0
    const auto &E = Decl.getExport();
273
0
    auto It = SExports.find(std::string(E.getName()));
274
0
    if (It == SExports.end()) {
275
0
      return false;
276
0
    }
277
0
    auto SKind = descTypeToSortType(It->second->getDescType());
278
0
    auto TKind = descTypeToSortType(E.getExternDesc().getDescType());
279
0
    if (SKind != TKind) {
280
0
      return false;
281
0
    }
282
    // Instance-on-instance: nested type indices on each side belong to
283
    // that side's own decls, so fall back via resolveNestedInstanceType.
284
0
    if (E.getExternDesc().getDescType() ==
285
0
        AST::Component::ExternDesc::DescType::InstanceType) {
286
0
      const auto *SubIT = CompCtx.getInstanceType(It->second->getTypeIndex());
287
0
      if (SubIT == nullptr) {
288
0
        SubIT = resolveNestedInstanceType(S, It->second->getTypeIndex());
289
0
      }
290
0
      const auto *ReqIT =
291
0
          CompCtx.getInstanceType(E.getExternDesc().getTypeIndex());
292
0
      if (ReqIT == nullptr) {
293
0
        ReqIT = resolveNestedInstanceType(T, E.getExternDesc().getTypeIndex());
294
0
      }
295
0
      if (SubIT != nullptr && ReqIT != nullptr &&
296
0
          !isInstanceSubtype(*SubIT, *ReqIT)) {
297
0
        return false;
298
0
      }
299
0
    }
300
0
  }
301
0
  return true;
302
0
}
303
304
Expect<void>
305
3.59k
Validator::validate(const AST::Component::Component &Comp) noexcept {
306
3.59k
  spdlog::warn("Component Model Validation is in active development."sv);
307
3.59k
  CompCtx.reset();
308
3.59k
  return validateComponent(Comp).and_then([&]() {
309
1.78k
    const_cast<AST::Component::Component &>(Comp).setIsValidated();
310
1.78k
    return Expect<void>{};
311
1.78k
  });
312
3.59k
}
313
314
Expect<void>
315
7.44k
Validator::validateComponent(const AST::Component::Component &Comp) noexcept {
316
  // Validation enters a fresh component scope and walks sections in their
317
  // binary order. The per-sort index spaces are built incrementally as
318
  // definitions are validated, so sortidx references in later sections
319
  // only resolve against entries already introduced. Custom sections are
320
  // ignored. Nested components recurse through this same function.
321
7.44k
  auto ReportError = [](auto E) {
322
1.82k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component));
323
1.82k
    return E;
324
1.82k
  };
325
326
7.44k
  CompCtx.enterComponent(&Comp);
327
2.94M
  for (const auto &Sec : Comp.getSections()) {
328
2.94M
    auto Func = [&](auto &&S) -> Expect<void> {
329
2.94M
      using T = std::decay_t<decltype(S)>;
330
2.94M
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.64M
      } else {
333
1.64M
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.64M
      }
335
1.64M
      return {};
336
2.94M
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::CustomSection const&>(WasmEdge::AST::CustomSection const&) const
Line
Count
Source
328
1.29M
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.29M
      using T = std::decay_t<decltype(S)>;
330
1.29M
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
      } else {
333
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
      }
335
1.29M
      return {};
336
1.29M
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreModuleSection const&>(WasmEdge::AST::Component::CoreModuleSection const&) const
Line
Count
Source
328
1.51k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.51k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.51k
      } else {
333
1.51k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.51k
      }
335
1.49k
      return {};
336
1.51k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreInstanceSection const&>(WasmEdge::AST::Component::CoreInstanceSection const&) const
Line
Count
Source
328
6.52k
    auto Func = [&](auto &&S) -> Expect<void> {
329
6.52k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
6.52k
      } else {
333
6.52k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
6.52k
      }
335
6.45k
      return {};
336
6.52k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreTypeSection const&>(WasmEdge::AST::Component::CoreTypeSection const&) const
Line
Count
Source
328
317k
    auto Func = [&](auto &&S) -> Expect<void> {
329
317k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
317k
      } else {
333
317k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
317k
      }
335
317k
      return {};
336
317k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ComponentSection const&>(WasmEdge::AST::Component::ComponentSection const&) const
Line
Count
Source
328
3.84k
    auto Func = [&](auto &&S) -> Expect<void> {
329
3.84k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
3.84k
      } else {
333
3.84k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
3.84k
      }
335
3.83k
      return {};
336
3.84k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::InstanceSection const&>(WasmEdge::AST::Component::InstanceSection const&) const
Line
Count
Source
328
80.2k
    auto Func = [&](auto &&S) -> Expect<void> {
329
80.2k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
80.2k
      } else {
333
80.2k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
80.2k
      }
335
80.0k
      return {};
336
80.2k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::AliasSection const&>(WasmEdge::AST::Component::AliasSection const&) const
Line
Count
Source
328
6.04k
    auto Func = [&](auto &&S) -> Expect<void> {
329
6.04k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
6.04k
      } else {
333
6.04k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
6.04k
      }
335
5.90k
      return {};
336
6.04k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::TypeSection const&>(WasmEdge::AST::Component::TypeSection const&) const
Line
Count
Source
328
1.18M
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.18M
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.18M
      } else {
333
1.18M
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.18M
      }
335
1.18M
      return {};
336
1.18M
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CanonSection const&>(WasmEdge::AST::Component::CanonSection const&) const
Line
Count
Source
328
22.9k
    auto Func = [&](auto &&S) -> Expect<void> {
329
22.9k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
22.9k
      } else {
333
22.9k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
22.9k
      }
335
22.7k
      return {};
336
22.9k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::StartSection const&>(WasmEdge::AST::Component::StartSection const&) const
Line
Count
Source
328
1.17k
    auto Func = [&](auto &&S) -> Expect<void> {
329
1.17k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
1.17k
      } else {
333
1.17k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
1.17k
      }
335
1.11k
      return {};
336
1.17k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ImportSection const&>(WasmEdge::AST::Component::ImportSection const&) const
Line
Count
Source
328
14.6k
    auto Func = [&](auto &&S) -> Expect<void> {
329
14.6k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
14.6k
      } else {
333
14.6k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
14.6k
      }
335
13.9k
      return {};
336
14.6k
    };
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ExportSection const&>(WasmEdge::AST::Component::ExportSection const&) const
Line
Count
Source
328
2.20k
    auto Func = [&](auto &&S) -> Expect<void> {
329
2.20k
      using T = std::decay_t<decltype(S)>;
330
      if constexpr (std::is_same_v<T, AST::CustomSection>) {
331
        // Always pass validation.
332
2.20k
      } else {
333
2.20k
        EXPECTED_TRY(validate(S).map_error(ReportError));
334
2.20k
      }
335
2.11k
      return {};
336
2.20k
    };
337
2.94M
    EXPECTED_TRY(std::visit(Func, Sec));
338
2.94M
  }
339
5.61k
  CompCtx.exitComponent();
340
5.61k
  return {};
341
7.44k
}
342
343
Expect<void>
344
1.51k
Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept {
345
1.51k
  EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) {
346
1.49k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod));
347
1.49k
    return E;
348
1.49k
  }));
349
1.49k
  const_cast<AST::Module &>(ModSec.getContent()).setIsValidated();
350
1.49k
  CompCtx.addCoreModule(ModSec.getContent());
351
1.49k
  return {};
352
1.51k
}
353
354
Expect<void> Validator::validate(
355
6.52k
    const AST::Component::CoreInstanceSection &InstSec) noexcept {
356
6.52k
  for (const auto &Inst : InstSec.getContent()) {
357
6.10k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
358
6.10k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance));
359
6.10k
      return E;
360
6.10k
    }));
361
6.10k
  }
362
6.45k
  return {};
363
6.52k
}
364
365
Expect<void>
366
317k
Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept {
367
317k
  for (const auto &Type : TypeSec.getContent()) {
368
252k
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
369
252k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType));
370
252k
      return E;
371
252k
    }));
372
252k
  }
373
317k
  return {};
374
317k
}
375
376
Expect<void>
377
3.84k
Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept {
378
3.84k
  EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) {
379
3.83k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component));
380
3.83k
    return E;
381
3.83k
  }));
382
3.83k
  CompCtx.addComponent(CompSec.getContent());
383
3.83k
  return {};
384
3.84k
}
385
386
Expect<void>
387
80.2k
Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept {
388
80.2k
  for (const auto &Inst : InstSec.getContent()) {
389
26.2k
    EXPECTED_TRY(validate(Inst).map_error([](auto E) {
390
26.2k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance));
391
26.2k
      return E;
392
26.2k
    }));
393
26.2k
  }
394
80.0k
  return {};
395
80.2k
}
396
397
Expect<void>
398
6.04k
Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept {
399
6.04k
  for (const auto &Alias : AliasSec.getContent()) {
400
5.56k
    EXPECTED_TRY(validate(Alias).map_error([](auto E) {
401
5.41k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias));
402
5.41k
      return E;
403
5.41k
    }));
404
5.41k
    const auto &Sort = Alias.getSort();
405
5.41k
    const bool IsOuter =
406
5.41k
        Alias.getTargetType() == AST::Component::Alias::TargetType::Outer;
407
5.41k
    if (Sort.isCore()) {
408
644
      uint32_t NewCoreIdx =
409
644
          CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
410
      // Carry the outer-aliased module's slot so the alias stays enumerable
411
      // when instantiated.
412
644
      if (IsOuter && Sort.getCoreSortType() ==
413
331
                         AST::Component::Sort::CoreSortType::Module) {
414
0
        CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first,
415
0
                                     Alias.getOuter().second);
416
0
      }
417
4.77k
    } else {
418
4.77k
      uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
419
      // Component analogue of the outer core-module carry above.
420
4.77k
      if (IsOuter &&
421
3.59k
          Sort.getSortType() == AST::Component::Sort::SortType::Component) {
422
422
        CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first,
423
422
                                    Alias.getOuter().second);
424
422
      }
425
      // Outer-aliasing a resource type keeps the resource's identity in this
426
      // scope so later own/borrow and (eq i) checks treat the slot correctly.
427
4.77k
      if (IsOuter &&
428
3.59k
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
429
3.17k
        CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first,
430
3.17k
                                   Alias.getOuter().second);
431
3.17k
      }
432
      // If the alias creates a new instance entry out of an `alias export`
433
      // on another instance, propagate the source instance's export table
434
      // into the new slot so a subsequent `alias export` on this slot can
435
      // resolve nested exports.
436
4.77k
      if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) {
437
1.18k
        const auto SrcInstIdx = Alias.getExport().first;
438
1.18k
        const auto &SrcName = Alias.getExport().second;
439
1.18k
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
440
1.18k
        auto It = SrcExports.find(std::string(SrcName));
441
1.18k
        if (It != SrcExports.end()) {
442
1.18k
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
443
1.18k
            if (It->second.IT != nullptr) {
444
0
              populateInstanceFromType(NewIdx, *It->second.IT);
445
1.18k
            } else if (It->second.NestedInstIdx.has_value()) {
446
1.18k
              const auto &NestedExports =
447
1.18k
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
448
1.18k
              for (const auto &[Name, IE] : NestedExports) {
449
1.18k
                CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
450
1.18k
                                          IE.NestedInstIdx, IE.ResourceId);
451
1.18k
              }
452
1.18k
            }
453
1.18k
          } else if (Sort.getSortType() ==
454
0
                         AST::Component::Sort::SortType::Type &&
455
0
                     It->second.ResourceId.has_value()) {
456
            // Alias-export of a resource type — same identity.
457
0
            CompCtx.addResource(NewIdx, {*It->second.ResourceId,
458
0
                                         /*LocallyDefined=*/false});
459
0
          }
460
1.18k
        }
461
1.18k
      }
462
4.77k
    }
463
5.41k
  }
464
5.90k
  return {};
465
6.04k
}
466
467
Expect<void>
468
1.18M
Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept {
469
1.19M
  for (const auto &Type : TypeSec.getContent()) {
470
1.19M
    EXPECTED_TRY(validate(Type).map_error([](auto E) {
471
1.19M
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type));
472
1.19M
      return E;
473
1.19M
    }));
474
1.19M
  }
475
1.18M
  return {};
476
1.18M
}
477
478
Expect<void>
479
22.9k
Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept {
480
22.9k
  for (const auto &C : CanonSec.getContent()) {
481
7.44k
    EXPECTED_TRY(validate(C).map_error([](auto E) {
482
7.44k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon));
483
7.44k
      return E;
484
7.44k
    }));
485
7.44k
  }
486
22.7k
  return {};
487
22.9k
}
488
489
Expect<void>
490
1.17k
Validator::validate(const AST::Component::StartSection &StartSec) noexcept {
491
  // Validation steps:
492
  //   1. `f` is in bounds of the component func index space.
493
  //   2. `f`'s functype param arity equals |arg*| and result arity equals
494
  //      `r`. Per-argument value-type subtype is checked once subtype
495
  //      machinery exists (Phase 3).
496
  //   3. Each argument index is in bounds of the value index space.
497
  //      Per-value linearity (consume-once) is the responsibility of
498
  //      GAP-EX-4: the per-value `consumed` flag and the end-of-component
499
  //      "all consumed" pass. They are not yet wired here.
500
  //   4. The function's result types are appended to the value index space
501
  //      as fresh values, so later definitions can reference them.
502
1.17k
  const auto &Start = StartSec.getContent();
503
504
  // 1. Function index bounds.
505
1.17k
  const uint32_t FuncIdx = Start.getFunctionIndex();
506
1.17k
  const uint32_t FuncSpaceSize =
507
1.17k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
508
1.17k
  if (FuncIdx >= FuncSpaceSize) {
509
23
    spdlog::error(ErrCode::Value::InvalidIndex);
510
23
    spdlog::error(
511
23
        "    Start: function index {} exceeds func index space size {}"sv,
512
23
        FuncIdx, FuncSpaceSize);
513
23
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
514
23
    return Unexpect(ErrCode::Value::InvalidIndex);
515
23
  }
516
517
  // 2. Look up the func type. If null (e.g. imported component func without
518
  // populated FuncType yet), skip arity check — the bound check above is
519
  // enough for the moment.
520
1.15k
  const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx);
521
1.15k
  if (FT != nullptr) {
522
436
    const auto Args = Start.getArguments();
523
436
    const auto &ParamList = FT->getParamList();
524
436
    if (Args.size() != ParamList.size()) {
525
1
      spdlog::error(ErrCode::Value::InvalidIndex);
526
1
      spdlog::error(
527
1
          "    Start: argument count {} does not match func {} param arity {}"sv,
528
1
          Args.size(), FuncIdx, ParamList.size());
529
1
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
530
1
      return Unexpect(ErrCode::Value::InvalidIndex);
531
1
    }
532
435
    const uint32_t ResultArity =
533
435
        static_cast<uint32_t>(FT->getResultList().size());
534
435
    if (Start.getResult() != ResultArity) {
535
8
      spdlog::error(ErrCode::Value::InvalidIndex);
536
8
      spdlog::error(
537
8
          "    Start: declared result count {} does not match func {} result arity {}"sv,
538
8
          Start.getResult(), FuncIdx, ResultArity);
539
8
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
540
8
      return Unexpect(ErrCode::Value::InvalidIndex);
541
8
    }
542
435
  }
543
544
  // 3. Argument indices must be in value index space bounds. Per-arg
545
  // consume-once tracking is deferred (GAP-EX-4).
546
1.14k
  const uint32_t ValueSpaceSize =
547
1.14k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value);
548
1.14k
  for (const uint32_t ArgIdx : Start.getArguments()) {
549
413
    if (ArgIdx >= ValueSpaceSize) {
550
22
      spdlog::error(ErrCode::Value::InvalidIndex);
551
22
      spdlog::error(
552
22
          "    Start: argument value index {} exceeds value index space size {}"sv,
553
22
          ArgIdx, ValueSpaceSize);
554
22
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start));
555
22
      return Unexpect(ErrCode::Value::InvalidIndex);
556
22
    }
557
413
  }
558
559
  // 4. Append result values to the value index space.
560
579M
  for (uint32_t I = 0; I < Start.getResult(); ++I) {
561
579M
    CompCtx.addValue();
562
579M
  }
563
1.11k
  return {};
564
1.14k
}
565
566
Expect<void>
567
14.6k
Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept {
568
14.6k
  for (const auto &Imp : ImpSec.getContent()) {
569
5.40k
    EXPECTED_TRY(validate(Imp).map_error([](auto E) {
570
5.40k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import));
571
5.40k
      return E;
572
5.40k
    }));
573
5.40k
  }
574
13.9k
  return {};
575
14.6k
}
576
577
Expect<void>
578
2.20k
Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept {
579
2.20k
  for (const auto &Exp : ExpSec.getContent()) {
580
705
    EXPECTED_TRY(validate(Exp).map_error([](auto E) {
581
705
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export));
582
705
      return E;
583
705
    }));
584
705
  }
585
2.11k
  return {};
586
2.20k
}
587
588
Expect<void>
589
6.10k
Validator::validate(const AST::Component::CoreInstance &Inst) noexcept {
590
6.10k
  if (Inst.isInstantiateModule()) {
591
    // Instantiate module case.
592
593
    // Check the module index bound first.
594
284
    const uint32_t ModIdx = Inst.getModuleIndex();
595
284
    if (ModIdx >= CompCtx.getCoreSortIndexSize(
596
284
                      AST::Component::Sort::CoreSortType::Module)) {
597
26
      spdlog::error(ErrCode::Value::InvalidIndex);
598
26
      spdlog::error(
599
26
          "    CoreInstance: Module index {} exceeds available core modules {}"sv,
600
26
          ModIdx,
601
26
          CompCtx.getCoreSortIndexSize(
602
26
              AST::Component::Sort::CoreSortType::Module));
603
26
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
604
26
      return Unexpect(ErrCode::Value::InvalidIndex);
605
26
    }
606
    // Reject duplicate argument names on an instantiate expression. The
607
    // spec requires argument names to be strongly-unique per instantiation.
608
258
    {
609
258
      std::unordered_set<std::string_view> SeenArgs;
610
258
      for (const auto &Arg : Inst.getInstantiateArgs()) {
611
0
        if (!SeenArgs.insert(Arg.getName()).second) {
612
0
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
613
0
          spdlog::error("    CoreInstance: Duplicate argument name '{}'"sv,
614
0
                        Arg.getName());
615
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
616
0
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
617
0
        }
618
0
      }
619
258
    }
620
621
    // Imports + exports come from the raw Module (inline) or the
622
    // CoreModuleType (imported / aliased) — GAP-CI-1.
623
258
    const auto &CoreModSlot = CompCtx.getCoreModule(ModIdx);
624
258
    const auto *Mod = CoreModSlot.Body;
625
258
    const auto *ModTy = CoreModSlot.Type;
626
627
    // Required arg module-names (one per distinct CoreImportDecl module).
628
258
    std::vector<std::string_view> RequiredArgNames;
629
258
    if (Mod != nullptr) {
630
258
      for (const auto &Import : Mod->getImportSection().getContent()) {
631
1
        RequiredArgNames.push_back(Import.getModuleName());
632
1
      }
633
258
    } else if (ModTy != nullptr && ModTy->isModuleType()) {
634
0
      for (const auto &Decl : ModTy->getModuleType()) {
635
0
        if (Decl.isImport()) {
636
0
          RequiredArgNames.push_back(Decl.getImport().getModuleName());
637
0
        }
638
0
      }
639
0
    }
640
641
258
    auto Args = Inst.getInstantiateArgs();
642
258
    for (const auto ImportName : RequiredArgNames) {
643
1
      const auto ArgIt =
644
1
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
645
0
            return Arg.getName() == ImportName;
646
0
          });
647
1
      if (ArgIt == Args.end()) {
648
1
        spdlog::error(ErrCode::Value::MissingArgument);
649
1
        spdlog::error(
650
1
            "    CoreInstance: Module index {} missing argument for import '{}'"sv,
651
1
            Inst.getModuleIndex(), ImportName);
652
1
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
653
1
        return Unexpect(ErrCode::Value::MissingArgument);
654
1
      }
655
1
    }
656
657
    // Allocate the core:instance and bind exports to it.
658
257
    uint32_t InstanceIdx = CompCtx.addCoreInstance();
659
257
    if (Mod != nullptr) {
660
257
      for (const auto &ExportDesc : Mod->getExportSection().getContent()) {
661
0
        CompCtx.addCoreInstanceExport(InstanceIdx, ExportDesc.getExternalName(),
662
0
                                      ExportDesc.getExternalType());
663
0
      }
664
257
    } else if (ModTy != nullptr && ModTy->isModuleType()) {
665
0
      for (const auto &Decl : ModTy->getModuleType()) {
666
0
        if (!Decl.isExport()) {
667
0
          continue;
668
0
        }
669
0
        const auto &Exp = Decl.getExport();
670
0
        const auto &ImpDesc = Exp.getImportDesc();
671
0
        ExternalType ET;
672
0
        if (ImpDesc.isFunc()) {
673
0
          ET = ExternalType::Function;
674
0
        } else if (ImpDesc.isTable()) {
675
0
          ET = ExternalType::Table;
676
0
        } else if (ImpDesc.isMemory()) {
677
0
          ET = ExternalType::Memory;
678
0
        } else if (ImpDesc.isGlobal()) {
679
0
          ET = ExternalType::Global;
680
0
        } else if (ImpDesc.isTag()) {
681
0
          ET = ExternalType::Tag;
682
0
        } else {
683
0
          continue;
684
0
        }
685
0
        CompCtx.addCoreInstanceExport(InstanceIdx, Exp.getName(), ET);
686
0
      }
687
0
    }
688
5.82k
  } else if (Inst.isInlineExport()) {
689
    // Inline export case.
690
    // Allocate the core instance first, then register each inline export.
691
5.82k
    uint32_t InstanceIdx = CompCtx.addCoreInstance();
692
693
    // Check the core:sort index bound and register the inline exports.
694
    // Inline-export names on a core instance must be strongly-unique.
695
5.82k
    std::unordered_set<std::string_view> SeenExports;
696
5.82k
    for (const auto &Export : Inst.getInlineExports()) {
697
1.51k
      if (!SeenExports.insert(Export.getName()).second) {
698
2
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
699
2
        spdlog::error("    CoreInstance: Duplicate inline-export name '{}'"sv,
700
2
                      Export.getName());
701
2
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
702
2
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
703
2
      }
704
1.51k
      const auto &Sort = Export.getSortIdx().getSort();
705
1.51k
      uint32_t Idx = Export.getSortIdx().getIdx();
706
1.51k
      assuming(Sort.isCore());
707
1.51k
      if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
708
        // The error message differs of the tag core sort.
709
39
        ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
710
39
        if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
711
2
          ErrValue = ErrCode::Value::UnknownCoreTag;
712
2
        }
713
39
        spdlog::error(ErrValue);
714
39
        spdlog::error(
715
39
            "    CoreInstance: Inline export '{}' refers to invalid index {}"sv,
716
39
            Export.getName(), Idx);
717
39
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
718
39
        return Unexpect(ErrValue);
719
39
      }
720
      // Map CoreSortType to ExternalType for the instance export map.
721
1.47k
      ExternalType ET;
722
1.47k
      switch (Sort.getCoreSortType()) {
723
1.47k
      case AST::Component::Sort::CoreSortType::Func:
724
1.47k
        ET = ExternalType::Function;
725
1.47k
        break;
726
0
      case AST::Component::Sort::CoreSortType::Table:
727
0
        ET = ExternalType::Table;
728
0
        break;
729
0
      case AST::Component::Sort::CoreSortType::Memory:
730
0
        ET = ExternalType::Memory;
731
0
        break;
732
0
      case AST::Component::Sort::CoreSortType::Global:
733
0
        ET = ExternalType::Global;
734
0
        break;
735
0
      case AST::Component::Sort::CoreSortType::Tag:
736
0
        ET = ExternalType::Tag;
737
0
        break;
738
2
      default:
739
2
        spdlog::error(ErrCode::Value::InvalidIndex);
740
2
        spdlog::error(
741
2
            "    CoreInstance: Inline export '{}' has unsupported core sort"sv,
742
2
            Export.getName());
743
2
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance));
744
2
        return Unexpect(ErrCode::Value::InvalidIndex);
745
1.47k
      }
746
1.47k
      CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET);
747
1.47k
    }
748
5.82k
  } else {
749
0
    assumingUnreachable();
750
0
  }
751
6.03k
  return {};
752
6.10k
}
753
754
Expect<void>
755
26.2k
Validator::validate(const AST::Component::Instance &Inst) noexcept {
756
26.2k
  if (Inst.isInstantiateModule()) {
757
    // Instantiate module case.
758
759
    // Check the component index bound first.
760
2.79k
    const uint32_t CompIdx = Inst.getComponentIndex();
761
2.79k
    if (CompIdx >=
762
2.79k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) {
763
74
      spdlog::error(ErrCode::Value::InvalidIndex);
764
74
      spdlog::error(
765
74
          "    Instance: Component index {} exceeds available components {}"sv,
766
74
          CompIdx,
767
74
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component));
768
74
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
769
74
      return Unexpect(ErrCode::Value::InvalidIndex);
770
74
    }
771
    // Reject duplicate argument names on an instantiate expression. The
772
    // spec requires argument names to be strongly-unique per instantiation.
773
2.72k
    {
774
2.72k
      std::unordered_set<std::string_view> SeenArgs;
775
2.72k
      for (const auto &Arg : Inst.getInstantiateArgs()) {
776
2.36k
        if (!SeenArgs.insert(Arg.getName()).second) {
777
22
          spdlog::error(ErrCode::Value::ComponentDuplicateName);
778
22
          spdlog::error("    Instance: Duplicate argument name '{}'"sv,
779
22
                        Arg.getName());
780
22
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
781
22
          return Unexpect(ErrCode::Value::ComponentDuplicateName);
782
22
        }
783
2.36k
      }
784
2.72k
    }
785
786
    // Source: raw Component (inline) or ComponentType (imported / aliased).
787
2.70k
    const auto &CompSlot = CompCtx.getComponent(CompIdx);
788
2.70k
    const auto *Comp = CompSlot.Body;
789
2.70k
    const auto *CompTy = CompSlot.Type;
790
791
    // Verify each component import is satisfied by some instantiate arg.
792
2.70k
    auto Args = Inst.getInstantiateArgs();
793
2.70k
    auto checkImport =
794
2.70k
        [&](std::string_view ImportName,
795
2.70k
            const AST::Component::ExternDesc &ImportDesc) -> Expect<void> {
796
20
      const auto ArgIt =
797
46
          std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) {
798
46
            return Arg.getName() == ImportName;
799
46
          });
800
20
      if (ArgIt == Args.end()) {
801
10
        spdlog::error(ErrCode::Value::MissingArgument);
802
10
        spdlog::error(
803
10
            "    Instance: Component index {} missing argument for import '{}'"sv,
804
10
            Inst.getComponentIndex(), ImportName);
805
10
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
806
10
        return Unexpect(ErrCode::Value::MissingArgument);
807
10
      }
808
10
      const auto &Sort = ArgIt->getIndex().getSort();
809
10
      const uint32_t Idx = ArgIt->getIndex().getIdx();
810
      // Only `core module` is admissible as a core-side import externdesc.
811
10
      if (Sort.isCore() && Sort.getCoreSortType() !=
812
3
                               AST::Component::Sort::CoreSortType::Module) {
813
2
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
814
2
        spdlog::error("    Instance: Argument '{}' uses a core sort other than "
815
2
                      "`core module`, which no import externdesc can accept"sv,
816
2
                      ImportName);
817
2
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
818
2
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
819
2
      }
820
8
      if (!sortMatchesDescType(Sort, ImportDesc.getDescType())) {
821
2
        spdlog::error(ErrCode::Value::ArgTypeMismatch);
822
2
        spdlog::error("    Instance: Argument '{}' sort mismatch for import"sv,
823
2
                      ImportName);
824
2
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
825
2
        return Unexpect(ErrCode::Value::ArgTypeMismatch);
826
2
      }
827
6
      if (Sort.isCore()) {
828
0
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
829
0
          spdlog::error(ErrCode::Value::InvalidIndex);
830
0
          spdlog::error(
831
0
              "    Instance: Argument '{}' refers to invalid index {}"sv,
832
0
              ImportName, Idx);
833
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
834
0
          return Unexpect(ErrCode::Value::InvalidIndex);
835
0
        }
836
0
        return {};
837
0
      }
838
6
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
839
1
        spdlog::error(ErrCode::Value::InvalidIndex);
840
1
        spdlog::error(
841
1
            "    Instance: Argument '{}' refers to invalid index {}"sv,
842
1
            ImportName, Idx);
843
1
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
844
1
        return Unexpect(ErrCode::Value::InvalidIndex);
845
1
      }
846
      // Partial subtype: for instance-typed imports, verify the provided
847
      // instance has every required export (raw-Component path only;
848
      // ComponentType path needs GAP-DECL-ED).
849
5
      if (Comp != nullptr &&
850
5
          ImportDesc.getDescType() ==
851
5
              AST::Component::ExternDesc::DescType::InstanceType &&
852
0
          Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
853
0
        const auto *RequiredIT =
854
0
            resolveChildInstanceType(*Comp, ImportDesc.getTypeIndex());
855
0
        if (RequiredIT != nullptr) {
856
0
          if (auto Missing = findMissingRequiredExport(Idx, *RequiredIT)) {
857
0
            spdlog::error(ErrCode::Value::InstanceMissingExpectedExport);
858
0
            spdlog::error("    Instance: Argument '{}' missing required export "
859
0
                          "'{}' for import"sv,
860
0
                          ImportName, *Missing);
861
0
            spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
862
0
            return Unexpect(ErrCode::Value::InstanceMissingExpectedExport);
863
0
          }
864
0
        }
865
0
      }
866
5
      return {};
867
5
    };
868
2.70k
    if (Comp != nullptr) {
869
6.75k
      for (const auto &Sec : Comp->getSections()) {
870
6.75k
        if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) {
871
478
          for (const auto &Imp : IS->getContent()) {
872
19
            EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc()));
873
19
          }
874
478
        }
875
6.75k
      }
876
1.47k
    } else if (CompTy != nullptr) {
877
537
      for (const auto &CD : CompTy->getDecl()) {
878
229
        if (CD.isImportDecl()) {
879
1
          const auto &ID = CD.getImport();
880
1
          EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc()));
881
1
        }
882
229
      }
883
537
    }
884
885
    // Allocate the slot + populate exports so alias-export can resolve.
886
    // Raw-Component path resolves the IT for instance-typed exports;
887
    // ComponentType path registers sort-kind only (GAP-DECL-ED).
888
    // TODO (GAP-I-3): fresh ResourceIds + per-export resource remapping.
889
2.68k
    uint32_t InstanceIdx = CompCtx.addInstance();
890
2.68k
    if (Comp != nullptr) {
891
6.68k
      for (const auto &Sec : Comp->getSections()) {
892
6.68k
        const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec);
893
6.68k
        if (ES == nullptr) {
894
5.29k
          continue;
895
5.29k
        }
896
1.39k
        for (const auto &Exp : ES->getContent()) {
897
0
          const auto &ExpSort = Exp.getSortIndex().getSort();
898
0
          if (ExpSort.isCore()) {
899
0
            continue;
900
0
          }
901
0
          const AST::Component::InstanceType *IT = nullptr;
902
0
          if (ExpSort.getSortType() ==
903
0
                  AST::Component::Sort::SortType::Instance &&
904
0
              Exp.getDesc().has_value() &&
905
0
              Exp.getDesc()->getDescType() ==
906
0
                  AST::Component::ExternDesc::DescType::InstanceType) {
907
0
            IT = resolveChildInstanceType(*Comp, Exp.getDesc()->getTypeIndex());
908
0
          }
909
0
          CompCtx.addInstanceExport(InstanceIdx, Exp.getName(),
910
0
                                    ExpSort.getSortType(), IT);
911
0
        }
912
1.39k
      }
913
1.46k
    } else if (CompTy != nullptr) {
914
536
      for (const auto &CD : CompTy->getDecl()) {
915
228
        if (!CD.isInstanceDecl()) {
916
0
          continue;
917
0
        }
918
228
        const auto &ID = CD.getInstance();
919
228
        if (!ID.isExportDecl()) {
920
0
          continue;
921
0
        }
922
228
        const auto &ED = ID.getExport();
923
228
        const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType());
924
228
        if (!OptST.has_value()) {
925
0
          continue; // `(core module)` export — not a component-side entry.
926
0
        }
927
228
        CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST);
928
228
      }
929
536
    }
930
23.4k
  } else if (Inst.isInlineExport()) {
931
    // Allocate the instance first so exports can be registered on it.
932
23.4k
    uint32_t InstanceIdx = CompCtx.addInstance();
933
934
    // Check the sort index bound of the inline exports, then record each
935
    // export on the new instance with a NestedInstIdx fallback so alias
936
    // chains through inline-export instances can follow the source.
937
    // Inline-export names on a component instance must be strongly-unique.
938
23.4k
    std::unordered_set<std::string_view> SeenExports;
939
23.4k
    for (const auto &Export : Inst.getInlineExports()) {
940
2.23k
      if (!SeenExports.insert(Export.getName()).second) {
941
2
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
942
2
        spdlog::error("    Instance: Duplicate inline-export name '{}'"sv,
943
2
                      Export.getName());
944
2
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
945
2
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
946
2
      }
947
2.23k
      const auto &Sort = Export.getSortIdx().getSort();
948
2.23k
      uint32_t Idx = Export.getSortIdx().getIdx();
949
2.23k
      if (Sort.isCore()) {
950
219
        if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
951
19
          spdlog::error(ErrCode::Value::InvalidIndex);
952
19
          spdlog::error(
953
19
              "    Instance: Inline export '{}' refers to invalid index {}"sv,
954
19
              Export.getName(), Idx);
955
19
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
956
19
          return Unexpect(ErrCode::Value::InvalidIndex);
957
19
        }
958
200
        continue;
959
219
      }
960
2.01k
      if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
961
28
        spdlog::error(ErrCode::Value::InvalidIndex);
962
28
        spdlog::error(
963
28
            "    Instance: Inline export '{}' refers to invalid index {}"sv,
964
28
            Export.getName(), Idx);
965
28
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance));
966
28
        return Unexpect(ErrCode::Value::InvalidIndex);
967
28
      }
968
1.98k
      if (Sort.getSortType() == AST::Component::Sort::SortType::Type) {
969
959
        auto SubstitutedIdx =
970
959
            CompCtx.getSubstitutedType(std::string(Export.getName()));
971
959
        if (SubstitutedIdx.has_value() && Idx != SubstitutedIdx.value()) {
972
0
          spdlog::error(ErrCode::Value::InvalidTypeReference);
973
0
          spdlog::error(
974
0
              "    Instance: Inline export '{}' type index {} does not match substituted type index {}"sv,
975
0
              Export.getName(), Idx, *SubstitutedIdx);
976
0
          return Unexpect(ErrCode::Value::InvalidTypeReference);
977
0
        }
978
959
      }
979
1.98k
      std::optional<uint32_t> NestedIdx;
980
1.98k
      const AST::Component::InstanceType *PropagatedIT = nullptr;
981
1.98k
      if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
982
942
        NestedIdx = Idx;
983
        // GAP-I-5b: forward source's InstanceType so later ascription /
984
        // subtype checks have a concrete type. Populated only when the
985
        // source slot was bound via ExternDesc::InstanceType; inline-
986
        // export / instantiate sources read nullptr (Phase-3 follow-up).
987
942
        PropagatedIT = CompCtx.getInstance(Idx).Type;
988
942
      }
989
1.98k
      CompCtx.addInstanceExport(InstanceIdx, Export.getName(),
990
1.98k
                                Sort.getSortType(), PropagatedIT, NestedIdx);
991
1.98k
    }
992
23.4k
  } else {
993
0
    assumingUnreachable();
994
0
  }
995
26.1k
  return {};
996
26.2k
}
997
998
138
Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept {
999
  // CoreAlias is always an outer alias.
1000
138
  uint32_t Ct = A.getComponentJump();
1001
138
  uint32_t Idx = A.getIndex();
1002
1003
138
  uint32_t OutLinkCompCnt = 0;
1004
138
  const auto *TargetCtx = &CompCtx.getCurrentContext();
1005
172
  while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1006
34
    TargetCtx = TargetCtx->Parent;
1007
34
    OutLinkCompCnt++;
1008
34
  }
1009
138
  if (TargetCtx == nullptr) {
1010
8
    spdlog::error(ErrCode::Value::InvalidIndex);
1011
    // The final hop is the one that ran off the top of the scope chain, so the
1012
    // number of actually-enclosing components is one less than the hop count.
1013
8
    spdlog::error(
1014
8
        "    CoreAlias: outer count {} exceeds enclosing component count {}"sv,
1015
8
        Ct, OutLinkCompCnt - 1);
1016
8
    return Unexpect(ErrCode::Value::InvalidIndex);
1017
8
  }
1018
1019
130
  const auto &Sort = A.getSort();
1020
130
  if (Sort.isCore()) {
1021
130
    if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1022
22
      spdlog::error(ErrCode::Value::InvalidIndex);
1023
22
      spdlog::error("    CoreAlias: outer index {} out of bounds"sv, Idx);
1024
22
      return Unexpect(ErrCode::Value::InvalidIndex);
1025
22
    }
1026
108
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1027
108
  }
1028
108
  return {};
1029
130
}
1030
1031
5.74k
Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept {
1032
5.74k
  const auto &Sort = Alias.getSort();
1033
5.74k
  switch (Alias.getTargetType()) {
1034
1.23k
  case AST::Component::Alias::TargetType::Export: {
1035
1.23k
    const auto Idx = Alias.getExport().first;
1036
1.23k
    const auto &Name = Alias.getExport().second;
1037
1038
1.23k
    if (Sort.isCore()) {
1039
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1040
2
      spdlog::error("    Alias export: Mapping an export '{}' to core:sort"sv,
1041
2
                    Name);
1042
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1043
2
    }
1044
1045
1.23k
    if (Idx >=
1046
1.23k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) {
1047
28
      spdlog::error(ErrCode::Value::InvalidIndex);
1048
28
      spdlog::error(
1049
28
          "    Alias export: Export index {} exceeds available component instance index {}"sv,
1050
28
          Idx,
1051
28
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance));
1052
28
      return Unexpect(ErrCode::Value::InvalidIndex);
1053
28
    }
1054
1055
1.20k
    const auto &InstExports = CompCtx.getInstance(Idx).Exports;
1056
1.20k
    auto It = InstExports.find(std::string(Name));
1057
1.20k
    if (It == InstExports.cend()) {
1058
19
      spdlog::error(ErrCode::Value::ExportNotFound);
1059
19
      spdlog::error(
1060
19
          "    Alias export: No matching export '{}' found in component instance index {}"sv,
1061
19
          Name, Idx);
1062
19
      return Unexpect(ErrCode::Value::ExportNotFound);
1063
19
    }
1064
1065
1.18k
    if (It->second.ST != Sort.getSortType()) {
1066
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1067
2
      spdlog::error("    Alias export: Type mapping mismatch for export '{}'"sv,
1068
2
                    Name);
1069
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1070
2
    }
1071
1.18k
    return {};
1072
1.18k
  }
1073
353
  case AST::Component::Alias::TargetType::CoreExport: {
1074
353
    const auto Idx = Alias.getExport().first;
1075
353
    const auto &Name = Alias.getExport().second;
1076
1077
353
    if (!Sort.isCore()) {
1078
6
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1079
6
      spdlog::error("    Alias core:export: Mapping a export '{}' to sort"sv,
1080
6
                    Name);
1081
6
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1082
6
    }
1083
1084
347
    if (Idx >= CompCtx.getCoreSortIndexSize(
1085
347
                   AST::Component::Sort::CoreSortType::Instance)) {
1086
26
      spdlog::error(ErrCode::Value::InvalidIndex);
1087
26
      spdlog::error(
1088
26
          "    Alias core:export: Export index {} exceeds available core instance index {}"sv,
1089
26
          Idx,
1090
26
          CompCtx.getCoreSortIndexSize(
1091
26
              AST::Component::Sort::CoreSortType::Instance) -
1092
26
              1);
1093
26
      return Unexpect(ErrCode::Value::InvalidIndex);
1094
26
    }
1095
1096
321
    const auto &CoreExports = CompCtx.getCoreInstance(Idx);
1097
321
    auto It = CoreExports.find(std::string(Name));
1098
321
    if (It == CoreExports.end()) {
1099
6
      spdlog::error(ErrCode::Value::ExportNotFound);
1100
6
      spdlog::error(
1101
6
          "    Alias core:export: No matching export '{}' found in core instance index {}"sv,
1102
6
          Name, Idx);
1103
6
      return Unexpect(ErrCode::Value::ExportNotFound);
1104
6
    }
1105
1106
315
    const auto ExternTy = It->second;
1107
315
    AST::Component::Sort::CoreSortType ST;
1108
315
    switch (ExternTy) {
1109
315
    case ExternalType::Function:
1110
315
      ST = AST::Component::Sort::CoreSortType::Func;
1111
315
      break;
1112
0
    case ExternalType::Table:
1113
0
      ST = AST::Component::Sort::CoreSortType::Table;
1114
0
      break;
1115
0
    case ExternalType::Memory:
1116
0
      ST = AST::Component::Sort::CoreSortType::Memory;
1117
0
      break;
1118
0
    case ExternalType::Global:
1119
0
      ST = AST::Component::Sort::CoreSortType::Global;
1120
0
      break;
1121
0
    case ExternalType::Tag:
1122
0
      ST = AST::Component::Sort::CoreSortType::Tag;
1123
0
      break;
1124
0
    default:
1125
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1126
0
      spdlog::error(
1127
0
          "    Alias core:export: Type mapping mismatch for export '{}'"sv,
1128
0
          Name);
1129
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1130
315
    }
1131
315
    if (ST != Sort.getCoreSortType()) {
1132
      // The error message differs of the tag core sort.
1133
2
      ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex;
1134
2
      if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) {
1135
1
        ErrValue = ErrCode::Value::UnknownCoreTag;
1136
1
      }
1137
2
      spdlog::error(ErrValue);
1138
2
      spdlog::error(
1139
2
          "    Alias core:export: Type mapping mismatch for export '{}'"sv,
1140
2
          Name);
1141
2
      return Unexpect(ErrValue);
1142
2
    }
1143
313
    return {};
1144
315
  }
1145
4.16k
  case AST::Component::Alias::TargetType::Outer: {
1146
4.16k
    const auto Ct = Alias.getOuter().first;
1147
4.16k
    const auto Idx = Alias.getOuter().second;
1148
1149
4.16k
    uint32_t OutLinkCompCnt = 0;
1150
4.16k
    const auto *TargetCtx = &CompCtx.getCurrentContext();
1151
4.61k
    while (Ct > OutLinkCompCnt && TargetCtx != nullptr) {
1152
453
      TargetCtx = TargetCtx->Parent;
1153
453
      OutLinkCompCnt++;
1154
453
    }
1155
4.16k
    if (TargetCtx == nullptr) {
1156
46
      spdlog::error(ErrCode::Value::InvalidIndex);
1157
      // The final hop is the one that ran off the top of the scope chain, so
1158
      // the number of actually-enclosing components is one less than the hop
1159
      // count.
1160
46
      spdlog::error(
1161
46
          "    Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv,
1162
46
          Ct, OutLinkCompCnt - 1);
1163
46
      return Unexpect(ErrCode::Value::InvalidIndex);
1164
46
    }
1165
1166
4.11k
    if (Sort.isCore()) {
1167
344
      if (Sort.getCoreSortType() !=
1168
344
              AST::Component::Sort::CoreSortType::Module &&
1169
336
          Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) {
1170
1
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1171
1
        spdlog::error(
1172
1
            "    Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv);
1173
1
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1174
1
      }
1175
343
      if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) {
1176
12
        spdlog::error(ErrCode::Value::InvalidIndex);
1177
12
        spdlog::error(
1178
12
            "    Alias outer: core:sort index {} invalid in component context"sv,
1179
12
            Idx);
1180
12
        return Unexpect(ErrCode::Value::InvalidIndex);
1181
12
      }
1182
3.77k
    } else {
1183
3.77k
      if (Sort.getSortType() != AST::Component::Sort::SortType::Type &&
1184
430
          Sort.getSortType() != AST::Component::Sort::SortType::Component) {
1185
5
        spdlog::error(ErrCode::Value::InvalidTypeReference);
1186
5
        spdlog::error(
1187
5
            "    Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv);
1188
5
        return Unexpect(ErrCode::Value::InvalidTypeReference);
1189
5
      }
1190
3.76k
      if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) {
1191
6
        spdlog::error(ErrCode::Value::InvalidIndex);
1192
6
        spdlog::error(
1193
6
            "    Alias outer: sort index {} invalid in component context"sv,
1194
6
            Idx);
1195
6
        return Unexpect(ErrCode::Value::InvalidIndex);
1196
6
      }
1197
      // Outer-aliasing a resource type is permitted: the alias preserves the
1198
      // resource's type identity rather than introducing a fresh generative
1199
      // resource, so a nested type/component can legitimately refer to a
1200
      // resource in an enclosing scope (e.g. an instance type referring to a
1201
      // resource imported by its enclosing component type).
1202
      //
1203
      // TODO: implement wasm-tools' full free-variable rule — reject an outer
1204
      // alias to a type whose transitive free variables include resources that
1205
      // would escape a crossed component boundary. That needs a free-variable
1206
      // walker over type bodies, tracked alongside the Phase 3/4 type-handle
1207
      // work (GAP-TH-1, GAP-EX-5).
1208
3.76k
    }
1209
4.09k
    return {};
1210
4.11k
  }
1211
0
  default:
1212
0
    assumingUnreachable();
1213
5.74k
  }
1214
5.74k
}
1215
1216
Expect<void>
1217
252k
Validator::validate(const AST::Component::CoreDefType &DType) noexcept {
1218
252k
  if (DType.isRecType()) {
1219
    // Each sub-type in the rec group gets its own entry in core:type.
1220
140k
    for (const auto &ST : DType.getSubTypes()) {
1221
139k
      CompCtx.addCoreType(&ST);
1222
139k
    }
1223
140k
  } else if (DType.isModuleType()) {
1224
    // Module types are validated with an initially-empty type index space.
1225
112k
    CompCtx.enterTypeDefinition();
1226
112k
    for (const auto &Decl : DType.getModuleType()) {
1227
1.44k
      EXPECTED_TRY(validate(Decl).map_error([](auto E) {
1228
1.44k
        spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType));
1229
1.44k
        return E;
1230
1.44k
      }));
1231
1.44k
    }
1232
112k
    CompCtx.exitComponent();
1233
    // Module type gets a core:type slot (Body=nullptr) bound to the
1234
    // CoreDefType so (core module (type i)) can recover the body — GAP-CI-1.
1235
112k
    uint32_t NewTypeIdx = CompCtx.addCoreType();
1236
112k
    CompCtx.setCoreModuleType(NewTypeIdx, &DType);
1237
112k
  } else {
1238
0
    assumingUnreachable();
1239
0
  }
1240
252k
  return {};
1241
252k
}
1242
1243
Expect<void>
1244
1.19M
Validator::validate(const AST::Component::DefType &DType) noexcept {
1245
1.19M
  auto ReportError = [](auto E) {
1246
358
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
1247
358
    return E;
1248
358
  };
1249
1250
1.19M
  if (DType.isDefValType()) {
1251
70.5k
    EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError));
1252
1.12M
  } else if (DType.isFuncType()) {
1253
9.94k
    EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError));
1254
1.11M
  } else if (DType.isComponentType()) {
1255
754k
    EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError));
1256
754k
  } else if (DType.isInstanceType()) {
1257
348k
    EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError));
1258
348k
  } else if (DType.isResourceType()) {
1259
16.5k
    EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError));
1260
16.5k
  } else {
1261
0
    assumingUnreachable();
1262
0
  }
1263
  // addType records body/id/locality for resource DefTypes in one step.
1264
1.19M
  CompCtx.addType(&DType);
1265
1.19M
  return {};
1266
1.19M
}
1267
1268
Expect<void>
1269
7.44k
Validator::validate(const AST::Component::Canonical &Canon) noexcept {
1270
7.44k
  switch (Canon.getOpCode()) {
1271
3.54k
  case ComponentCanonOpCode::Lift:
1272
3.54k
    return validateCanonLift(Canon);
1273
775
  case ComponentCanonOpCode::Lower:
1274
775
    return validateCanonLower(Canon);
1275
979
  case ComponentCanonOpCode::Resource__new:
1276
979
    return validateCanonResourceNew(Canon);
1277
415
  case ComponentCanonOpCode::Resource__rep:
1278
415
    return validateCanonResourceRep(Canon);
1279
1.46k
  case ComponentCanonOpCode::Resource__drop:
1280
1.72k
  case ComponentCanonOpCode::Resource__drop_async:
1281
1.72k
    return validateCanonResourceDrop(Canon);
1282
16
  default:
1283
16
    spdlog::error(ErrCode::Value::ComponentNotImplValidator);
1284
16
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1285
16
    return Unexpect(ErrCode::Value::ComponentNotImplValidator);
1286
7.44k
  }
1287
7.44k
}
1288
1289
Expect<void> Validator::validateCanonOptions(
1290
    ComponentCanonOpCode Code,
1291
7.25k
    Span<const AST::Component::CanonOpt> Opts) noexcept {
1292
7.25k
  using OptCode = ComponentCanonOptCode;
1293
7.25k
  using CanonOp = ComponentCanonOpCode;
1294
1295
  // Only canon lift/lower accept canonical options. Any other built-in
1296
  // (resource.new/rep/drop, drop_async, ...) must be invoked without options.
1297
7.25k
  if (Code != CanonOp::Lift && Code != CanonOp::Lower && !Opts.empty()) {
1298
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1299
0
    spdlog::error(
1300
0
        "    canonical options are not allowed for this canon built-in"sv);
1301
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1302
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1303
0
  }
1304
1305
7.25k
  bool HasEncoding = false;
1306
7.25k
  bool HasMemory = false;
1307
7.25k
  bool HasRealloc = false;
1308
7.25k
  bool HasPostReturn = false;
1309
7.25k
  bool HasAsync = false;
1310
7.25k
  bool HasCallback = false;
1311
7.25k
  bool HasAlwaysTaskReturn = false;
1312
7.25k
  uint32_t ReallocIdx = 0;
1313
7.25k
  uint32_t CallbackIdx = 0;
1314
7.25k
  uint32_t PostReturnIdx = 0;
1315
7.25k
  uint32_t MemoryIdx = 0;
1316
1317
7.25k
  auto RejectDup = [&](const char *Name) -> Expect<void> {
1318
10
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1319
10
    spdlog::error("    canonical option '{}' appears more than once"sv, Name);
1320
10
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1321
10
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1322
10
  };
1323
7.25k
  auto RejectSite = [&](const char *Name) -> Expect<void> {
1324
4
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1325
4
    spdlog::error(
1326
4
        "    canonical option '{}' is not allowed in this canon built-in"sv,
1327
4
        Name);
1328
4
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1329
4
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1330
4
  };
1331
1332
7.25k
  for (const auto &Opt : Opts) {
1333
800
    switch (Opt.getCode()) {
1334
334
    case OptCode::Encode_UTF8:
1335
619
    case OptCode::Encode_UTF16:
1336
676
    case OptCode::Encode_Latin1:
1337
676
      if (HasEncoding) {
1338
5
        return RejectDup("string-encoding");
1339
5
      }
1340
671
      HasEncoding = true;
1341
671
      break;
1342
9
    case OptCode::Memory:
1343
9
      if (HasMemory) {
1344
1
        return RejectDup("memory");
1345
1
      }
1346
8
      HasMemory = true;
1347
8
      MemoryIdx = Opt.getIndex();
1348
8
      break;
1349
5
    case OptCode::Realloc:
1350
5
      if (HasRealloc) {
1351
1
        return RejectDup("realloc");
1352
1
      }
1353
4
      HasRealloc = true;
1354
4
      ReallocIdx = Opt.getIndex();
1355
4
      break;
1356
1
    case OptCode::PostReturn:
1357
1
      if (Code != CanonOp::Lift) {
1358
1
        return RejectSite("post-return");
1359
1
      }
1360
0
      if (HasPostReturn) {
1361
0
        return RejectDup("post-return");
1362
0
      }
1363
0
      HasPostReturn = true;
1364
0
      PostReturnIdx = Opt.getIndex();
1365
0
      break;
1366
99
    case OptCode::Async:
1367
99
      if (HasAsync) {
1368
2
        return RejectDup("async");
1369
2
      }
1370
97
      HasAsync = true;
1371
97
      break;
1372
3
    case OptCode::Callback:
1373
3
      if (Code != CanonOp::Lift) {
1374
2
        return RejectSite("callback");
1375
2
      }
1376
1
      if (HasCallback) {
1377
0
        return RejectDup("callback");
1378
0
      }
1379
1
      HasCallback = true;
1380
1
      CallbackIdx = Opt.getIndex();
1381
1
      break;
1382
7
    case OptCode::AlwaysTaskReturn:
1383
7
      if (Code != CanonOp::Lift) {
1384
1
        return RejectSite("always-task-return");
1385
1
      }
1386
6
      if (HasAlwaysTaskReturn) {
1387
1
        return RejectDup("always-task-return");
1388
1
      }
1389
5
      HasAlwaysTaskReturn = true;
1390
5
      break;
1391
0
    default:
1392
0
      spdlog::error(ErrCode::Value::UnknownCanonicalOption);
1393
0
      spdlog::error("    unknown canonical option code 0x{:02x}"sv,
1394
0
                    static_cast<unsigned>(Opt.getCode()));
1395
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1396
0
      return Unexpect(ErrCode::Value::UnknownCanonicalOption);
1397
800
    }
1398
800
  }
1399
1400
  // Structural rules.
1401
7.24k
  if (HasPostReturn && HasAsync) {
1402
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1403
0
    spdlog::error(
1404
0
        "    canonical options 'post-return' and 'async' are mutually exclusive"sv);
1405
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1406
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1407
0
  }
1408
7.24k
  if (HasCallback && !HasAsync) {
1409
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1410
0
    spdlog::error("    canonical option 'callback' requires 'async'"sv);
1411
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1412
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1413
0
  }
1414
7.24k
  if (HasAlwaysTaskReturn && !HasAsync) {
1415
0
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1416
0
    spdlog::error(
1417
0
        "    canonical option 'always-task-return' requires 'async'"sv);
1418
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1419
0
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1420
0
  }
1421
7.24k
  if (HasRealloc && !HasMemory) {
1422
1
    spdlog::error(ErrCode::Value::InvalidCanonOption);
1423
1
    spdlog::error(
1424
1
        "    canonical option 'realloc' requires 'memory' to also be specified"sv);
1425
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1426
1
    return Unexpect(ErrCode::Value::InvalidCanonOption);
1427
1
  }
1428
1429
  // Index bounds checks. Core func signature body checks (realloc/callback)
1430
  // deferred as GAP-C-5b once getCoreFunc signatures are populated.
1431
7.24k
  if (HasMemory &&
1432
6
      MemoryIdx >= CompCtx.getCoreSortIndexSize(
1433
6
                       AST::Component::Sort::CoreSortType::Memory)) {
1434
6
    spdlog::error(ErrCode::Value::InvalidIndex);
1435
6
    spdlog::error(
1436
6
        "    canonical option 'memory': core memory index {} out of bounds"sv,
1437
6
        MemoryIdx);
1438
6
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1439
6
    return Unexpect(ErrCode::Value::InvalidIndex);
1440
6
  }
1441
7.23k
  const uint32_t CoreFuncSpaceSize =
1442
7.23k
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1443
7.23k
  if (HasRealloc && ReallocIdx >= CoreFuncSpaceSize) {
1444
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1445
0
    spdlog::error(
1446
0
        "    canonical option 'realloc': core func index {} out of bounds"sv,
1447
0
        ReallocIdx);
1448
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1449
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1450
0
  }
1451
7.23k
  if (HasCallback && CallbackIdx >= CoreFuncSpaceSize) {
1452
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1453
0
    spdlog::error(
1454
0
        "    canonical option 'callback': core func index {} out of bounds"sv,
1455
0
        CallbackIdx);
1456
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1457
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1458
0
  }
1459
7.23k
  if (HasPostReturn && PostReturnIdx >= CoreFuncSpaceSize) {
1460
0
    spdlog::error(ErrCode::Value::InvalidIndex);
1461
0
    spdlog::error(
1462
0
        "    canonical option 'post-return': core func index {} out of bounds"sv,
1463
0
        PostReturnIdx);
1464
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1465
0
    return Unexpect(ErrCode::Value::InvalidIndex);
1466
0
  }
1467
7.23k
  return {};
1468
7.23k
}
1469
1470
Expect<void>
1471
3.54k
Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept {
1472
3.54k
  const uint32_t CoreFuncIdx = Canon.getIndex();
1473
3.54k
  const uint32_t CoreFuncSpaceSize =
1474
3.54k
      CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func);
1475
  // 1. Core func index bounds.
1476
3.54k
  if (CoreFuncIdx >= CoreFuncSpaceSize) {
1477
20
    spdlog::error(ErrCode::Value::InvalidIndex);
1478
20
    spdlog::error(
1479
20
        "    canon lift: core func index {} exceeds core func index space size {}"sv,
1480
20
        CoreFuncIdx, CoreFuncSpaceSize);
1481
20
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1482
20
    return Unexpect(ErrCode::Value::InvalidIndex);
1483
20
  }
1484
3.52k
  const uint32_t TypeIdx = Canon.getTargetIndex();
1485
3.52k
  const uint32_t TypeSpaceSize =
1486
3.52k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1487
  // 2. Target type index bounds.
1488
3.52k
  if (TypeIdx >= TypeSpaceSize) {
1489
6
    spdlog::error(ErrCode::Value::InvalidIndex);
1490
6
    spdlog::error(
1491
6
        "    canon lift: type index {} exceeds type index space size {}"sv,
1492
6
        TypeIdx, TypeSpaceSize);
1493
6
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1494
6
    return Unexpect(ErrCode::Value::InvalidIndex);
1495
6
  }
1496
  // 3. Target type must be a component FuncType.
1497
3.51k
  const auto *DT = CompCtx.getDefType(TypeIdx);
1498
3.51k
  if (DT == nullptr) {
1499
    // Unresolved slot: the index was registered by an import or outer alias
1500
    // but the concrete definition has not been filled in yet.
1501
1
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1502
1
    spdlog::error(
1503
1
        "    canon lift: target type index {} is an unresolved type slot"sv,
1504
1
        TypeIdx);
1505
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1506
1
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1507
1
  }
1508
3.51k
  if (!DT->isFuncType()) {
1509
3
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1510
3
    spdlog::error(
1511
3
        "    canon lift: target type index {} does not reference a component func type"sv,
1512
3
        TypeIdx);
1513
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1514
3
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1515
3
  }
1516
  // 4. Validate canonical options (Lift site allows all).
1517
3.51k
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1518
  // 5. Allocate component func, binding the resolved FuncType. Full ABI
1519
  // signature match (flat_lifted) deferred as GAP-C-1b.
1520
3.50k
  CompCtx.addFunc(&DT->getFuncType());
1521
3.50k
  return {};
1522
3.51k
}
1523
1524
Expect<void>
1525
775
Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept {
1526
775
  const uint32_t FuncIdx = Canon.getIndex();
1527
775
  const uint32_t FuncSpaceSize =
1528
775
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func);
1529
  // 1. Component func index bounds.
1530
775
  if (FuncIdx >= FuncSpaceSize) {
1531
10
    spdlog::error(ErrCode::Value::InvalidIndex);
1532
10
    spdlog::error(
1533
10
        "    canon lower: component func index {} exceeds func index space size {}"sv,
1534
10
        FuncIdx, FuncSpaceSize);
1535
10
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1536
10
    return Unexpect(ErrCode::Value::InvalidIndex);
1537
10
  }
1538
  // 2. Validate canonical options (per-site rules for Lower).
1539
765
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1540
  // 3. Allocate the resulting core func. Full ABI signature synthesis
1541
  // (flatten_functype for lower) deferred as GAP-C-2b.
1542
750
  CompCtx.addCoreFunc();
1543
750
  return {};
1544
765
}
1545
1546
Expect<void> Validator::validateCanonResourceNew(
1547
979
    const AST::Component::Canonical &Canon) noexcept {
1548
979
  const uint32_t Idx = Canon.getIndex();
1549
979
  const uint32_t TypeSpaceSize =
1550
979
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1551
  // 1. Type index bounds.
1552
979
  if (Idx >= TypeSpaceSize) {
1553
19
    spdlog::error(ErrCode::Value::InvalidIndex);
1554
19
    spdlog::error(
1555
19
        "    canon resource.new: type index {} exceeds type index space size {}"sv,
1556
19
        Idx, TypeSpaceSize);
1557
19
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1558
19
    return Unexpect(ErrCode::Value::InvalidIndex);
1559
19
  }
1560
  // 2. Type must be a locally-defined resource.
1561
960
  const auto *RInfo = CompCtx.getResource(Idx);
1562
960
  if (RInfo == nullptr) {
1563
17
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1564
17
    spdlog::error(
1565
17
        "    canon resource.new: type index {} does not reference a resource"sv,
1566
17
        Idx);
1567
17
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1568
17
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1569
17
  }
1570
943
  if (!RInfo->LocallyDefined) {
1571
3
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1572
3
    spdlog::error(
1573
3
        "    canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1574
3
        Idx);
1575
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1576
3
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1577
3
  }
1578
  // 4. Validate canonical options.
1579
940
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1580
  // 5. Allocate the resulting core func with synthesized signature
1581
  // [i32] -> [i32] (rep i32 in, new handle out).
1582
940
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1583
940
  return {};
1584
940
}
1585
1586
Expect<void> Validator::validateCanonResourceRep(
1587
415
    const AST::Component::Canonical &Canon) noexcept {
1588
415
  const uint32_t Idx = Canon.getIndex();
1589
415
  const uint32_t TypeSpaceSize =
1590
415
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1591
  // 1. Type index bounds.
1592
415
  if (Idx >= TypeSpaceSize) {
1593
27
    spdlog::error(ErrCode::Value::InvalidIndex);
1594
27
    spdlog::error(
1595
27
        "    canon resource.rep: type index {} exceeds type index space size {}"sv,
1596
27
        Idx, TypeSpaceSize);
1597
27
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1598
27
    return Unexpect(ErrCode::Value::InvalidIndex);
1599
27
  }
1600
  // 2. Type must be a locally-defined resource.
1601
388
  const auto *RInfo = CompCtx.getResource(Idx);
1602
388
  if (RInfo == nullptr) {
1603
22
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1604
22
    spdlog::error(
1605
22
        "    canon resource.rep: type index {} does not reference a resource"sv,
1606
22
        Idx);
1607
22
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1608
22
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1609
22
  }
1610
366
  if (!RInfo->LocallyDefined) {
1611
3
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1612
3
    spdlog::error(
1613
3
        "    canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv,
1614
3
        Idx);
1615
3
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1616
3
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1617
3
  }
1618
  // 4. Validate canonical options.
1619
363
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1620
  // 5. Allocate the resulting core func with synthesized signature
1621
  // [i32] -> [i32] (handle in, rep out).
1622
363
  CompCtx.addCoreFunc(&CoreFuncType_I32_I32);
1623
363
  return {};
1624
363
}
1625
1626
Expect<void> Validator::validateCanonResourceDrop(
1627
1.72k
    const AST::Component::Canonical &Canon) noexcept {
1628
1.72k
  const uint32_t Idx = Canon.getIndex();
1629
1.72k
  const uint32_t TypeSpaceSize =
1630
1.72k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1631
  // 1. Type index bounds.
1632
1.72k
  if (Idx >= TypeSpaceSize) {
1633
33
    spdlog::error(ErrCode::Value::InvalidIndex);
1634
33
    spdlog::error(
1635
33
        "    canon resource.drop: type index {} exceeds type index space size {}"sv,
1636
33
        Idx, TypeSpaceSize);
1637
33
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1638
33
    return Unexpect(ErrCode::Value::InvalidIndex);
1639
33
  }
1640
  // 2. Type must be a resource type.
1641
1.68k
  if (CompCtx.getResource(Idx) == nullptr) {
1642
14
    spdlog::error(ErrCode::Value::InvalidTypeReference);
1643
14
    spdlog::error(
1644
14
        "    canon resource.drop: type index {} does not reference a resource"sv,
1645
14
        Idx);
1646
14
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical));
1647
14
    return Unexpect(ErrCode::Value::InvalidTypeReference);
1648
14
  }
1649
  // 3. resource.drop accepts both local and imported resources — no locality
1650
  // check.
1651
  // 4. Validate canonical options.
1652
1.67k
  EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions()));
1653
  // 5. Allocate the resulting core func with synthesized signature
1654
  // [i32] -> [] (handle in, no result — matches the resource destructor
1655
  // shape required by validate(ResourceType)).
1656
1.67k
  CompCtx.addCoreFunc(&CoreFuncType_I32_Void);
1657
1.67k
  return {};
1658
1.67k
}
1659
1660
5.40k
Expect<void> Validator::validate(const AST::Component::Import &Im) noexcept {
1661
  // Validation steps:
1662
  //   1. Validate the externdesc and introduce the imported entity into
1663
  //      its sort's index space.
1664
  //   2. Parse the import name per the structured import-name grammar.
1665
  //   3. Enforce the annotated-name constraints ([constructor]/[method]/
1666
  //      [static] only on func imports).
1667
  //   4. Reject duplicate import names (strongly-unique across imports).
1668
  //
1669
  // The per-annotated-name structural checks (constructor result type,
1670
  // method `self` param, static resource name in scope) are not yet
1671
  // implemented and tracked as follow-ups below.
1672
  // Snapshot the type space size before validating the desc so we can
1673
  // recover the new type index allocated by a TypeBound (sub resource).
1674
5.40k
  const uint32_t TypeSpaceBefore =
1675
5.40k
      CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1676
1677
5.40k
  EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) {
1678
5.34k
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1679
5.34k
    return E;
1680
5.34k
  }));
1681
1682
10.1k
  EXPECTED_TRY(ComponentName CName,
1683
10.1k
               ComponentName::parse(Im.getName()).map_error([](auto E) {
1684
10.1k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1685
10.1k
                 return E;
1686
10.1k
               }));
1687
1688
  // Annotated plainnames ([constructor], [method], [static]) can only appear
1689
  // on func imports.
1690
10.1k
  switch (CName.getKind()) {
1691
6
  case ComponentNameKind::Constructor:
1692
11
  case ComponentNameKind::Method:
1693
20
  case ComponentNameKind::Static:
1694
20
    if (Im.getDesc().getDescType() !=
1695
20
        AST::Component::ExternDesc::DescType::FuncType) {
1696
20
      spdlog::error(ErrCode::Value::ComponentInvalidName);
1697
20
      spdlog::error("    Import: annotated name requires func type"sv);
1698
20
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1699
20
      return Unexpect(ErrCode::Value::ComponentInvalidName);
1700
20
    }
1701
0
    break;
1702
4.77k
  default:
1703
4.77k
    break;
1704
10.1k
  }
1705
1706
  // Resolve the resource name referenced by an annotated name. The resource
1707
  // must have been previously introduced by a TypeBound import or export
1708
  // with a kebab-case label in this scope.
1709
  // TODO: extend to the full structural checks once func-type bodies are
1710
  // walked here:
1711
  //   - [constructor]R: result type must be (own $R).
1712
  //   - [method]R.f:    first param must be (borrow $R).
1713
  //   - [static]R.f:    no `self` param of (borrow $R).
1714
4.77k
  std::string_view ResourceLabel;
1715
4.77k
  switch (CName.getKind()) {
1716
0
  case ComponentNameKind::Constructor:
1717
0
    ResourceLabel = CName.getDetail().get<ConstructorDetail>().Label;
1718
0
    break;
1719
0
  case ComponentNameKind::Method:
1720
0
    ResourceLabel = CName.getDetail().get<MethodDetail>().Resource;
1721
0
    break;
1722
0
  case ComponentNameKind::Static:
1723
0
    ResourceLabel = CName.getDetail().get<StaticDetail>().Resource;
1724
0
    break;
1725
4.77k
  default:
1726
4.77k
    break;
1727
4.77k
  }
1728
4.77k
  if (!ResourceLabel.empty() && !CompCtx.hasResourceLabel(ResourceLabel)) {
1729
0
    spdlog::error(ErrCode::Value::ComponentInvalidName);
1730
0
    spdlog::error(
1731
0
        "    Import: annotated name references unknown resource '{}'"sv,
1732
0
        ResourceLabel);
1733
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1734
0
    return Unexpect(ErrCode::Value::ComponentInvalidName);
1735
0
  }
1736
1737
4.77k
  if (!CompCtx.addImportedName(CName)) {
1738
68
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1739
68
    spdlog::error("    Import: Duplicate import name"sv);
1740
68
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import));
1741
68
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1742
68
  }
1743
1744
  // If this import introduced a TypeBound resource with a label name,
1745
  // register the label so subsequent annotated names can reference it.
1746
4.70k
  if (Im.getDesc().getDescType() ==
1747
4.70k
          AST::Component::ExternDesc::DescType::TypeBound &&
1748
2.08k
      CName.getKind() == ComponentNameKind::Label) {
1749
2.05k
    CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore);
1750
2.05k
  }
1751
1752
4.70k
  return {};
1753
4.77k
}
1754
1755
705
Expect<void> Validator::validate(const AST::Component::Export &Ex) noexcept {
1756
  // Validation steps:
1757
  //   1. `sortidx` is in-bounds and (for core sorts) must be `core module`.
1758
  //   2. If an `externdesc` ascription is present, it must be a supertype
1759
  //      of the inferred externdesc of the `sortidx` (kind-only check for
1760
  //      now; structural subtype is a follow-up).
1761
  //   3. The export name parses under the export-name grammar.
1762
  //   4. The export name is strongly-unique across exports.
1763
  //   5. The export introduces a new index aliasing the definition in the
1764
  //      component's own index space for its sort.
1765
  //
1766
  // Not yet enforced: transitive resource-avoidance in exported types;
1767
  // flipping the "consumed" flag for value exports.
1768
1769
  // Validate the sortidx bounds.
1770
705
  const auto &Sort = Ex.getSortIndex().getSort();
1771
705
  uint32_t Idx = Ex.getSortIndex().getIdx();
1772
705
  if (Sort.isCore()) {
1773
    // The externdesc grammar permits only `core module` as a component-level
1774
    // core export — no other core sort is exportable from a component.
1775
6
    if (Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Module) {
1776
2
      spdlog::error(ErrCode::Value::InvalidTypeReference);
1777
2
      spdlog::error(
1778
2
          "    Export: core sort other than `core module` is not allowed"sv);
1779
2
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1780
2
      return Unexpect(ErrCode::Value::InvalidTypeReference);
1781
2
    }
1782
4
    if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) {
1783
3
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1784
3
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1785
3
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1786
3
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1787
3
    }
1788
699
  } else {
1789
699
    if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) {
1790
9
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
1791
9
      spdlog::error("    Export: sort index {} out of bounds"sv, Idx);
1792
9
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1793
9
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
1794
9
    }
1795
699
  }
1796
1797
  // If an externdesc ascription is present, the spec requires it to be a
1798
  // supertype of the inferred externdesc of the sortidx. For now we only
1799
  // enforce that the ascription's kind matches the sortidx's sort; full
1800
  // structural subtype between ascription and inferred type is not yet
1801
  // implemented.
1802
691
  if (Ex.getDesc().has_value() &&
1803
12
      !sortMatchesDescType(Sort, Ex.getDesc()->getDescType())) {
1804
1
    spdlog::error(ErrCode::Value::ExportAscriptionIncompatible);
1805
1
    spdlog::error(
1806
1
        "    Export: ascribed externdesc kind does not match sortidx sort"sv);
1807
1
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1808
1
    return Unexpect(ErrCode::Value::ExportAscriptionIncompatible);
1809
1
  }
1810
1811
  // Validate name grammar, then enforce strong-uniqueness across exports.
1812
1.31k
  EXPECTED_TRY(ComponentName CName,
1813
1.31k
               validateExportName(Ex.getName()).map_error([](auto E) {
1814
1.31k
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1815
1.31k
                 return E;
1816
1.31k
               }));
1817
1.31k
  if (!CompCtx.addExportedName(CName)) {
1818
16
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
1819
16
    spdlog::error("    Export: Duplicate export name '{}'"sv, Ex.getName());
1820
16
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1821
16
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
1822
16
  }
1823
1824
  // Every export introduces a new index that aliases the exported
1825
  // definition in the component's own index space for that sort. For
1826
  // instance-sort exports we also copy/build the export table so that a
1827
  // later alias-export against this slot can resolve its sub-exports.
1828
609
  if (Sort.isCore()) {
1829
0
    CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
1830
609
  } else {
1831
609
    const AST::Component::InstanceType *IT = nullptr;
1832
609
    const bool IsInst =
1833
609
        Sort.getSortType() == AST::Component::Sort::SortType::Instance;
1834
609
    const bool HasInstAscription =
1835
609
        IsInst && Ex.getDesc().has_value() &&
1836
0
        Ex.getDesc()->getDescType() ==
1837
0
            AST::Component::ExternDesc::DescType::InstanceType;
1838
609
    if (HasInstAscription) {
1839
0
      IT = CompCtx.getInstanceType(Ex.getDesc()->getTypeIndex());
1840
0
      if (IT != nullptr) {
1841
0
        if (auto Missing = findMissingRequiredExport(Idx, *IT)) {
1842
0
          spdlog::error(ErrCode::Value::ExportAscriptionIncompatible);
1843
0
          spdlog::error(
1844
0
              "    Export: ascribed instance type requires export '{}' "
1845
0
              "not present in inferred type"sv,
1846
0
              *Missing);
1847
0
          spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export));
1848
0
          return Unexpect(ErrCode::Value::ExportAscriptionIncompatible);
1849
0
        }
1850
0
      }
1851
0
    }
1852
609
    uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType());
1853
609
    if (IsInst) {
1854
4
      if (IT != nullptr) {
1855
0
        populateInstanceFromType(NewIdx, *IT);
1856
4
      } else {
1857
        // Either no ascription, or the ascription's type index didn't
1858
        // resolve to an inline InstanceType here (cross-scope type-index
1859
        // resolution is not yet implemented). Copy the source instance's
1860
        // inferred exports so a later alias-export on this slot can still
1861
        // find them.
1862
4
        const auto &SrcExports = CompCtx.getInstance(Idx).Exports;
1863
4
        for (const auto &[Name, IE] : SrcExports) {
1864
1
          CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT,
1865
1
                                    IE.NestedInstIdx);
1866
1
        }
1867
4
      }
1868
4
    }
1869
609
  }
1870
609
  return {};
1871
609
}
1872
1873
Expect<void>
1874
5.99k
Validator::validate(const AST::Component::ExternDesc &Desc) noexcept {
1875
  // Validating an externdesc introduces a new entry into the index space
1876
  // matching the descriptor's sort:
1877
  //   * core module (CoreType) → core:module
1878
  //   * func                   → func
1879
  //   * value                  → value (consumed=false; not yet tracked)
1880
  //   * type (eq i)            → type aliased to i (resource property
1881
  //                              propagated when i refers to a resource)
1882
  //   * type (sub resource)    → fresh abstract resource type
1883
  //   * component / instance   → component / instance
1884
  //
1885
  // GAP-ED-1: bounds-check the referenced type index. The CoreType
1886
  // externdesc indexes the core:type space (the moduletype lives there);
1887
  // FuncType / ComponentType / InstanceType all index the component-level
1888
  // type space. Kind-of-type checks (e.g. that a FuncType index actually
1889
  // resolves to a component func type, not a record) are a follow-up that
1890
  // needs the type body to be retained on every type entry.
1891
5.99k
  switch (Desc.getDescType()) {
1892
127
  case AST::Component::ExternDesc::DescType::CoreType: {
1893
127
    const uint32_t RefIdx = Desc.getTypeIndex();
1894
127
    const uint32_t CoreTypeSize =
1895
127
        CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type);
1896
127
    if (RefIdx >= CoreTypeSize) {
1897
7
      spdlog::error(ErrCode::Value::InvalidIndex);
1898
7
      spdlog::error(
1899
7
          "    ExternDesc: core type index {} exceeds core:type index space size {}"sv,
1900
7
          RefIdx, CoreTypeSize);
1901
7
      return Unexpect(ErrCode::Value::InvalidIndex);
1902
7
    }
1903
120
    break;
1904
127
  }
1905
450
  case AST::Component::ExternDesc::DescType::FuncType:
1906
773
  case AST::Component::ExternDesc::DescType::ComponentType:
1907
1.01k
  case AST::Component::ExternDesc::DescType::InstanceType: {
1908
1.01k
    const uint32_t RefIdx = Desc.getTypeIndex();
1909
1.01k
    const uint32_t TypeSize =
1910
1.01k
        CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type);
1911
1.01k
    if (RefIdx >= TypeSize) {
1912
37
      spdlog::error(ErrCode::Value::InvalidIndex);
1913
37
      spdlog::error(
1914
37
          "    ExternDesc: referenced type index {} exceeds type index space size {}"sv,
1915
37
          RefIdx, TypeSize);
1916
37
      return Unexpect(ErrCode::Value::InvalidIndex);
1917
37
    }
1918
978
    break;
1919
1.01k
  }
1920
4.85k
  default:
1921
4.85k
    break;
1922
5.99k
  }
1923
1924
5.95k
  switch (Desc.getDescType()) {
1925
120
  case AST::Component::ExternDesc::DescType::CoreType: {
1926
    // The CoreType externdesc is `(core module (type i))`, so it introduces
1927
    // a new core:module slot bound to the moduletype at core:type idx i.
1928
120
    const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex());
1929
120
    CompCtx.addCoreModule(CT);
1930
120
    break;
1931
0
  }
1932
426
  case AST::Component::ExternDesc::DescType::FuncType:
1933
426
    CompCtx.addFunc();
1934
426
    break;
1935
2.70k
  case AST::Component::ExternDesc::DescType::ValueBound:
1936
2.70k
    CompCtx.addValue();
1937
2.70k
    break;
1938
2.14k
  case AST::Component::ExternDesc::DescType::TypeBound:
1939
2.14k
    if (Desc.isEqType()) {
1940
      // (type (eq i)) — alias type i
1941
85
      uint32_t RefIdx = Desc.getTypeIndex();
1942
85
      if (RefIdx >=
1943
85
          CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
1944
16
        spdlog::error(ErrCode::Value::InvalidIndex);
1945
16
        spdlog::error("    ExternDesc: eq type bound index {} out of bounds"sv,
1946
16
                      RefIdx);
1947
16
        return Unexpect(ErrCode::Value::InvalidIndex);
1948
16
      }
1949
      // (eq i): inherits the source resource's id; body lives on the
1950
      // shared registry entry.
1951
69
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1952
69
      if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) {
1953
24
        CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false});
1954
24
      }
1955
2.05k
    } else {
1956
      // (sub resource): abstract import — fresh id with no body.
1957
2.05k
      uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false);
1958
2.05k
      CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(),
1959
2.05k
                                   /*LocallyDefined=*/false});
1960
2.05k
    }
1961
2.12k
    break;
1962
2.12k
  case AST::Component::ExternDesc::DescType::ComponentType: {
1963
    // Bind the new component slot to its ComponentType so a later
1964
    // instantiation can pull imports/exports from it (GAP-I-1).
1965
318
    const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex());
1966
318
    CompCtx.addComponent(CT);
1967
318
    break;
1968
2.14k
  }
1969
234
  case AST::Component::ExternDesc::DescType::InstanceType: {
1970
    // GAP-ED-2: populate exports from the referenced InstanceType so
1971
    // alias-export resolves. GAP-I-5b: also bind it to the slot.
1972
234
    const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex());
1973
234
    uint32_t InstIdx = CompCtx.addInstance(IT);
1974
234
    if (IT != nullptr) {
1975
82
      populateInstanceFromType(InstIdx, *IT);
1976
82
    }
1977
    // If the type index resolved against an outer scope's InstanceType,
1978
    // populating from it requires cross-scope walking (the rest of
1979
    // GAP-DECL-ED). For now the instance keeps an empty export table when
1980
    // the InstanceType body isn't visible in this scope.
1981
234
    break;
1982
2.14k
  }
1983
0
  default:
1984
0
    assumingUnreachable();
1985
5.95k
  }
1986
1987
5.93k
  return {};
1988
5.95k
}
1989
1990
Expect<void>
1991
1.25k
Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept {
1992
1.25k
  if (Desc.isFunc()) {
1993
36
    uint32_t TypeIdx = Desc.getTypeIndex();
1994
36
    if (TypeIdx >= CompCtx.getCoreSortIndexSize(
1995
36
                       AST::Component::Sort::CoreSortType::Type)) {
1996
30
      spdlog::error(ErrCode::Value::InvalidIndex);
1997
30
      spdlog::error("    CoreImportDesc: func type index {} out of bounds"sv,
1998
30
                    TypeIdx);
1999
30
      return Unexpect(ErrCode::Value::InvalidIndex);
2000
30
    }
2001
6
    CompCtx.addCoreFunc();
2002
1.22k
  } else if (Desc.isTable()) {
2003
415
    CompCtx.addCoreTable();
2004
807
  } else if (Desc.isMemory()) {
2005
422
    CompCtx.addCoreMemory();
2006
422
  } else if (Desc.isGlobal()) {
2007
253
    CompCtx.addCoreGlobal();
2008
253
  } else if (Desc.isTag()) {
2009
132
    CompCtx.addCoreTag();
2010
132
  } else {
2011
0
    assumingUnreachable();
2012
0
  }
2013
1.22k
  return {};
2014
1.25k
}
2015
2016
Expect<void>
2017
542
Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept {
2018
542
  return validate(Decl.getImportDesc());
2019
542
}
2020
2021
Expect<void>
2022
716
Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept {
2023
716
  return validate(Decl.getImportDesc());
2024
716
}
2025
2026
Expect<void>
2027
1.44k
Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept {
2028
1.44k
  auto ReportError = [](auto E) {
2029
64
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule));
2030
64
    return E;
2031
64
  };
2032
2033
1.44k
  if (Decl.isImport()) {
2034
542
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2035
902
  } else if (Decl.isType()) {
2036
48
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2037
854
  } else if (Decl.isAlias()) {
2038
138
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2039
716
  } else if (Decl.isExport()) {
2040
716
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2041
716
  } else {
2042
0
    assumingUnreachable();
2043
0
  }
2044
1.38k
  return {};
2045
1.44k
}
2046
2047
Expect<void>
2048
341
Validator::validate(const AST::Component::ImportDecl &Decl) noexcept {
2049
341
  auto ReportError = [](auto E) {
2050
11
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2051
11
    return E;
2052
11
  };
2053
2054
  // Validate the extern descriptor (also increments sort index spaces).
2055
341
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError));
2056
2057
  // Parse and validate the import name.
2058
664
  EXPECTED_TRY(ComponentName CName,
2059
664
               ComponentName::parse(Decl.getName()).map_error(ReportError));
2060
2061
  // Annotated plainnames can only appear on func imports.
2062
664
  switch (CName.getKind()) {
2063
0
  case ComponentNameKind::Constructor:
2064
0
  case ComponentNameKind::Method:
2065
0
  case ComponentNameKind::Static:
2066
0
    if (Decl.getExternDesc().getDescType() !=
2067
0
        AST::Component::ExternDesc::DescType::FuncType) {
2068
0
      spdlog::error(ErrCode::Value::ComponentInvalidName);
2069
0
      spdlog::error("    ImportDecl: annotated name requires func type"sv);
2070
0
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2071
0
      return Unexpect(ErrCode::Value::ComponentInvalidName);
2072
0
    }
2073
0
    break;
2074
330
  default:
2075
330
    break;
2076
664
  }
2077
2078
  // Check import name uniqueness.
2079
330
  if (!CompCtx.addImportedName(CName)) {
2080
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
2081
0
    spdlog::error("    ImportDecl: Duplicate import name"sv);
2082
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import));
2083
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
2084
0
  }
2085
2086
330
  return {};
2087
330
}
2088
2089
Expect<void>
2090
255
Validator::validate(const AST::Component::ExportDecl &Decl) noexcept {
2091
  // Check export name grammar and uniqueness before mutating the index
2092
  // spaces so a duplicate or malformed name doesn't widen the scope's
2093
  // sort counts.
2094
255
  EXPECTED_TRY(ComponentName CName,
2095
253
               validateExportName(Decl.getName()).map_error([](auto E) {
2096
253
                 spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2097
253
                 return E;
2098
253
               }));
2099
253
  if (!CompCtx.addExportedName(CName)) {
2100
0
    spdlog::error(ErrCode::Value::ComponentDuplicateName);
2101
0
    spdlog::error("    ExportDecl: Duplicate export name '{}'"sv,
2102
0
                  Decl.getName());
2103
0
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2104
0
    return Unexpect(ErrCode::Value::ComponentDuplicateName);
2105
0
  }
2106
2107
  // Validate the extern descriptor (also increments sort index spaces and
2108
  // performs type-index bounds checking). For nested ExportDecls whose
2109
  // ExternDesc references a type index defined in an outer scope, the
2110
  // current scope's lookup may return nullptr — validate(ExternDesc) handles
2111
  // that gracefully by allocating an empty instance slot. Cross-scope
2112
  // type-index resolution (walking the parent CompCtxs chain to resolve
2113
  // names like `(export "f" (func (type 0)))` where type 0 lives outside
2114
  // the InstanceType body) is the structural follow-up tracked by the rest
2115
  // of GAP-DECL-ED / GAP-ED-1 / GAP-ED-2.
2116
253
  EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) {
2117
251
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export));
2118
251
    return E;
2119
251
  }));
2120
2121
251
  return {};
2122
253
}
2123
2124
Expect<void>
2125
1.29k
Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept {
2126
1.29k
  auto ReportError = [](auto E) {
2127
82
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance));
2128
82
    return E;
2129
82
  };
2130
2131
1.29k
  if (Decl.isCoreType()) {
2132
43
    EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError));
2133
1.24k
  } else if (Decl.isType()) {
2134
810
    EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError));
2135
810
  } else if (Decl.isAlias()) {
2136
184
    EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError));
2137
166
    const auto &A = Decl.getAlias();
2138
166
    const auto &Sort = A.getSort();
2139
166
    if (Sort.isCore()) {
2140
0
      CompCtx.incCoreSortIndexSize(Sort.getCoreSortType());
2141
166
    } else {
2142
166
      uint32_t NewInstIdx = CompCtx.incSortIndexSize(Sort.getSortType());
2143
      // Outer-aliasing a resource type keeps the resource's identity in this
2144
      // scope so later own/borrow and (eq i) checks treat the slot correctly.
2145
166
      if (A.getTargetType() == AST::Component::Alias::TargetType::Outer &&
2146
166
          Sort.getSortType() == AST::Component::Sort::SortType::Type) {
2147
166
        CompCtx.carryOuterResource(NewInstIdx, A.getOuter().first,
2148
166
                                   A.getOuter().second);
2149
166
      }
2150
      // When aliasing an instance export, propagate either the source
2151
      // instance's export table (for Instance targets) or the source
2152
      // export's resource identity (for Type targets that are resources)
2153
      // — mirrors the AliasSection handling.
2154
166
      if (A.getTargetType() == AST::Component::Alias::TargetType::Export) {
2155
0
        const auto SrcInstIdx = A.getExport().first;
2156
0
        const auto &SrcName = A.getExport().second;
2157
0
        const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports;
2158
0
        auto It = SrcExports.find(std::string(SrcName));
2159
0
        if (It != SrcExports.end()) {
2160
0
          if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) {
2161
0
            if (It->second.IT != nullptr) {
2162
0
              populateInstanceFromType(NewInstIdx, *It->second.IT);
2163
0
            } else if (It->second.NestedInstIdx.has_value()) {
2164
0
              const auto &NestedExports =
2165
0
                  CompCtx.getInstance(*It->second.NestedInstIdx).Exports;
2166
0
              for (const auto &[Name, IE] : NestedExports) {
2167
0
                CompCtx.addInstanceExport(NewInstIdx, Name, IE.ST, IE.IT,
2168
0
                                          IE.NestedInstIdx, IE.ResourceId);
2169
0
              }
2170
0
            }
2171
0
          } else if (Sort.getSortType() ==
2172
0
                         AST::Component::Sort::SortType::Type &&
2173
0
                     It->second.ResourceId.has_value()) {
2174
0
            CompCtx.addResource(NewInstIdx, {*It->second.ResourceId,
2175
0
                                             /*LocallyDefined=*/false});
2176
0
          }
2177
0
        }
2178
0
      }
2179
166
    }
2180
255
  } else if (Decl.isExportDecl()) {
2181
255
    EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError));
2182
255
  } else {
2183
0
    assumingUnreachable();
2184
0
  }
2185
1.21k
  return {};
2186
1.29k
}
2187
2188
Expect<void>
2189
1.14k
Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept {
2190
1.14k
  auto ReportError = [](auto E) {
2191
47
    spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component));
2192
47
    return E;
2193
47
  };
2194
2195
1.14k
  if (Decl.isImportDecl()) {
2196
341
    EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError));
2197
807
  } else if (Decl.isInstanceDecl()) {
2198
807
    EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError));
2199
807
  } else {
2200
0
    assumingUnreachable();
2201
0
  }
2202
1.10k
  return {};
2203
1.14k
}
2204
2205
19.4k
Expect<void> Validator::validate(const ComponentValType &VT) noexcept {
2206
19.4k
  if (VT.getCode() == ComponentTypeCode::TypeIndex) {
2207
15.8k
    uint32_t Idx = VT.getTypeIndex();
2208
15.8k
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2209
69
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2210
69
      spdlog::error("    ComponentValType: type index {} out of bounds"sv, Idx);
2211
69
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2212
69
    }
2213
15.7k
    const auto *DT = CompCtx.getDefType(Idx);
2214
15.7k
    if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) {
2215
20
      spdlog::error(ErrCode::Value::NotADefinedType);
2216
20
      spdlog::error(
2217
20
          "    ComponentValType: type index {} is not a defined value type"sv,
2218
20
          Idx);
2219
20
      return Unexpect(ErrCode::Value::NotADefinedType);
2220
20
    }
2221
15.7k
  }
2222
19.3k
  return {};
2223
19.4k
}
2224
2225
Expect<void>
2226
70.5k
Validator::validate(const AST::Component::DefValType &DVT) noexcept {
2227
70.5k
  if (DVT.isOwnTy()) {
2228
463
    uint32_t Idx = DVT.getOwn().Idx;
2229
463
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2230
19
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2231
19
      spdlog::error("    DefValType: own type index {} out of bounds"sv, Idx);
2232
19
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2233
19
    }
2234
444
    if (CompCtx.getResource(Idx) == nullptr) {
2235
38
      spdlog::error(ErrCode::Value::NotADefinedType);
2236
38
      spdlog::error(
2237
38
          "    DefValType: own type index {} does not refer to a resource type"sv,
2238
38
          Idx);
2239
38
      return Unexpect(ErrCode::Value::NotADefinedType);
2240
38
    }
2241
70.0k
  } else if (DVT.isBorrowTy()) {
2242
428
    uint32_t Idx = DVT.getBorrow().Idx;
2243
428
    if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) {
2244
29
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
2245
29
      spdlog::error("    DefValType: borrow type index {} out of bounds"sv,
2246
29
                    Idx);
2247
29
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
2248
29
    }
2249
399
    if (CompCtx.getResource(Idx) == nullptr) {
2250
15
      spdlog::error(ErrCode::Value::NotADefinedType);
2251
15
      spdlog::error(
2252
15
          "    DefValType: borrow type index {} does not refer to a resource type"sv,
2253
15
          Idx);
2254
15
      return Unexpect(ErrCode::Value::NotADefinedType);
2255
15
    }
2256
69.6k
  } else if (DVT.isRecordTy()) {
2257
714
    const auto &Rec = DVT.getRecord();
2258
714
    if (Rec.LabelTypes.empty()) {
2259
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2260
0
      spdlog::error("    DefValType: record must have at least one field"sv);
2261
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2262
0
    }
2263
714
    std::unordered_set<std::string> Seen;
2264
717
    for (const auto &LT : Rec.LabelTypes) {
2265
717
      if (LT.getLabel().empty()) {
2266
1
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2267
1
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2268
1
      }
2269
716
      if (!isKebabString(LT.getLabel())) {
2270
2
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2271
2
        spdlog::error(
2272
2
            "    DefValType: record field '{}' is not valid kebab-case"sv,
2273
2
            LT.getLabel());
2274
2
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2275
2
      }
2276
714
      if (!Seen.insert(toLowerStr(LT.getLabel())).second) {
2277
1
        spdlog::error(ErrCode::Value::RecordFieldNameConflicts);
2278
1
        spdlog::error("    DefValType: duplicate record field '{}'"sv,
2279
1
                      LT.getLabel());
2280
1
        return Unexpect(ErrCode::Value::RecordFieldNameConflicts);
2281
1
      }
2282
713
      EXPECTED_TRY(validate(LT.getValType()));
2283
713
    }
2284
68.9k
  } else if (DVT.isVariantTy()) {
2285
670
    const auto &Var = DVT.getVariant();
2286
670
    if (Var.Cases.empty()) {
2287
2
      spdlog::error(ErrCode::Value::VariantMustHaveCase);
2288
2
      return Unexpect(ErrCode::Value::VariantMustHaveCase);
2289
2
    }
2290
668
    std::unordered_set<std::string> Seen;
2291
668
    for (const auto &C : Var.Cases) {
2292
668
      if (C.first.empty()) {
2293
1
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2294
1
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2295
1
      }
2296
667
      if (!isKebabString(C.first)) {
2297
4
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2298
4
        spdlog::error(
2299
4
            "    DefValType: variant case '{}' is not valid kebab-case"sv,
2300
4
            C.first);
2301
4
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2302
4
      }
2303
663
      if (!Seen.insert(toLowerStr(C.first)).second) {
2304
0
        spdlog::error(ErrCode::Value::VariantCaseNameConflicts);
2305
0
        spdlog::error("    DefValType: duplicate variant case '{}'"sv, C.first);
2306
0
        return Unexpect(ErrCode::Value::VariantCaseNameConflicts);
2307
0
      }
2308
663
      if (C.second.has_value()) {
2309
527
        EXPECTED_TRY(validate(*C.second));
2310
527
      }
2311
663
    }
2312
68.2k
  } else if (DVT.isTupleTy()) {
2313
674
    if (DVT.getTuple().Types.empty()) {
2314
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2315
0
      spdlog::error("    DefValType: tuple must have at least one element"sv);
2316
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2317
0
    }
2318
782
    for (const auto &T : DVT.getTuple().Types) {
2319
782
      EXPECTED_TRY(validate(T));
2320
782
    }
2321
67.5k
  } else if (DVT.isListTy()) {
2322
1.61k
    EXPECTED_TRY(validate(DVT.getList().ValTy));
2323
65.9k
  } else if (DVT.isOptionTy()) {
2324
498
    EXPECTED_TRY(validate(DVT.getOption().ValTy));
2325
65.4k
  } else if (DVT.isResultTy()) {
2326
2.30k
    const auto &R = DVT.getResult();
2327
2.30k
    if (R.ValTy.has_value()) {
2328
870
      EXPECTED_TRY(validate(*R.ValTy));
2329
870
    }
2330
2.30k
    if (R.ErrTy.has_value()) {
2331
1.43k
      EXPECTED_TRY(validate(*R.ErrTy));
2332
1.43k
    }
2333
63.1k
  } else if (DVT.isFlagsTy()) {
2334
144
    const auto &Flags = DVT.getFlags();
2335
144
    if (Flags.Labels.empty()) {
2336
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2337
0
      spdlog::error("    DefValType: flags must have at least one label"sv);
2338
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2339
0
    }
2340
144
    if (Flags.Labels.size() > 32) {
2341
0
      spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags);
2342
0
      return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags);
2343
0
    }
2344
144
    std::unordered_set<std::string> Seen;
2345
147
    for (const auto &L : Flags.Labels) {
2346
147
      if (L.empty()) {
2347
3
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2348
3
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2349
3
      }
2350
144
      if (!isKebabString(L)) {
2351
15
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2352
15
        spdlog::error(
2353
15
            "    DefValType: flags label '{}' is not valid kebab-case"sv, L);
2354
15
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2355
15
      }
2356
129
      if (!Seen.insert(toLowerStr(L)).second) {
2357
1
        spdlog::error(ErrCode::Value::FlagNameConflicts);
2358
1
        spdlog::error("    DefValType: duplicate flags label '{}'"sv, L);
2359
1
        return Unexpect(ErrCode::Value::FlagNameConflicts);
2360
1
      }
2361
129
    }
2362
63.0k
  } else if (DVT.isEnumTy()) {
2363
625
    const auto &Enm = DVT.getEnum();
2364
625
    if (Enm.Labels.empty()) {
2365
6
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2366
6
      spdlog::error("    DefValType: enum must have at least one label"sv);
2367
6
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2368
6
    }
2369
619
    std::unordered_set<std::string> Seen;
2370
625
    for (const auto &L : Enm.Labels) {
2371
625
      if (L.empty()) {
2372
4
        spdlog::error(ErrCode::Value::NameCannotBeEmpty);
2373
4
        return Unexpect(ErrCode::Value::NameCannotBeEmpty);
2374
4
      }
2375
621
      if (!isKebabString(L)) {
2376
7
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2377
7
        spdlog::error(
2378
7
            "    DefValType: enum label '{}' is not valid kebab-case"sv, L);
2379
7
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2380
7
      }
2381
614
      if (!Seen.insert(toLowerStr(L)).second) {
2382
1
        spdlog::error(ErrCode::Value::EnumTagNameConflicts);
2383
1
        spdlog::error("    DefValType: duplicate enum label '{}'"sv, L);
2384
1
        return Unexpect(ErrCode::Value::EnumTagNameConflicts);
2385
1
      }
2386
614
    }
2387
62.3k
  } else if (DVT.isStreamTy()) {
2388
814
    if (DVT.getStream().ValTy.has_value()) {
2389
292
      EXPECTED_TRY(validate(*DVT.getStream().ValTy));
2390
292
    }
2391
61.5k
  } else if (DVT.isFutureTy()) {
2392
541
    if (DVT.getFuture().ValTy.has_value()) {
2393
109
      EXPECTED_TRY(validate(*DVT.getFuture().ValTy));
2394
109
    }
2395
541
  }
2396
70.3k
  return {};
2397
70.5k
}
2398
2399
9.94k
Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept {
2400
  // Validate param names: kebab-case + unique
2401
9.94k
  std::unordered_set<std::string_view> ParamNames;
2402
9.94k
  for (const auto &P : FT.getParamList()) {
2403
3.39k
    if (!P.getLabel().empty()) {
2404
1.68k
      if (!isKebabString(P.getLabel())) {
2405
14
        spdlog::error(ErrCode::Value::ComponentInvalidName);
2406
14
        spdlog::error(
2407
14
            "    FuncType: parameter name '{}' is not valid kebab-case"sv,
2408
14
            P.getLabel());
2409
14
        return Unexpect(ErrCode::Value::ComponentInvalidName);
2410
14
      }
2411
1.67k
      if (!ParamNames.insert(P.getLabel()).second) {
2412
1
        spdlog::error(ErrCode::Value::ComponentDuplicateName);
2413
1
        spdlog::error("    FuncType: duplicate parameter name '{}'"sv,
2414
1
                      P.getLabel());
2415
1
        return Unexpect(ErrCode::Value::ComponentDuplicateName);
2416
1
      }
2417
1.67k
    }
2418
3.37k
    EXPECTED_TRY(validate(P.getValType()));
2419
3.37k
  }
2420
  // Reject transitive use of borrow in results
2421
9.91k
  for (const auto &R : FT.getResultList()) {
2422
9.26k
    EXPECTED_TRY(validate(R.getValType()));
2423
9.23k
    if (containsBorrow(R.getValType())) {
2424
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
2425
0
      spdlog::error(
2426
0
          "    FuncType: borrow type not allowed in function results"sv);
2427
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
2428
0
    }
2429
9.23k
  }
2430
9.89k
  return {};
2431
9.91k
}
2432
2433
Expect<void>
2434
348k
Validator::validate(const AST::Component::InstanceType &IT) noexcept {
2435
  // Instance types are validated with an initially-empty index space.
2436
348k
  CompCtx.enterTypeDefinition();
2437
348k
  for (const auto &Decl : IT.getDecl()) {
2438
485
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2439
485
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2440
485
      return E;
2441
485
    }));
2442
485
  }
2443
348k
  CompCtx.exitComponent();
2444
348k
  return {};
2445
348k
}
2446
2447
Expect<void>
2448
754k
Validator::validate(const AST::Component::ComponentType &CT) noexcept {
2449
  // Component types are validated with an initially-empty index space.
2450
754k
  CompCtx.enterTypeDefinition();
2451
754k
  for (const auto &Decl : CT.getDecl()) {
2452
1.14k
    EXPECTED_TRY(validate(Decl).map_error([](auto E) {
2453
1.14k
      spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType));
2454
1.14k
      return E;
2455
1.14k
    }));
2456
1.14k
  }
2457
754k
  CompCtx.exitComponent();
2458
754k
  return {};
2459
754k
}
2460
2461
Expect<void>
2462
16.5k
Validator::validate(const AST::Component::ResourceType &RT) noexcept {
2463
  // Resource types are not allowed inside componenttype/instancetype scopes.
2464
16.5k
  if (CompCtx.isTypeDefinitionScope()) {
2465
1
    spdlog::error(ErrCode::Value::InvalidTypeReference);
2466
1
    spdlog::error("    ResourceType: resource types cannot be defined inside "
2467
1
                  "componenttype or instancetype"sv);
2468
1
    return Unexpect(ErrCode::Value::InvalidTypeReference);
2469
1
  }
2470
16.5k
  if (RT.getDestructor().has_value()) {
2471
571
    uint32_t DtorIdx = *RT.getDestructor();
2472
571
    if (DtorIdx >= CompCtx.getCoreSortIndexSize(
2473
571
                       AST::Component::Sort::CoreSortType::Func)) {
2474
10
      spdlog::error(ErrCode::Value::InvalidIndex);
2475
10
      spdlog::error(
2476
10
          "    ResourceType: destructor core func index {} out of bounds"sv,
2477
10
          DtorIdx);
2478
10
      return Unexpect(ErrCode::Value::InvalidIndex);
2479
10
    }
2480
    // Verify the destructor's signature is `[i32] -> []`. The signature is
2481
    // only available for core funcs the validator has populated (canon
2482
    // resource.* synthesise it; aliased-from-core-module funcs are tracked
2483
    // as a TODO that needs CoreInstance export-type plumbing). Skip the
2484
    // check when the SubType pointer is null — the bounds check above
2485
    // already caught the obvious "no such func" mistake.
2486
    // TODO: also accept `[i64] -> []` when the resource rep is i64
2487
    // (memory64 proposal); needs ResourceType to carry rep size.
2488
561
    const AST::SubType *DtorST = CompCtx.getCoreFunc(DtorIdx);
2489
561
    if (DtorST != nullptr) {
2490
417
      const auto &DtorType = DtorST->getCompositeType();
2491
417
      const auto &ExpType = CoreFuncType_I32_Void.getCompositeType();
2492
417
      if (!DtorType.isFunc() ||
2493
417
          DtorType.getFuncType() != ExpType.getFuncType()) {
2494
1
        spdlog::error(ErrCode::Value::InvalidTypeReference);
2495
1
        spdlog::error(
2496
1
            "    ResourceType: destructor core func {} must have signature [i32] -> []"sv,
2497
1
            DtorIdx);
2498
1
        return Unexpect(ErrCode::Value::InvalidTypeReference);
2499
1
      }
2500
417
    }
2501
561
  }
2502
16.5k
  return {};
2503
16.5k
}
2504
2505
20.0k
bool Validator::containsBorrow(const ComponentValType &VT) const noexcept {
2506
20.0k
  if (VT.getCode() == ComponentTypeCode::Borrow) {
2507
0
    return true;
2508
0
  }
2509
20.0k
  if (VT.getCode() != ComponentTypeCode::TypeIndex) {
2510
5.47k
    return false;
2511
5.47k
  }
2512
14.5k
  uint32_t Idx = VT.getTypeIndex();
2513
14.5k
  const auto *DT = CompCtx.getDefType(Idx);
2514
14.5k
  if (DT == nullptr || !DT->isDefValType()) {
2515
2.09k
    return false;
2516
2.09k
  }
2517
12.4k
  return containsBorrow(DT->getDefValType());
2518
14.5k
}
2519
2520
bool Validator::containsBorrow(
2521
12.4k
    const AST::Component::DefValType &DVT) const noexcept {
2522
12.4k
  if (DVT.isBorrowTy()) {
2523
0
    return true;
2524
0
  }
2525
12.4k
  if (DVT.isRecordTy()) {
2526
1.94k
    for (const auto &F : DVT.getRecord().LabelTypes) {
2527
1.94k
      if (containsBorrow(F.getValType())) {
2528
0
        return true;
2529
0
      }
2530
1.94k
    }
2531
1.94k
    return false;
2532
1.94k
  }
2533
10.5k
  if (DVT.isVariantTy()) {
2534
2.01k
    for (const auto &C : DVT.getVariant().Cases) {
2535
2.01k
      if (C.second.has_value() && containsBorrow(*C.second)) {
2536
0
        return true;
2537
0
      }
2538
2.01k
    }
2539
2.01k
    return false;
2540
2.01k
  }
2541
8.50k
  if (DVT.isListTy()) {
2542
1.09k
    return containsBorrow(DVT.getList().ValTy);
2543
1.09k
  }
2544
7.40k
  if (DVT.isTupleTy()) {
2545
787
    for (const auto &T : DVT.getTuple().Types) {
2546
787
      if (containsBorrow(T)) {
2547
0
        return true;
2548
0
      }
2549
787
    }
2550
717
    return false;
2551
717
  }
2552
6.68k
  if (DVT.isOptionTy()) {
2553
89
    return containsBorrow(DVT.getOption().ValTy);
2554
89
  }
2555
6.59k
  if (DVT.isResultTy()) {
2556
4.67k
    const auto &R = DVT.getResult();
2557
4.67k
    return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) ||
2558
4.67k
           (R.ErrTy.has_value() && containsBorrow(*R.ErrTy));
2559
4.67k
  }
2560
1.92k
  if (DVT.isStreamTy()) {
2561
390
    return DVT.getStream().ValTy.has_value() &&
2562
200
           containsBorrow(*DVT.getStream().ValTy);
2563
390
  }
2564
1.53k
  if (DVT.isFutureTy()) {
2565
214
    return DVT.getFuture().ValTy.has_value() &&
2566
69
           containsBorrow(*DVT.getFuture().ValTy);
2567
214
  }
2568
1.32k
  return false; // PrimValType, OwnTy, FlagsTy, EnumTy
2569
1.53k
}
2570
2571
} // namespace Validator
2572
} // namespace WasmEdge