How to typecheck pattern in CaseStmt before building TypeRefinementContext

Hi everyone!
Recently I have often run into SR-4079, so I decided to use this as interesting task to make first contribution to swift and get a little compiler practice.
Firstly I ran swift -dump-ast on simple enum with availability attributes on cases

enum SomeEnum {
  case unrestricted
  @available(OSX 10.52, *)
  case availableOn1052
  @available(OSX 10.53, *)
  case availableOn1053

  var availableVersion: Int? {
    switch self {
    case .unrestricted:
      return nil
    case .availableOn1052:
      return 1052
    case .availableOn1053:
      return 1053
    }
  }
}

and found the following important part for my task:

(case_stmt range=...
         (case_label_item
         (pattern_enum_element type='SomeEnum' SomeEnum.unrestricted))

I looked through CaseStmt class and located getPattern() method with EnumElementPattern subclass. Using that classes I added minimal method buildCaseStmtRefinementContext to TypeRefinementContextBuilder:

 void buildCaseStmtRefinementContext(CaseStmt *CS) {
    Optional<AvailabilityContext> BodyRange = None;
    for (const auto &caseItem : CS->getCaseLabelItems()) {
      auto *elementPattern = dyn_cast<EnumElementPattern>(caseItem.getPattern());
      if (!elementPattern)
        continue;

      auto *D = elementPattern->getElementDecl();
      if (!hasActiveAvailableAttribute(D, Context))
        continue;

      AvailabilityContext ElementInfo =
          swift::AvailabilityInference::availableRange(D, Context);
      if (BodyRange.hasValue()) {
        BodyRange.getValue().constrainWith(ElementInfo);
      } else {
        BodyRange = ElementInfo;
      }
    }
    if (BodyRange.hasValue()) {
      // Create a new context for the body and traverse it in the new
      // context.
      auto *BodyTRC = TypeRefinementContext::createForCaseStmtBody(
          Context, CS, getCurrentTRC(), BodyRange.getValue());
      TypeRefinementContextBuilder(BodyTRC, Context).build(CS->getBody());
    } else {
      build(CS->getBody());
    }
  }

After compiling the compiler the problem remained in place, therefore I had to dig deeper. Two hours of debugging led me to TypeCheckSourceFileRequest where availability refinement context builds before type checking decls. CS->dump() outputs

(pattern_expr
  (unresolved_member_expr type='<null>' name='unrestricted' arg_labels=))

I can't get original enum case decl with attributes from unresolved_member_expr. In ideal world there is API for partial typechecking before the full one, and I suspect swift has one, but I don't know how to use it :(

I don't think there is currently a mechanism for doing this. Type refinement contexts are built very early on because they're used for name lookup, and name lookup is used by type checking. So attempting to type check while building type refinement contexts would introduce a circular dependency inside the implementation.

Thanks you, Slava. I think I got around this limitation by deferring building refinement context for CaseStmt until case labels type checked. Could you please take a look at SR-4079: Make Enum CaseStmt introduce new type refinement context by goingreen · Pull Request #29073 · apple/swift · GitHub

1 Like