Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Type checker] Use DependentMemberType instead of type variables for nested types. #5626

Merged
merged 6 commits into from
Nov 6, 2016

Conversation

DougGregor
Copy link
Member

In the constraint solver, we've traditionally modeled nested type via
a "type member" constraint of the form

$T1 = $T0.NameOfTypeMember

and treated $T1 as a type variable. While the solver did generally try
to avoid attempting bindings for $T1 (it would wait until $T0 was
bound, which solves the constraint), on occasion we would get weird
behavior because the solver did try to bind the type
variable.

With this commit, model nested types via DependentMemberType, the same
way we handle (e.g.) the nested type of a generic type parameter. This
solution maintains more information (e.g., we know specifically which
associated type we're referring to), fits in better with the type
system (we know how to deal with dependent members throughout the type
checker, AST, and so on), and is easier to reason able.

This change is a performance optimization for the type checker for a
few reasons. First, it reduces the number of type variables we need to
deal with significantly (we create half as many type variables while
type checking the standard library), and the solver scales poorly with
the number of type variables because it visits all of the
as-yet-unbound type variables at each solving step. Second, it
eliminates a number of redundant by-name lookups in cases where we
already know which associated type we want.

Overall, this change provides a 25% speedup when type-checking the
standard library.

@DougGregor
Copy link
Member Author

@swift-ci please smoke test and merge

@DougGregor
Copy link
Member Author

Darn.

Fndation/NSScanner.swift:605:49: error: 'Int1' is not convertible to 'Bool'
let options: NSString.CompareOptions = [caseSensitive ? [] : NSString.CompareOptions.caseInsensitive, NSString.CompareOptions.anchored]
^~~~~~~~~~~~~

@DougGregor
Copy link
Member Author

Reduced bug:

struct X : OptionSet {
  let rawValue: Int

  static let option: X = X(rawValue: 1)
}

func test(b: Bool) {
  let x: X = [ b ? [] : X.option, X.option]
}

@slavapestov
Copy link
Contributor

Overall, this change provides a 25% speedup when type-checking the
standard library.

Bugs aside, this is awesome!

return type.transform([&](Type type) -> Type {
if (auto tvt = dyn_cast<TypeVariableType>(type.getPointer())) {
auto known = typeBindings.find(tvt);
assert(known != typeBindings.end());
return known->second;
}

// If this is a dependent member type for which we end up simplifying
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks!

auto dictionaryValueTy = CS.getMemberType(dictionaryTy,
valueAssocTy,
locatorBuilder.withPathElement(
ConstraintLocator::ArrayElementType),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ArrayElementType not used anymore?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, it's still used... actually, abused, because it's used for completely unrelated things.

The constraint solver scales poorly with the number of type variables,
so track it.
…nested types.

In the constraint solver, we've traditionally modeled nested type via
a "type member" constraint of the form

  $T1 = $T0.NameOfTypeMember

and treated $T1 as a type variable. While the solver did generally try
to avoid attempting bindings for $T1 (it would wait until $T0 was
bound, which solves the constraint), on occasion we would get weird
behavior because the solver did try to bind the type
variable.

With this commit, model nested types via DependentMemberType, the same
way we handle (e.g.) the nested type of a generic type parameter. This
solution maintains more information (e.g., we know specifically which
associated type we're referring to), fits in better with the type
system (we know how to deal with dependent members throughout the type
checker, AST, and so on), and is easier to reason able.

This change is a performance optimization for the type checker for a
few reasons. First, it reduces the number of type variables we need to
deal with significantly (we create half as many type variables while
type checking the standard library), and the solver scales poorly with
the number of type variables because it visits all of the
as-yet-unbound type variables at each solving step. Second, it
eliminates a number of redundant by-name lookups in cases where we
already know which associated type we want.

Overall, this change provides a 25% speedup when type-checking the
standard library.
The ASTContext had a wacky "get member type" callback that actually
called back into the constraint system (!) to build member types. This
callback was obsoleted by the change that started representing nested
types as DependentMemberTypes.
The constraint solver tries not to solve for type variables that
"involve other type variables", which handles the case where we have
seen a constraint that mentions the type variable under consideration
as well as a different type variable, but in a constraint that we
cannot capture in a binding. Solving for such type variables too early
can lead to missed solutions, so we avoid it.

Tweak the logic for this computation to not consider type variables
mentioned within dependent member types (e.g., $T0.Iterator.Element),
because such types do not affect type inference at all, and therefore
shouldn't prevent solving for the type variable in question.
@DougGregor
Copy link
Member Author

@swift-ci please smoke test and merge

The code in TypeBase::gatherAllSubstitutions() walks a declaration
context and type to form a complete set of substitutions for a
(nested) type. When provided with a context for a nested
extension---e.g., "extension OuterGeneric.InnerGeneric", it would skip
over OuterGeneric due to the lexical nesting of DeclContexts not
matching the semantic nesting.

Teach this function to determine the type parameters without walking DeclContexts.
@DougGregor
Copy link
Member Author

... fixed a latent bug in gatherAllSubstitutions() that this somehow triggered.

@DougGregor
Copy link
Member Author

@swift-ci please smoke test and merge

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please smoke test and merge

@DougGregor
Copy link
Member Author

@swift-ci please smoke test Linux

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please smoke test Linux

@DougGregor DougGregor merged commit 8271c1a into swiftlang:master Nov 6, 2016
@DougGregor DougGregor deleted the dependent-type-variables branch November 6, 2016 15:27
@practicalswift
Copy link
Contributor

@DougGregor Thank you! Excellent stuff! :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants